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.

Diagram showcasing the flow of the integration between Medusa and Slack

Example Repository
Find the full code of the guide in this repository.

Step 1: Install a Medusa Application#

Start by installing the Medusa application on your machine with the following command:

Terminal
npx create-medusa-app@latest

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.

Why is the storefront installed separately? The Medusa application is composed of a headless Node.js server and an admin dashboard. The storefront is installed or custom-built separately and connects to the Medusa application through its REST endpoints, called API routes. Learn more in Medusa's Architecture documentation.

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.

Ran into Errors? Check out the troubleshooting guides for help.

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.

Note: Refer to the Modules documentation to learn more about modules in Medusa.

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:

src/modules/slack/service.ts
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.
Note: You'll learn how to set these options when you add the module provider to Medusa's configurations.

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.

Note: Refer to the Create Notification Module Provider guide for detailed information about the methods.

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:

src/modules/slack/service.ts
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:

src/modules/slack/service.ts
1class SlackNotificationProviderService extends AbstractNotificationProviderService {2  // ...3  private async getDisplayAmount(amount: number, currencyCode: string) {4    return Intl.NumberFormat("en-US", {5      style: "currency",6      currency: currencyCode,7    }).format(amount)8  }9}

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:

src/modules/slack/service.ts
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:

src/modules/slack/service.ts
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.

Note: The method receives other parameters, which you can find in the Create Notification Module Provider guide.

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:

src/modules/slack/index.ts
1import SlackNotificationProvider from "./service"2import { 3  ModuleProvider, 4  Modules,5} from "@medusajs/framework/utils"6
7export default ModuleProvider(Modules.NOTIFICATION, {8  services: [SlackNotificationProvider],9})

You use ModuleProvider from the Modules SDK to create the module provider's definition. It accepts two parameters:

  1. The name of the module that this provider belongs to, which is Modules.NOTIFICATION in this case.
  2. 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:

medusa-config.ts
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 the src/modules/slack directory.
  • id: The ID of the module provider. The Notification Module Provider is then registered with the ID np_{identifier}_{id}, where:
    • {identifier}: The identifier static property defined in the Module Provider's service, which is slack in this case.
    • {id}: The ID set in this configuration, which is also slack in this case.
  • options: The options to pass to the module provider. These are the options you defined in the Options 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.

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:

  1. Go to your Slack Apps page.
  2. Click the "Create New App" button.

Create New App button on Slack Apps page

  1. In the pop-up, choose "From scratch".
  2. Enter the app's name and select the workspace to install it in.
  3. Once you're done, click the "Create App" button.

Create App pop-up

  1. In the app's settings, go to the "Incoming Webhooks" section.
  2. Enable the "Activate Incoming Webhooks" toggle.

Enable Incoming Webhooks toggle

  1. In the "Webhook URLs for Your Workspace" section, click the "Add New Webhook" button.

Add New Webhook button

  1. Select the channel or conversation to send notifications to and click the "Allow" button.
  2. Copy the generated webhook URL.

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:

.env
1SLACK_WEBHOOK_URL=https://hooks.slack.com/...2SLACK_ADMIN_URL=http://localhost:9000/app

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:

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:

src/workflows/order-placed-notification.ts
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 to slack-channel.
    • channel: The channel to use for sending the notification. By setting it to slack, the Notification Module will use the Slack Notification Module Provider to send the notification.
    • template: The template to use for sending the notification, which is order-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.

Note: Refer to the Workflows documentation to learn more about workflows and steps.

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:

src/subscribers/order-placed.ts
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:

  1. An asynchronous function, which is the subscriber that is executed when the event is emitted.
  2. 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 the order.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.

Note: Refer to the Events and Subscribers documentation to learn more about creating subscribers.

Test it Out#

You'll now test out the integration by placing an order using the Next.js Starter Storefront.

Reminder: 

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:

Terminal
cd ../medusa-slack-storefront # change based on your project name

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.

Slack Notification Example


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:

  1. Visit the Medusa GitHub repository to report issues or ask questions.
  2. Join the Medusa Discord community for real-time support from community members.
  3. Contact the sales team to get help from the Medusa team.
Was this page helpful?
Ask Anything
FAQ
What is Medusa?
How can I create a module?
How can I create a data model?
How do I create a workflow?
How can I extend a data model in the Product Module?
Recipes
How do I build a marketplace with Medusa?
How do I build digital products with Medusa?
How do I build subscription-based purchases with Medusa?
What other recipes are available in the Medusa documentation?
Chat is cleared on refresh
Line break