CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-medusa-interfaces

Core interfaces for Medusa e-commerce framework service implementations

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

notification-service.mddocs/

Notification Service

Interface for notification service implementations enabling sending and resending notifications through various channels like email, SMS, or push notifications.

Deprecation Notice: Use AbstractNotificationService from @medusajs/medusa instead.

Capabilities

Static Methods

Type checking and identification methods for notification services.

/**
 * Static property identifying this as a notification service
 */
static _isNotificationService: boolean;

/**
 * Checks if an object is a notification service
 * @param {object} obj - Object to check
 * @returns {boolean} True if obj is a notification service
 */
static isNotificationService(obj: object): boolean;

Service Identification

Method to get the service identifier.

/**
 * Returns the service identifier from constructor
 * @returns {string} Service identifier
 */
getIdentifier(): string;

Core Notification Operations

Abstract methods that must be implemented by child classes.

/**
 * Sends a notification for a specific event with provided data
 * @param {string} event - The event type that triggered the notification
 * @param {object} data - Data associated with the event and notification
 * @returns {any} Notification sending result
 * @throws {Error} If not overridden by child class
 */
sendNotification(event: string, data: object): any;

/**
 * Resends an existing notification with optional configuration
 * @param {object} notification - The notification object to resend
 * @param {object} config - Optional configuration for resending
 * @returns {any} Notification resending result
 * @throws {Error} If not overridden by child class
 */
resendNotification(notification: object, config?: object): any;

Implementation Example

import { NotificationService } from "medusa-interfaces";

class EmailNotificationService extends NotificationService {
  static identifier = "email-notification";
  
  constructor(options) {
    super();
    this.emailClient = options.email_client;
    this.templates = options.templates || {};
    this.from_email = options.from_email;
  }

  async sendNotification(event, data) {
    // Map events to email templates
    const template = this.getTemplateForEvent(event);
    if (!template) {
      throw new Error(`No template found for event: ${event}`);
    }

    const emailData = this.prepareEmailData(event, data, template);
    
    try {
      const result = await this.emailClient.send({
        from: this.from_email,
        to: emailData.to,
        subject: emailData.subject,
        html: emailData.html,
        text: emailData.text
      });

      return {
        id: result.messageId,
        event: event,
        recipient: emailData.to,
        status: "sent",
        sent_at: new Date()
      };
    } catch (error) {
      throw new Error(`Failed to send notification: ${error.message}`);
    }
  }

  async resendNotification(notification, config = {}) {
    // Override recipient if provided in config
    const recipient = config.recipient || notification.recipient;
    
    try {
      const result = await this.emailClient.send({
        from: this.from_email,
        to: recipient,
        subject: notification.subject,
        html: notification.html,
        text: notification.text
      });

      return {
        id: result.messageId,
        original_id: notification.id,
        event: notification.event,
        recipient: recipient,
        status: "resent",
        sent_at: new Date()
      };
    } catch (error) {
      throw new Error(`Failed to resend notification: ${error.message}`);
    }
  }

  getTemplateForEvent(event) {
    const eventTemplateMap = {
      "order.placed": "order_confirmation",
      "order.shipped": "order_shipped",
      "order.cancelled": "order_cancelled",
      "customer.password_reset": "password_reset",
      "customer.created": "welcome_email"
    };
    
    return this.templates[eventTemplateMap[event]];
  }

  prepareEmailData(event, data, template) {
    switch (event) {
      case "order.placed":
        return {
          to: data.order.email,
          subject: `Order Confirmation - #${data.order.display_id}`,
          html: template.renderHtml(data),
          text: template.renderText(data)
        };
      
      case "order.shipped":
        return {
          to: data.order.email,
          subject: `Your order has shipped - #${data.order.display_id}`,
          html: template.renderHtml(data),
          text: template.renderText(data)
        };
      
      case "customer.password_reset":
        return {
          to: data.customer.email,
          subject: "Password Reset Request",
          html: template.renderHtml(data),
          text: template.renderText(data)
        };
      
      default:
        throw new Error(`Unsupported event: ${event}`);
    }
  }
}

// SMS Notification Service Example
class SMSNotificationService extends NotificationService {
  static identifier = "sms-notification";
  
  constructor(options) {
    super();
    this.smsClient = options.sms_client;
    this.from_number = options.from_number;
  }

  async sendNotification(event, data) {
    const message = this.getMessageForEvent(event, data);
    const recipient = this.getRecipientPhoneNumber(data);
    
    if (!recipient) {
      throw new Error("No phone number found for SMS notification");
    }

    try {
      const result = await this.smsClient.messages.create({
        body: message,
        from: this.from_number,
        to: recipient
      });

      return {
        id: result.sid,
        event: event,
        recipient: recipient,
        status: "sent",
        sent_at: new Date()
      };
    } catch (error) {
      throw new Error(`Failed to send SMS: ${error.message}`);
    }
  }

  async resendNotification(notification, config = {}) {
    const recipient = config.recipient || notification.recipient;
    
    try {
      const result = await this.smsClient.messages.create({
        body: notification.message,
        from: this.from_number,
        to: recipient
      });

      return {
        id: result.sid,
        original_id: notification.id,
        event: notification.event,
        recipient: recipient,
        status: "resent",
        sent_at: new Date()
      };
    } catch (error) {
      throw new Error(`Failed to resend SMS: ${error.message}`);
    }
  }

  getMessageForEvent(event, data) {
    switch (event) {
      case "order.placed":
        return `Order confirmed! Your order #${data.order.display_id} has been placed.`;
      
      case "order.shipped":
        return `Your order #${data.order.display_id} has shipped! Track: ${data.tracking_url}`;
      
      default:
        return `Notification for event: ${event}`;
    }
  }

  getRecipientPhoneNumber(data) {
    return data.order?.phone || data.customer?.phone || data.phone;
  }
}

Usage in Medusa

Notification services are typically used for:

  • Order Notifications: Confirmation, shipping, cancellation
  • Customer Communications: Welcome emails, password resets
  • Admin Alerts: Low inventory, failed payments
  • Marketing: Promotional campaigns, newsletters

Basic Usage Pattern:

// In a Medusa service or subscriber
class OrderService {
  constructor({ notificationService }) {
    this.notificationService_ = notificationService;
  }

  async placeOrder(order) {
    // ... order placement logic
    
    // Send order confirmation notification
    await this.notificationService_.sendNotification("order.placed", {
      order: order,
      customer: order.customer,
      items: order.items
    });
    
    return order;
  }
}

// Event subscriber example
class OrderSubscriber {
  constructor({ notificationService }) {
    this.notificationService_ = notificationService;
  }

  async handleOrderPlaced(data) {
    await this.notificationService_.sendNotification("order.placed", data);
  }

  async handleOrderShipped(data) {
    await this.notificationService_.sendNotification("order.shipped", data);
  }
}

Multi-Channel Implementation

class MultiChannelNotificationService extends NotificationService {
  static identifier = "multi-channel";
  
  constructor(options) {
    super();
    this.emailService = options.email_service;
    this.smsService = options.sms_service;
    this.pushService = options.push_service;
  }

  async sendNotification(event, data) {
    const results = [];
    const channels = this.getChannelsForEvent(event, data);
    
    for (const channel of channels) {
      try {
        let result;
        switch (channel) {
          case "email":
            result = await this.emailService.sendNotification(event, data);
            break;
          case "sms":
            result = await this.smsService.sendNotification(event, data);
            break;
          case "push":
            result = await this.pushService.sendNotification(event, data);
            break;
        }
        
        results.push({ channel, ...result });
      } catch (error) {
        results.push({ 
          channel, 
          status: "failed", 
          error: error.message 
        });
      }
    }
    
    return results;
  }

  getChannelsForEvent(event, data) {
    // Determine which channels to use based on event and user preferences
    const defaultChannels = ["email"];
    
    if (data.customer?.notification_preferences) {
      return data.customer.notification_preferences[event] || defaultChannels;
    }
    
    return defaultChannels;
  }
}

Error Handling

Both abstract methods throw descriptive errors when not implemented:

  • "Must be overridden by child" (for sendNotification)
  • "Must be overridden by child" (for resendNotification)

docs

base-service.md

file-service.md

fulfillment-service.md

index.md

notification-service.md

oauth-service.md

payment-service.md

search-service.md

tile.json