Integrate Slack (Notification) with Medusa
In this tutorial, you'll learn how to integrate Slack with Medusa to receive notifications about created orders.
When you install a Medusa application, you get a fully-fledged commerce platform with a Framework for customization. Medusa's architecture facilitates integrating third-party services to customize Medusa's infrastructure for your business needs.
Medusa's Notification Module allows you to customize Medusa's infrastructure to send notifications using the third-party provider that fits your business needs, such as Slack.
In this tutorial, you'll integrate Slack with Medusa to receive notifications about created orders.
Summary#
By following this tutorial, you'll learn how to:
- Install and set up Medusa.
- Integrate Slack with Medusa.
- Handle Medusa's
order.placed
event to send notifications to Slack.
You can follow this tutorial whether you're new to Medusa or an advanced Medusa developer.
Step 1: Install a Medusa Application#
Start by installing the Medusa application on your machine with the following command:
First, you'll be asked for the project's name. Then, when prompted about installing the Next.js Starter Storefront, choose "Yes."
Afterwards, the installation process will start, which will install the Medusa application in a directory with your project's name and the Next.js Starter Storefront in a separate directory named {project-name}-storefront
.
Once the installation finishes successfully, the Medusa Admin dashboard will open with a form to create a new user. Enter the user's credentials and submit the form. Afterwards, you can log in with the new user and explore the dashboard.
Step 2: Create Slack Module Provider#
To integrate third-party services into Medusa, you create a custom module. A module is a reusable package with functionalities related to a single feature or domain.
Medusa's Notification Module provides an interface to send notifications in your Medusa application. It delegates the actual sending of notifications to the underlying provider, such as Slack.
In this step, you'll integrate Slack as a Notification Module Provider. Later, you'll use it to send a notification when an order is created.
a. Install Axios#
To send requests to Slack, you'll use the Axios library. So, run the following command to install it in your Medusa application:
You'll use Axios in the module's service.
b. Create Module Directory#
A module is created under the src/modules
directory of your Medusa application. So, create the directory src/modules/slack
.
c. Create Slack Module's Service#
A module has a service that contains its logic. For Notification Module Providers, the service implements the logic to send notifications with a third-party service.
To create the service of the Slack Notification Module Provider, create the file src/modules/slack/service.ts
with the following content:
1import { 2 AbstractNotificationProviderService,3} from "@medusajs/framework/utils"4 5type Options = {6 webhook_url: string7 admin_url: string8}9 10type InjectedDependencies = {}11 12class SlackNotificationProviderService extends AbstractNotificationProviderService {13 static identifier = "slack"14 protected options: Options15 16 constructor(container: InjectedDependencies, options: Options) {17 super()18 this.options = options19 }20}
A Notification Module Provider's service must extend the AbstractNotificationProviderService
class. You'll implement its methods in a bit.
The service must also have an identifier
static property, which is a unique identifier for the module. This identifier is used when registering the module in the Medusa container.
The service's constructor receives two parameters:
container
: The module's container that contains Framework resources available to the module. You don't need to access any resources for this provider.options
: Options that are passed to the module provider when it's registered in Medusa's configurations. You define the following options:webhook_url
: The Slack webhook URL to send notifications to.admin_url
: The URL of the Medusa Admin dashboard, which you'll use to add links in the notifications.
In the constructor, you set the options
property to the passed options.
In the next sections, you'll implement the methods of the AbstractNotificationProviderService
class.
d. Implement validateOptions Method#
The validateOptions
method is used to validate the options passed to the module provider. If the method throws an error, the Medusa application won't start.
So, add the validateOptions
method to the SlackNotificationProviderService
class:
1// other imports...2import { 3 MedusaError,4} from "@medusajs/framework/utils"5 6class SlackNotificationProviderService extends AbstractNotificationProviderService {7 // ...8 static validateOptions(options: Record<any, any>): void | never {9 if (!options.webhook_url) {10 throw new MedusaError(11 MedusaError.Types.INVALID_ARGUMENT,12 "Webhook URL is required"13 )14 }15 if (!options.admin_url) {16 throw new MedusaError(17 MedusaError.Types.INVALID_ARGUMENT,18 "Admin URL is required"19 )20 }21 }22}
The validateOptions
method receives the options passed to the module provider as a parameter.
In the method, you throw an error if any of the options are not set.
e. Implement send Method#
When the Medusa application needs to send a notification through a channel (such as Slack), it calls the send
method of the channel's module provider.
You'll first add helper methods that you'll use in the send
method. Then, you'll implement the send
method itself.
getDisplayAmount Method
The first method you'll add is a method to format amounts for displaying them in notifications. So, add the getDisplayAmount
method to the SlackNotificationProviderService
class:
The getDisplayAmount
method receives an amount and a currency code and returns the formatted amount as a string.
sendOrderNotification Method
Next, you'll add a method to format a Slack message for created orders. So, add the sendOrderNotification
method to the SlackNotificationProviderService
class:
1// other imports...2import { 3 OrderDTO, 4 ProviderSendNotificationDTO, 5 ProviderSendNotificationResultsDTO,6} from "@medusajs/framework/types"7import axios from "axios"8 9class SlackNotificationProviderService extends AbstractNotificationProviderService {10 // ...11 async sendOrderNotification(notification: ProviderSendNotificationDTO) {12 const order = notification.data?.order as OrderDTO13 if (!order) {14 throw new MedusaError(15 MedusaError.Types.NOT_FOUND,16 "Order not found"17 )18 }19 const shippingAddress = order.shipping_address20 const blocks: Record<string, unknown>[] = [21 {22 type: "section",23 text: {24 type: "mrkdwn",25 text: `Order *<${this.options.admin_url}/orders/${order.id}|#${order.display_id}>* has been created.`,26 },27 },28 ]29 30 if (shippingAddress) {31 blocks.push({32 type: "section",33 text: {34 type: "mrkdwn",35 text: `*Customer*\n${shippingAddress.first_name} ${36 shippingAddress.last_name37 }\n${order.email}\n*Destination*\n${38 shippingAddress.address_139 }\n${40 shippingAddress.city41 }, ${(shippingAddress.country_code as string).toUpperCase()}`,42 },43 })44 }45 46 blocks.push(47 {48 type: "section",49 text: {50 type: "mrkdwn",51 text: `*Subtotal*\t${await this.getDisplayAmount(order.subtotal as number, order.currency_code)}\n*Shipping*\t${52 await this.getDisplayAmount(order.shipping_total as number, order.currency_code)53 }\n*Discount Total*\t${await this.getDisplayAmount(54 order.discount_total as number,55 order.currency_code56 )}\n*Tax*\t${await this.getDisplayAmount(order.tax_total as number, order.currency_code)}\n*Total*\t${57 await this.getDisplayAmount(order.total as number, order.currency_code)58 }`,59 },60 },61 {62 type: "divider",63 }64 )65 66 await Promise.all(67 order.items?.map(async (item) => {68 const line: Record<string, unknown> = {69 type: "section",70 text: {71 type: "mrkdwn",72 text: `*${item.title}*\n${item.quantity} x ${await this.getDisplayAmount(73 item.unit_price as number,74 order.currency_code75 )}`,76 },77 }78 79 if (item.thumbnail) {80 const url = item.thumbnail81 82 line.accessory = {83 type: "image",84 alt_text: "Item",85 image_url: url,86 }87 }88 89 blocks.push(line)90 blocks.push({91 type: "divider",92 })93 }) || []94 )95 96 await axios.post(this.options.webhook_url, {97 text: `Order ${order.display_id} was created`,98 blocks,99 })100 101 return {102 id: order.id,103 }104 }105}
The sendOrderNotification
method receives a ProviderSendNotificationDTO
object, which is the object passed to the send
method that you'll implement next. The object has a data
property that contains the order data.
In the method, you format the Slack message to show the order's ID, customer information, shipping address, order items, and total amounts. You also add a link to the order's details page in the Medusa Admin dashboard.
Finally, you send the formatted message to Slack using the axios.post
method with the configured webhook URL.
send Method
You can now implement the send
method, which is the method that Medusa calls to send notifications using the provider. Add the send
method to the SlackNotificationProviderService
class:
1class SlackNotificationProviderService extends AbstractNotificationProviderService {2 // ...3 async send(4 notification: ProviderSendNotificationDTO5 ): Promise<ProviderSendNotificationResultsDTO> {6 const { template } = notification7 8 switch (template) {9 case "order-created":10 return this.sendOrderNotification(notification)11 default:12 throw new MedusaError(13 MedusaError.Types.NOT_FOUND,14 `Template ${template} not supported`15 )16 }17 }18}
The send
method receives a ProviderSendNotificationDTO
object, which contains the notification data and the template to use for sending the notification.
In the method, you check the template
property of the notification object. If it's order-created
, you call the sendOrderNotification
method to send the notification. Otherwise, you throw an error indicating that the template is not supported.
f. Export Module Definition#
You've now finished implementing the necessary methods for the Slack Notification Module Provider.
The final piece to a module is its definition, which you export in an index.ts
file at the module's root directory. This definition tells Medusa the module's details, including its service.
To create the module's definition, create the file src/modules/slack/index.ts
with the following content:
You use ModuleProvider
from the Modules SDK to create the module provider's definition. It accepts two parameters:
- The name of the module that this provider belongs to, which is
Modules.NOTIFICATION
in this case. - An object with a required property
services
indicating the Module Provider's services.
g. Add Module Provider to Medusa's Configurations#
Once you finish building the module, add it to Medusa's configurations to start using it.
In medusa-config.ts
, add a modules
property:
1module.exports = defineConfig({2 // ...3 modules: [4 {5 resolve: "@medusajs/medusa/notification",6 options: {7 providers: [8 {9 resolve: "./src/modules/slack",10 id: "slack",11 options: {12 channels: ["slack"],13 webhook_url: process.env.SLACK_WEBHOOK_URL,14 admin_url: process.env.SLACK_ADMIN_URL,15 },16 },17 ],18 },19 },20 ],21})
To pass a Notification Module Provider to the Notification Module, you add the modules
property to the Medusa configuration and pass the Notification Module in its value.
The Notification Module accepts a providers
option, which is an array of Notification Module Providers to register.
To register the Slack Notification Module Provider, you add an object to the providers
array with the following properties:
resolve
: The NPM package or path to the module provider. In this case, it's the path to thesrc/modules/slack
directory.id
: The ID of the module provider. The Notification Module Provider is then registered with the IDnp_{identifier}_{id}
, where:{identifier}
: The identifier static property defined in the Module Provider's service, which isslack
in this case.{id}
: The ID set in this configuration, which is alsoslack
in this case.
options
: The options to pass to the module provider. These are the options you defined in theOptions
interface of the module provider's service.- You must also set a
channel
option that indicates the channel this provider is used to send notifications.
- You must also set a
h. Set Options as Environment Variables#
Next, you'll set the options you passed to the Slack Notification Module Provider as environment variables.
To set the webhook URL, you need to create a Slack application and configure a webhook URL. To do that:
- Go to your Slack Apps page.
- Click the "Create New App" button.
- In the pop-up, choose "From scratch".
- Enter the app's name and select the workspace to install it in.
- Once you're done, click the "Create App" button.
- In the app's settings, go to the "Incoming Webhooks" section.
- Enable the "Activate Incoming Webhooks" toggle.
- In the "Webhook URLs for Your Workspace" section, click the "Add New Webhook" button.
- Select the channel or conversation to send notifications to and click the "Allow" button.
- Copy the generated webhook URL.
Then, in the .env
file of your Medusa application, set the SLACK_WEBHOOK_URL
and SLACK_ADMIN_URL
environment variables:
In development, the Medusa Admin dashboard is running on http://localhost:9000/app
, so you can use that URL for the SLACK_ADMIN_URL
environment variable.
You'll test out the integration when you handle the order.placed
event in the next step.
Step 3: Handle the order.placed
Event#
Now that you've integrated Slack with Medusa, you can send notifications to Slack at any point, including when an order is created.
To send a notification to Slack when an order is created, you will:
- Implement the order details retrieval and notification sending with Slack in a workflow. A workflow is a series of actions, called steps, that complete a task with rollback and retry mechanisms.
- Listen to the
order.placed
event in a subscriber. A subscriber is an asynchronous function that listens to events to perform actions, such as execute a workflow, when the event is emitted.
a. Create the Workflow#
The workflow to send notifications to Slack will have the following steps:
View step details
Medusa provides both steps in its @medusajs/medusa/core-flows
package.
So, to create the workflow, create the file src/workflows/order-placed-notification.ts
with the following content:
1import { 2 createWorkflow, 3} from "@medusajs/framework/workflows-sdk"4import { sendNotificationsStep, useQueryGraphStep } from "@medusajs/medusa/core-flows"5 6type WorkflowInput = {7 id: string8}9 10export const orderPlacedNotificationWorkflow = createWorkflow(11 "order-placed-notification",12 ({ id }: WorkflowInput) => {13 // @ts-ignore14 const { data: orders } = useQueryGraphStep({15 entity: "order",16 fields: [17 "id",18 "display_id", 19 "email",20 "shipping_address.*",21 "subtotal",22 "shipping_total",23 "currency_code", 24 "discount_total",25 "tax_total",26 "total",27 "items.*",28 "original_total",29 ],30 filters: {31 id,32 },33 })34 35 sendNotificationsStep([{36 to: "slack-channel", // This will be configured in the Slack app37 channel: "slack",38 template: "order-created",39 data: {40 order: orders[0],41 },42 }])43 }44)
You create a workflow using createWorkflow
from the Workflows SDK. It accepts the workflow's unique name as a first parameter.
It accepts as a second parameter a constructor function, which is the workflow's implementation. The function can accept input, which in this case is an object holding the ID of the order placed.
In the workflow's constructor function, you:
- Retrieve the order's details using the
useQueryGraphStep
. This step uses Medusa's Query tool to retrieve data across modules. You pass it the order ID to retrieve. - Send a notification using the
sendNotificationsStep
. You pass the step an array of notifications to send, and each notification is an object with the following properties:to
: The channel to send the notification to. Since you configure this in the Slack app, you just set it toslack-channel
.channel
: The channel to use for sending the notification. By setting it toslack
, the Notification Module will use the Slack Notification Module Provider to send the notification.template
: The template to use for sending the notification, which isorder-created
in this case.data
: The data payload to pass with the notification, which contains the order details.
You now have a workflow that retrieves the order details and sends a notification to Slack when an order is placed.
b. Create the Subscriber#
To execute the workflow when an order is placed, you need to create a subscriber that listens to the order.placed
event and executes the workflow.
To create the subscriber, create the file src/subscribers/order-placed.ts
with the following content:
1import { SubscriberArgs, SubscriberConfig } from "@medusajs/framework"2import { 3 orderPlacedNotificationWorkflow,4} from "../workflows/order-placed-notification"5 6export default async function orderPlacedHandler({7 event: { data },8 container,9}: SubscriberArgs<{ id: string }>) {10 await orderPlacedNotificationWorkflow(container)11 .run({12 input: data,13 })14}15 16export const config: SubscriberConfig = {17 event: "order.placed",18}
A subscriber file must export:
- An asynchronous function, which is the subscriber that is executed when the event is emitted.
- A configuration object that holds the name of the event that the subscriber listens to, which is
order.placed
in this case.
The subscriber function receives an object as a parameter that has the following properties:
event
: An object that holds the event's data payload. The payload of theorder.placed
event is the ID of the order placed.container
: The Medusa container to access the Framework and commerce tools.
In the subscriber function, you execute the orderPlacedNotificationWorkflow
by invoking it, passing the Medusa container as a parameter. Then, you chain a run
method, passing it the order ID from the event's data payload as input.
Test it Out#
You'll now test out the integration by placing an order using the Next.js Starter Storefront.
The Next.js Starter Storefront was installed in a separate directory from Medusa. The directory's name is {your-project}-storefront
.
So, if your Medusa application's directory is medusa-slack
, you can find the storefront by going back to the parent directory and changing to the medusa-slack-storefront
directory:
First, start the Medusa application by running the following command in the Medusa application's directory:
Then, start the Next.js Starter Storefront by running the following command in the storefront's directory:
Next, open the Next.js Starter Storefront in your browser at http://localhost:8000
. Add a product to the cart, proceed to checkout, and place an order.
Afterwards, you'll receive a notification in the Slack channel you configured in the Slack app. The notification will contain the order details.
You can also click on the order's ID in the notification to open the order's details page in the Medusa Admin dashboard.
Next Steps#
You've now integrated Slack with Medusa to receive notifications about created orders. You can expand on this integration to send other types of notifications, such as order updates. Refer to the Events Reference for a list of events you can listen to in your subscribers.
You can also customize the notifications to include more information or change the format of the messages. Refer to Slack's documentation for more information on how to format messages in Slack.
If you're new to Medusa, check out the main documentation, where you'll get a more in-depth understanding of all the concepts you've used in this guide and more.
To learn more about the commerce features that Medusa provides, check out Medusa's Commerce Modules.
Troubleshooting#
If you encounter issues during your development, check out the troubleshooting guides.
Getting Help#
If you encounter issues not covered in the troubleshooting guides:
- Visit the Medusa GitHub repository to report issues or ask questions.
- Join the Medusa Discord community for real-time support from community members.
- Contact the sales team to get help from the Medusa team.