docs: added slack integration guide (#12696)
* docs: added slack integration guide * generate
This commit is contained in:
@@ -31119,7 +31119,7 @@ module.exports = defineConfig({
|
||||
{
|
||||
resolve: "@medusajs/analytics-local",
|
||||
id: "local",
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -31431,7 +31431,7 @@ module.exports = defineConfig({
|
||||
posthogEventsKey: process.env.POSTHOG_EVENTS_API_KEY,
|
||||
posthogHost: process.env.POSTHOG_HOST,
|
||||
},
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -61840,14 +61840,14 @@ class SegmentAnalyticsProviderService extends AbstractAnalyticsProviderService {
|
||||
userId: data.actor_id,
|
||||
anonymousId,
|
||||
traits,
|
||||
context: data.properties
|
||||
context: data.properties,
|
||||
})
|
||||
} else {
|
||||
this.client.identify({
|
||||
userId: data.actor_id,
|
||||
anonymousId,
|
||||
traits,
|
||||
context: data.properties
|
||||
context: data.properties,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -63601,6 +63601,632 @@ If you're new to Medusa, check out the [main documentation](https://docs.medusaj
|
||||
To learn more about the commerce features that Medusa provides, check out Medusa's [Commerce Modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/index.html.md).
|
||||
|
||||
|
||||
# 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](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/notification/index.html.md) allows you to customize Medusa's infrastructure to send notifications using the third-party provider that fits your business needs, such as [Slack](https://slack.com/).
|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
[Example Repository](https://github.com/medusajs/examples/tree/main/slack-integration): Find the full code of the guide in this repository.
|
||||
|
||||
***
|
||||
|
||||
## Step 1: Install a Medusa Application
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- [Node.js v20+](https://nodejs.org/en/download)
|
||||
- [Git CLI tool](https://git-scm.com/downloads)
|
||||
- [PostgreSQL](https://www.postgresql.org/download/)
|
||||
|
||||
Start by installing the Medusa application on your machine with the following command:
|
||||
|
||||
```bash
|
||||
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](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/nextjs-starter/index.html.md), 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`.
|
||||
|
||||
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](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/index.html.md). Learn more in [Medusa's Architecture documentation](https://docs.medusajs.com/docs/learn/introduction/architecture/index.html.md).
|
||||
|
||||
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.
|
||||
|
||||
Check out the [troubleshooting guides](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/troubleshooting/create-medusa-app-errors/index.html.md) for help.
|
||||
|
||||
***
|
||||
|
||||
## Step 2: Create Slack Module Provider
|
||||
|
||||
To integrate third-party services into Medusa, you create a custom [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md). A module is a reusable package with functionalities related to a single feature or domain.
|
||||
|
||||
Medusa's [Notification Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/notification/index.html.md) 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.
|
||||
|
||||
Refer to the [Modules](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) documentation to learn more about modules in Medusa.
|
||||
|
||||
### a. Install Axios
|
||||
|
||||
To send requests to Slack, you'll use the [Axios](https://axios-http.com/) library. So, run the following command to install it in your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install axios
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```ts title="src/modules/slack/service.ts" highlights={serviceHighlights}
|
||||
import {
|
||||
AbstractNotificationProviderService,
|
||||
} from "@medusajs/framework/utils"
|
||||
|
||||
type Options = {
|
||||
webhook_url: string
|
||||
admin_url: string
|
||||
}
|
||||
|
||||
type InjectedDependencies = {}
|
||||
|
||||
class SlackNotificationProviderService extends AbstractNotificationProviderService {
|
||||
static identifier = "slack"
|
||||
protected options: Options
|
||||
|
||||
constructor(container: InjectedDependencies, options: Options) {
|
||||
super()
|
||||
this.options = options
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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](https://docs.medusajs.com/docs/learn/fundamentals/medusa-container/index.html.md).
|
||||
|
||||
The service's constructor receives two parameters:
|
||||
|
||||
- `container`: The [module's container](https://docs.medusajs.com/docs/learn/fundamentals/modules/container/index.html.md) 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.
|
||||
|
||||
You'll learn how to set these options when you [add the module provider to Medusa's configurations](#g-add-module-provider-to-medusas-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.
|
||||
|
||||
Refer to the [Create Notification Module Provider](https://docs.medusajs.com/references/notification-provider-module/index.html.md) 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:
|
||||
|
||||
```ts title="src/modules/slack/service.ts"
|
||||
// other imports...
|
||||
import {
|
||||
MedusaError,
|
||||
} from "@medusajs/framework/utils"
|
||||
|
||||
class SlackNotificationProviderService extends AbstractNotificationProviderService {
|
||||
// ...
|
||||
static validateOptions(options: Record<any, any>): void | never {
|
||||
if (!options.webhook_url) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
"Webhook URL is required"
|
||||
)
|
||||
}
|
||||
if (!options.admin_url) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
"Admin URL is required"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```ts title="src/modules/slack/service.ts"
|
||||
class SlackNotificationProviderService extends AbstractNotificationProviderService {
|
||||
// ...
|
||||
private async getDisplayAmount(amount: number, currencyCode: string) {
|
||||
return Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: currencyCode,
|
||||
}).format(amount)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```ts title="src/modules/slack/service.ts" highlights={sendOrderNotificationHighlights}
|
||||
// other imports...
|
||||
import {
|
||||
OrderDTO,
|
||||
ProviderSendNotificationDTO,
|
||||
ProviderSendNotificationResultsDTO,
|
||||
} from "@medusajs/framework/types"
|
||||
import axios from "axios"
|
||||
|
||||
class SlackNotificationProviderService extends AbstractNotificationProviderService {
|
||||
// ...
|
||||
async sendOrderNotification(notification: ProviderSendNotificationDTO) {
|
||||
const order = notification.data?.order as OrderDTO
|
||||
if (!order) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
"Order not found"
|
||||
)
|
||||
}
|
||||
const shippingAddress = order.shipping_address
|
||||
const blocks: Record<string, unknown>[] = [
|
||||
{
|
||||
type: "section",
|
||||
text: {
|
||||
type: "mrkdwn",
|
||||
text: `Order *<${this.options.admin_url}/orders/${order.id}|#${order.display_id}>* has been created.`,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
if (shippingAddress) {
|
||||
blocks.push({
|
||||
type: "section",
|
||||
text: {
|
||||
type: "mrkdwn",
|
||||
text: `*Customer*\n${shippingAddress.first_name} ${
|
||||
shippingAddress.last_name
|
||||
}\n${order.email}\n*Destination*\n${
|
||||
shippingAddress.address_1
|
||||
}\n${
|
||||
shippingAddress.city
|
||||
}, ${(shippingAddress.country_code as string).toUpperCase()}`,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
blocks.push(
|
||||
{
|
||||
type: "section",
|
||||
text: {
|
||||
type: "mrkdwn",
|
||||
text: `*Subtotal*\t${await this.getDisplayAmount(order.subtotal as number, order.currency_code)}\n*Shipping*\t${
|
||||
await this.getDisplayAmount(order.shipping_total as number, order.currency_code)
|
||||
}\n*Discount Total*\t${await this.getDisplayAmount(
|
||||
order.discount_total as number,
|
||||
order.currency_code
|
||||
)}\n*Tax*\t${await this.getDisplayAmount(order.tax_total as number, order.currency_code)}\n*Total*\t${
|
||||
await this.getDisplayAmount(order.total as number, order.currency_code)
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "divider",
|
||||
}
|
||||
)
|
||||
|
||||
await Promise.all(
|
||||
order.items?.map(async (item) => {
|
||||
const line: Record<string, unknown> = {
|
||||
type: "section",
|
||||
text: {
|
||||
type: "mrkdwn",
|
||||
text: `*${item.title}*\n${item.quantity} x ${await this.getDisplayAmount(
|
||||
item.unit_price as number,
|
||||
order.currency_code
|
||||
)}`,
|
||||
},
|
||||
}
|
||||
|
||||
if (item.thumbnail) {
|
||||
const url = item.thumbnail
|
||||
|
||||
line.accessory = {
|
||||
type: "image",
|
||||
alt_text: "Item",
|
||||
image_url: url,
|
||||
}
|
||||
}
|
||||
|
||||
blocks.push(line)
|
||||
blocks.push({
|
||||
type: "divider",
|
||||
})
|
||||
}) || []
|
||||
)
|
||||
|
||||
await axios.post(this.options.webhook_url, {
|
||||
text: `Order ${order.display_id} was created`,
|
||||
blocks,
|
||||
})
|
||||
|
||||
return {
|
||||
id: order.id,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```ts title="src/modules/slack/service.ts"
|
||||
class SlackNotificationProviderService extends AbstractNotificationProviderService {
|
||||
// ...
|
||||
async send(
|
||||
notification: ProviderSendNotificationDTO
|
||||
): Promise<ProviderSendNotificationResultsDTO> {
|
||||
const { template } = notification
|
||||
|
||||
switch (template) {
|
||||
case "order-created":
|
||||
return this.sendOrderNotification(notification)
|
||||
default:
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Template ${template} not supported`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `send` method receives a `ProviderSendNotificationDTO` object, which contains the notification data and the template to use for sending the notification.
|
||||
|
||||
The method receives other parameters, which you can find in the [Create Notification Module Provider](https://docs.medusajs.com/references/notification-provider-module#send/index.html.md) 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:
|
||||
|
||||
```ts title="src/modules/slack/index.ts"
|
||||
import SlackNotificationProvider from "./service"
|
||||
import {
|
||||
ModuleProvider,
|
||||
Modules,
|
||||
} from "@medusajs/framework/utils"
|
||||
|
||||
export default ModuleProvider(Modules.NOTIFICATION, {
|
||||
services: [SlackNotificationProvider],
|
||||
})
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```ts title="medusa-config.ts" highlights={configHighlights}
|
||||
module.exports = defineConfig({
|
||||
// ...
|
||||
modules: [
|
||||
{
|
||||
resolve: "@medusajs/medusa/notification",
|
||||
options: {
|
||||
providers: [
|
||||
{
|
||||
resolve: "./src/modules/slack",
|
||||
id: "slack",
|
||||
options: {
|
||||
channels: ["slack"],
|
||||
webhook_url: process.env.SLACK_WEBHOOK_URL,
|
||||
admin_url: process.env.SLACK_ADMIN_URL,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
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](https://api.slack.com/apps) page.
|
||||
2. Click the "Create New App" button.
|
||||
|
||||

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

|
||||
|
||||
6. In the app's settings, go to the "Incoming Webhooks" section.
|
||||
7. Enable the "Activate Incoming Webhooks" toggle.
|
||||
|
||||

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

|
||||
|
||||
9. Select the channel or conversation to send notifications to and click the "Allow" button.
|
||||
10. 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:
|
||||
|
||||
```shell title=".env"
|
||||
SLACK_WEBHOOK_URL=https://hooks.slack.com/...
|
||||
SLACK_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](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md). 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](https://docs.medusajs.com/docs/learn/fundamentals/events-and-subscribers/index.html.md). 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:
|
||||
|
||||
- [useQueryGraphStep](https://docs.medusajs.com/references/helper-steps/useQueryGraphStep/index.html.md): Retrieve the order details.
|
||||
- [sendNotificationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/sendNotificationsStep/index.html.md): Send a notification with a configured provider.
|
||||
|
||||
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:
|
||||
|
||||
```ts title="src/workflows/order-placed-notification.ts" highlights={orderPlacedNotificationWorkflowHighlights}
|
||||
import {
|
||||
createWorkflow,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { sendNotificationsStep, useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
|
||||
type WorkflowInput = {
|
||||
id: string
|
||||
}
|
||||
|
||||
export const orderPlacedNotificationWorkflow = createWorkflow(
|
||||
"order-placed-notification",
|
||||
({ id }: WorkflowInput) => {
|
||||
// @ts-ignore
|
||||
const { data: orders } = useQueryGraphStep({
|
||||
entity: "order",
|
||||
fields: [
|
||||
"id",
|
||||
"display_id",
|
||||
"email",
|
||||
"shipping_address.*",
|
||||
"subtotal",
|
||||
"shipping_total",
|
||||
"currency_code",
|
||||
"discount_total",
|
||||
"tax_total",
|
||||
"total",
|
||||
"items.*",
|
||||
"original_total",
|
||||
],
|
||||
filters: {
|
||||
id,
|
||||
},
|
||||
})
|
||||
|
||||
sendNotificationsStep([{
|
||||
to: "slack-channel", // This will be configured in the Slack app
|
||||
channel: "slack",
|
||||
template: "order-created",
|
||||
data: {
|
||||
order: orders[0],
|
||||
},
|
||||
}])
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
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](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md) 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.
|
||||
|
||||
Refer to the [Workflows](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md) 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:
|
||||
|
||||
```ts title="src/subscribers/order-placed.ts"
|
||||
import { SubscriberArgs, SubscriberConfig } from "@medusajs/framework"
|
||||
import {
|
||||
orderPlacedNotificationWorkflow,
|
||||
} from "../workflows/order-placed-notification"
|
||||
|
||||
export default async function orderPlacedHandler({
|
||||
event: { data },
|
||||
container,
|
||||
}: SubscriberArgs<{ id: string }>) {
|
||||
await orderPlacedNotificationWorkflow(container)
|
||||
.run({
|
||||
input: data,
|
||||
})
|
||||
}
|
||||
|
||||
export const config: SubscriberConfig = {
|
||||
event: "order.placed",
|
||||
}
|
||||
```
|
||||
|
||||
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](https://docs.medusajs.com/docs/learn/fundamentals/medusa-container/index.html.md) 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.
|
||||
|
||||
Refer to the [Events and Subscribers](https://docs.medusajs.com/docs/learn/fundamentals/events-and-subscribers/index.html.md) 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](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/nextjs-starter/index.html.md).
|
||||
|
||||
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:
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```bash npm2yarn badgeLabel="Medusa Application" badgeColor="green"
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then, start the Next.js Starter Storefront by running the following command in the storefront's directory:
|
||||
|
||||
```bash npm2yarn badgeLabel="Next.js Starter Storefront" badgeColor="blue"
|
||||
npm run dev
|
||||
```
|
||||
|
||||
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](https://docs.medusajs.com/references/events/index.html.md) 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](https://api.slack.com/reference/surfaces/formatting) for more information on how to format messages in Slack.
|
||||
|
||||
If you're new to Medusa, check out the [main documentation](https://docs.medusajs.com/docs/learn/index.html.md), 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](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/index.html.md).
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
If you encounter issues during your development, check out the [troubleshooting guides](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/troubleshooting/index.html.md).
|
||||
|
||||
### Getting Help
|
||||
|
||||
If you encounter issues not covered in the troubleshooting guides:
|
||||
|
||||
1. Visit the [Medusa GitHub repository](https://github.com/medusajs/medusa) to report issues or ask questions.
|
||||
2. Join the [Medusa Discord community](https://discord.gg/medusajs) for real-time support from community members.
|
||||
3. Contact the [sales team](https://medusajs.com/contact/) to get help from the Medusa team.
|
||||
|
||||
|
||||
# Integrations
|
||||
|
||||
You can integrate any third-party service to Medusa, including storage services, notification systems, Content-Management Systems (CMS), etc… By integrating third-party services, you build flows and synchronize data around these integrations, making Medusa not only your commerce application, but a middleware layer between your data sources and operations.
|
||||
|
||||
@@ -37,7 +37,7 @@ module.exports = defineConfig({
|
||||
{
|
||||
resolve: "@medusajs/analytics-local",
|
||||
id: "local",
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -58,7 +58,7 @@ module.exports = defineConfig({
|
||||
posthogEventsKey: process.env.POSTHOG_EVENTS_API_KEY,
|
||||
posthogHost: process.env.POSTHOG_HOST,
|
||||
},
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -205,14 +205,14 @@ class SegmentAnalyticsProviderService extends AbstractAnalyticsProviderService {
|
||||
userId: data.actor_id,
|
||||
anonymousId,
|
||||
traits,
|
||||
context: data.properties
|
||||
context: data.properties,
|
||||
})
|
||||
} else {
|
||||
this.client.identify({
|
||||
userId: data.actor_id,
|
||||
anonymousId,
|
||||
traits,
|
||||
context: data.properties
|
||||
context: data.properties,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,735 @@
|
||||
---
|
||||
sidebar_label: "Integrate Slack"
|
||||
tags:
|
||||
- server
|
||||
- tutorial
|
||||
- notification
|
||||
products:
|
||||
- order
|
||||
---
|
||||
|
||||
import { Card, Prerequisites, Details, WorkflowDiagram } from "docs-ui"
|
||||
import { Github, PlaySolid } from "@medusajs/icons"
|
||||
|
||||
export const metadata = {
|
||||
title: `Integrate Slack (Notification) with Medusa`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
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](../../../infrastructure-modules/notification/page.mdx) allows you to customize Medusa's infrastructure to send notifications using the third-party provider that fits your business needs, such as [Slack](https://slack.com/).
|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
<Card
|
||||
title="Example Repository"
|
||||
text="Find the full code of the guide in this repository."
|
||||
href="https://github.com/medusajs/examples/tree/main/slack-integration"
|
||||
icon={Github}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Install a Medusa Application
|
||||
|
||||
<Prerequisites items={[
|
||||
{
|
||||
text: "Node.js v20+",
|
||||
link: "https://nodejs.org/en/download"
|
||||
},
|
||||
{
|
||||
text: "Git CLI tool",
|
||||
link: "https://git-scm.com/downloads"
|
||||
},
|
||||
{
|
||||
text: "PostgreSQL",
|
||||
link: "https://www.postgresql.org/download/"
|
||||
}
|
||||
]} />
|
||||
|
||||
Start by installing the Medusa application on your machine with the following command:
|
||||
|
||||
```bash
|
||||
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](../../../nextjs-starter/page.mdx), 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`.
|
||||
|
||||
<Note title="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](!docs!/learn/fundamentals/api-routes). Learn more in [Medusa's Architecture documentation](!docs!/learn/introduction/architecture).
|
||||
|
||||
</Note>
|
||||
|
||||
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.
|
||||
|
||||
<Note title="Ran into Errors?">
|
||||
|
||||
Check out the [troubleshooting guides](../../../troubleshooting/create-medusa-app-errors/page.mdx) for help.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Create Slack Module Provider
|
||||
|
||||
To integrate third-party services into Medusa, you create a custom [module](!docs!/learn/fundamentals/modules). A module is a reusable package with functionalities related to a single feature or domain.
|
||||
|
||||
Medusa's [Notification Module](../../../infrastructure-modules/notification/page.mdx) 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](!docs!/learn/fundamentals/modules) documentation to learn more about modules in Medusa.
|
||||
|
||||
</Note>
|
||||
|
||||
### a. Install Axios
|
||||
|
||||
To send requests to Slack, you'll use the [Axios](https://axios-http.com/) library. So, run the following command to install it in your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install axios
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
export const serviceHighlights = [
|
||||
["6", "webhook_url", "The Slack webhook URL to send notifications to."],
|
||||
["7", "admin_url", "The URL of the Medusa Admin dashboard, used to add links in the notifications."],
|
||||
["13", "identifier", "The unique identifier for the module provider."],
|
||||
]
|
||||
|
||||
```ts title="src/modules/slack/service.ts" highlights={serviceHighlights}
|
||||
import {
|
||||
AbstractNotificationProviderService,
|
||||
} from "@medusajs/framework/utils"
|
||||
|
||||
type Options = {
|
||||
webhook_url: string
|
||||
admin_url: string
|
||||
}
|
||||
|
||||
type InjectedDependencies = {}
|
||||
|
||||
class SlackNotificationProviderService extends AbstractNotificationProviderService {
|
||||
static identifier = "slack"
|
||||
protected options: Options
|
||||
|
||||
constructor(container: InjectedDependencies, options: Options) {
|
||||
super()
|
||||
this.options = options
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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](!docs!/learn/fundamentals/medusa-container).
|
||||
|
||||
The service's constructor receives two parameters:
|
||||
|
||||
- `container`: The [module's container](!docs!/learn/fundamentals/modules/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](#g-add-module-provider-to-medusas-configurations).
|
||||
|
||||
</Note>
|
||||
|
||||
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](/references/notification-provider-module) guide for detailed information about the methods.
|
||||
|
||||
</Note>
|
||||
|
||||
### 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:
|
||||
|
||||
```ts title="src/modules/slack/service.ts"
|
||||
// other imports...
|
||||
import {
|
||||
MedusaError,
|
||||
} from "@medusajs/framework/utils"
|
||||
|
||||
class SlackNotificationProviderService extends AbstractNotificationProviderService {
|
||||
// ...
|
||||
static validateOptions(options: Record<any, any>): void | never {
|
||||
if (!options.webhook_url) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
"Webhook URL is required"
|
||||
)
|
||||
}
|
||||
if (!options.admin_url) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
"Admin URL is required"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```ts title="src/modules/slack/service.ts"
|
||||
class SlackNotificationProviderService extends AbstractNotificationProviderService {
|
||||
// ...
|
||||
private async getDisplayAmount(amount: number, currencyCode: string) {
|
||||
return Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: currencyCode,
|
||||
}).format(amount)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
export const sendOrderNotificationHighlights = [
|
||||
["12", "order", "The order to send a notification for."],
|
||||
["20", "blocks", "Format the slack message as blocks."],
|
||||
["96", "axios", "Send the formatted message to Slack using Axios."],
|
||||
]
|
||||
|
||||
```ts title="src/modules/slack/service.ts" highlights={sendOrderNotificationHighlights}
|
||||
// other imports...
|
||||
import {
|
||||
OrderDTO,
|
||||
ProviderSendNotificationDTO,
|
||||
ProviderSendNotificationResultsDTO,
|
||||
} from "@medusajs/framework/types"
|
||||
import axios from "axios"
|
||||
|
||||
class SlackNotificationProviderService extends AbstractNotificationProviderService {
|
||||
// ...
|
||||
async sendOrderNotification(notification: ProviderSendNotificationDTO) {
|
||||
const order = notification.data?.order as OrderDTO
|
||||
if (!order) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
"Order not found"
|
||||
)
|
||||
}
|
||||
const shippingAddress = order.shipping_address
|
||||
const blocks: Record<string, unknown>[] = [
|
||||
{
|
||||
type: "section",
|
||||
text: {
|
||||
type: "mrkdwn",
|
||||
text: `Order *<${this.options.admin_url}/orders/${order.id}|#${order.display_id}>* has been created.`,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
if (shippingAddress) {
|
||||
blocks.push({
|
||||
type: "section",
|
||||
text: {
|
||||
type: "mrkdwn",
|
||||
text: `*Customer*\n${shippingAddress.first_name} ${
|
||||
shippingAddress.last_name
|
||||
}\n${order.email}\n*Destination*\n${
|
||||
shippingAddress.address_1
|
||||
}\n${
|
||||
shippingAddress.city
|
||||
}, ${(shippingAddress.country_code as string).toUpperCase()}`,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
blocks.push(
|
||||
{
|
||||
type: "section",
|
||||
text: {
|
||||
type: "mrkdwn",
|
||||
text: `*Subtotal*\t${await this.getDisplayAmount(order.subtotal as number, order.currency_code)}\n*Shipping*\t${
|
||||
await this.getDisplayAmount(order.shipping_total as number, order.currency_code)
|
||||
}\n*Discount Total*\t${await this.getDisplayAmount(
|
||||
order.discount_total as number,
|
||||
order.currency_code
|
||||
)}\n*Tax*\t${await this.getDisplayAmount(order.tax_total as number, order.currency_code)}\n*Total*\t${
|
||||
await this.getDisplayAmount(order.total as number, order.currency_code)
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "divider",
|
||||
}
|
||||
)
|
||||
|
||||
await Promise.all(
|
||||
order.items?.map(async (item) => {
|
||||
const line: Record<string, unknown> = {
|
||||
type: "section",
|
||||
text: {
|
||||
type: "mrkdwn",
|
||||
text: `*${item.title}*\n${item.quantity} x ${await this.getDisplayAmount(
|
||||
item.unit_price as number,
|
||||
order.currency_code
|
||||
)}`,
|
||||
},
|
||||
}
|
||||
|
||||
if (item.thumbnail) {
|
||||
const url = item.thumbnail
|
||||
|
||||
line.accessory = {
|
||||
type: "image",
|
||||
alt_text: "Item",
|
||||
image_url: url,
|
||||
}
|
||||
}
|
||||
|
||||
blocks.push(line)
|
||||
blocks.push({
|
||||
type: "divider",
|
||||
})
|
||||
}) || []
|
||||
)
|
||||
|
||||
await axios.post(this.options.webhook_url, {
|
||||
text: `Order ${order.display_id} was created`,
|
||||
blocks,
|
||||
})
|
||||
|
||||
return {
|
||||
id: order.id,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```ts title="src/modules/slack/service.ts"
|
||||
class SlackNotificationProviderService extends AbstractNotificationProviderService {
|
||||
// ...
|
||||
async send(
|
||||
notification: ProviderSendNotificationDTO
|
||||
): Promise<ProviderSendNotificationResultsDTO> {
|
||||
const { template } = notification
|
||||
|
||||
switch (template) {
|
||||
case "order-created":
|
||||
return this.sendOrderNotification(notification)
|
||||
default:
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Template ${template} not supported`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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](/references/notification-provider-module#send) guide.
|
||||
|
||||
</Note>
|
||||
|
||||
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:
|
||||
|
||||
```ts title="src/modules/slack/index.ts"
|
||||
import SlackNotificationProvider from "./service"
|
||||
import {
|
||||
ModuleProvider,
|
||||
Modules,
|
||||
} from "@medusajs/framework/utils"
|
||||
|
||||
export default ModuleProvider(Modules.NOTIFICATION, {
|
||||
services: [SlackNotificationProvider],
|
||||
})
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
export const configHighlights = [
|
||||
["10", "id", "The ID of the module provider, used to register it."],
|
||||
["12", "channels", "The channels that this provider is used to send notifications."],
|
||||
["13", "webhook_url", "The Slack webhook URL to send notifications to."],
|
||||
["14", "admin_url", "The URL of the Medusa Admin dashboard, used to add links in the notifications."]
|
||||
]
|
||||
|
||||
```ts title="medusa-config.ts" highlights={configHighlights}
|
||||
module.exports = defineConfig({
|
||||
// ...
|
||||
modules: [
|
||||
{
|
||||
resolve: "@medusajs/medusa/notification",
|
||||
options: {
|
||||
providers: [
|
||||
{
|
||||
resolve: "./src/modules/slack",
|
||||
id: "slack",
|
||||
options: {
|
||||
channels: ["slack"],
|
||||
webhook_url: process.env.SLACK_WEBHOOK_URL,
|
||||
admin_url: process.env.SLACK_ADMIN_URL,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
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](https://api.slack.com/apps) page.
|
||||
2. Click the "Create New App" button.
|
||||
|
||||

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

|
||||
|
||||
6. In the app's settings, go to the "Incoming Webhooks" section.
|
||||
7. Enable the "Activate Incoming Webhooks" toggle.
|
||||
|
||||

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

|
||||
|
||||
9. Select the channel or conversation to send notifications to and click the "Allow" button.
|
||||
10. 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:
|
||||
|
||||
```shell title=".env"
|
||||
SLACK_WEBHOOK_URL=https://hooks.slack.com/...
|
||||
SLACK_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](!docs!/learn/fundamentals/workflows). 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](!docs!/learn/fundamentals/events-and-subscribers). 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:
|
||||
|
||||
<WorkflowDiagram
|
||||
workflow={{
|
||||
name: "orderPlacedNotificationWorkflow",
|
||||
steps: [
|
||||
{
|
||||
type: "step",
|
||||
name: "useQueryGraphStep",
|
||||
description: "Retrieve the order details.",
|
||||
link: "/references/helper-steps/useQueryGraphStep",
|
||||
depth: 1
|
||||
},
|
||||
{
|
||||
type: "step",
|
||||
name: "sendNotificationsStep",
|
||||
description: "Send a notification with a configured provider.",
|
||||
link: "/references/medusa-workflows/steps/sendNotificationsStep",
|
||||
depth: 1
|
||||
}
|
||||
]
|
||||
}}
|
||||
hideLegend
|
||||
/>
|
||||
|
||||
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:
|
||||
|
||||
export const orderPlacedNotificationWorkflowHighlights = [
|
||||
["14", "useQueryGraphStep", "Retrieve the order details using the Query Graph."],
|
||||
["35", "sendNotificationsStep", "Send a notification to Slack with the order details."]
|
||||
]
|
||||
|
||||
```ts title="src/workflows/order-placed-notification.ts" highlights={orderPlacedNotificationWorkflowHighlights}
|
||||
import {
|
||||
createWorkflow,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { sendNotificationsStep, useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
|
||||
type WorkflowInput = {
|
||||
id: string
|
||||
}
|
||||
|
||||
export const orderPlacedNotificationWorkflow = createWorkflow(
|
||||
"order-placed-notification",
|
||||
({ id }: WorkflowInput) => {
|
||||
// @ts-ignore
|
||||
const { data: orders } = useQueryGraphStep({
|
||||
entity: "order",
|
||||
fields: [
|
||||
"id",
|
||||
"display_id",
|
||||
"email",
|
||||
"shipping_address.*",
|
||||
"subtotal",
|
||||
"shipping_total",
|
||||
"currency_code",
|
||||
"discount_total",
|
||||
"tax_total",
|
||||
"total",
|
||||
"items.*",
|
||||
"original_total",
|
||||
],
|
||||
filters: {
|
||||
id,
|
||||
},
|
||||
})
|
||||
|
||||
sendNotificationsStep([{
|
||||
to: "slack-channel", // This will be configured in the Slack app
|
||||
channel: "slack",
|
||||
template: "order-created",
|
||||
data: {
|
||||
order: orders[0],
|
||||
},
|
||||
}])
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
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](!docs!/learn/fundamentals/module-links/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](!docs!/learn/fundamentals/workflows) documentation to learn more about workflows and steps.
|
||||
|
||||
</Note>
|
||||
|
||||
### 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:
|
||||
|
||||
```ts title="src/subscribers/order-placed.ts"
|
||||
import { SubscriberArgs, SubscriberConfig } from "@medusajs/framework"
|
||||
import {
|
||||
orderPlacedNotificationWorkflow,
|
||||
} from "../workflows/order-placed-notification"
|
||||
|
||||
export default async function orderPlacedHandler({
|
||||
event: { data },
|
||||
container,
|
||||
}: SubscriberArgs<{ id: string }>) {
|
||||
await orderPlacedNotificationWorkflow(container)
|
||||
.run({
|
||||
input: data,
|
||||
})
|
||||
}
|
||||
|
||||
export const config: SubscriberConfig = {
|
||||
event: "order.placed",
|
||||
}
|
||||
```
|
||||
|
||||
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](!docs!/learn/fundamentals/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](!docs!/learn/fundamentals/events-and-subscribers) documentation to learn more about creating subscribers.
|
||||
|
||||
</Note>
|
||||
|
||||
### Test it Out
|
||||
|
||||
You'll now test out the integration by placing an order using the [Next.js Starter Storefront](../../../nextjs-starter/page.mdx).
|
||||
|
||||
<Note title="Reminder" forceMultiline>
|
||||
|
||||
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:
|
||||
|
||||
```bash
|
||||
cd ../medusa-slack-storefront # change based on your project name
|
||||
```
|
||||
|
||||
</Note>
|
||||
|
||||
First, start the Medusa application by running the following command in the Medusa application's directory:
|
||||
|
||||
```bash npm2yarn badgeLabel="Medusa Application" badgeColor="green"
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then, start the Next.js Starter Storefront by running the following command in the storefront's directory:
|
||||
|
||||
```bash npm2yarn badgeLabel="Next.js Starter Storefront" badgeColor="blue"
|
||||
npm run dev
|
||||
```
|
||||
|
||||
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](/references/events) 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](https://api.slack.com/reference/surfaces/formatting) for more information on how to format messages in Slack.
|
||||
|
||||
If you're new to Medusa, check out the [main documentation](!docs!/learn), 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](../../../commerce-modules/page.mdx).
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
If you encounter issues during your development, check out the [troubleshooting guides](../../../troubleshooting/page.mdx).
|
||||
|
||||
### Getting Help
|
||||
|
||||
If you encounter issues not covered in the troubleshooting guides:
|
||||
|
||||
1. Visit the [Medusa GitHub repository](https://github.com/medusajs/medusa) to report issues or ask questions.
|
||||
2. Join the [Medusa Discord community](https://discord.gg/medusajs) for real-time support from community members.
|
||||
3. Contact the [sales team](https://medusajs.com/contact/) to get help from the Medusa team.
|
||||
@@ -6544,5 +6544,6 @@ export const generatedEditDates = {
|
||||
"references/js_sdk/admin/DraftOrder/methods/js_sdk.admin.DraftOrder.delete/page.mdx": "2025-06-05T19:05:54.961Z",
|
||||
"references/utils/Payment/variables/utils.Payment.PaymentEvents/page.mdx": "2025-06-05T19:05:53.491Z",
|
||||
"references/utils/types/utils.NormalizedRow/page.mdx": "2025-06-05T19:05:53.365Z",
|
||||
"references/utils/utils.Payment/page.mdx": "2025-06-05T19:05:53.489Z"
|
||||
"references/utils/utils.Payment/page.mdx": "2025-06-05T19:05:53.489Z",
|
||||
"app/integrations/guides/slack/page.mdx": "2025-06-03T09:37:42.528Z"
|
||||
}
|
||||
@@ -891,6 +891,10 @@ export const filesMap = [
|
||||
"filePath": "/www/apps/resources/app/integrations/guides/shipstation/page.mdx",
|
||||
"pathname": "/integrations/guides/shipstation"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/integrations/guides/slack/page.mdx",
|
||||
"pathname": "/integrations/guides/slack"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/integrations/page.mdx",
|
||||
"pathname": "/integrations"
|
||||
|
||||
@@ -426,6 +426,14 @@ const generatedgeneratedInfrastructureModulesSidebarSidebar = {
|
||||
"title": "Integrate Resend",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
"type": "ref",
|
||||
"title": "Integrate Slack",
|
||||
"path": "https://docs.medusajs.com/resources/integrations/guides/slack",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
|
||||
@@ -179,6 +179,14 @@ const generatedgeneratedIntegrationsSidebarSidebar = {
|
||||
"title": "SendGrid",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
"type": "link",
|
||||
"path": "/integrations/guides/slack",
|
||||
"title": "Slack",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
|
||||
@@ -122,6 +122,11 @@ export const integrationsSidebar = [
|
||||
path: "/infrastructure-modules/notification/sendgrid",
|
||||
title: "SendGrid",
|
||||
},
|
||||
{
|
||||
type: "link",
|
||||
path: "/integrations/guides/slack",
|
||||
title: "Slack",
|
||||
},
|
||||
{
|
||||
type: "ref",
|
||||
path: "/how-to-tutorials/tutorials/phone-auth#step-3-integrate-twilio-sms",
|
||||
|
||||
@@ -11,6 +11,10 @@ export const notification = [
|
||||
"title": "Send Notification",
|
||||
"path": "https://docs.medusajs.com/resources/infrastructure-modules/notification/send-notification"
|
||||
},
|
||||
{
|
||||
"title": "Integrate Slack",
|
||||
"path": "https://docs.medusajs.com/resources/integrations/guides/slack"
|
||||
},
|
||||
{
|
||||
"title": "notifyOnFailureStep",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/notifyOnFailureStep"
|
||||
|
||||
@@ -71,6 +71,10 @@ export const product = [
|
||||
"title": "Get Variant Inventory",
|
||||
"path": "https://docs.medusajs.com/resources/commerce-modules/product/guides/variant-inventory"
|
||||
},
|
||||
{
|
||||
"title": "Implement Custom Line Item Pricing in Medusa",
|
||||
"path": "https://docs.medusajs.com/resources/examples/guides/custom-item-price"
|
||||
},
|
||||
{
|
||||
"title": "Implement Product Reviews",
|
||||
"path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/product-reviews"
|
||||
|
||||
@@ -91,6 +91,10 @@ export const server = [
|
||||
"title": "Integrate Segment",
|
||||
"path": "https://docs.medusajs.com/resources/integrations/guides/segment"
|
||||
},
|
||||
{
|
||||
"title": "Integrate Slack",
|
||||
"path": "https://docs.medusajs.com/resources/integrations/guides/slack"
|
||||
},
|
||||
{
|
||||
"title": "Build Wishlist Plugin",
|
||||
"path": "https://docs.medusajs.com/resources/plugins/guides/wishlist"
|
||||
|
||||
@@ -55,6 +55,10 @@ export const tutorial = [
|
||||
"title": "Integrate Segment",
|
||||
"path": "https://docs.medusajs.com/resources/integrations/guides/segment"
|
||||
},
|
||||
{
|
||||
"title": "Integrate Slack",
|
||||
"path": "https://docs.medusajs.com/resources/integrations/guides/slack"
|
||||
},
|
||||
{
|
||||
"title": "Build Wishlist Plugin",
|
||||
"path": "https://docs.medusajs.com/resources/plugins/guides/wishlist"
|
||||
|
||||
Reference in New Issue
Block a user