Core interfaces for Medusa e-commerce framework service implementations
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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.
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;Method to get the service identifier.
/**
* Returns the service identifier from constructor
* @returns {string} Service identifier
*/
getIdentifier(): string;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;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;
}
}Notification services are typically used for:
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);
}
}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;
}
}Both abstract methods throw descriptive errors when not implemented:
"Must be overridden by child" (for sendNotification)"Must be overridden by child" (for resendNotification)