645 lines
20 KiB
Plaintext
645 lines
20 KiB
Plaintext
---
|
||
addHowToData: true
|
||
---
|
||
|
||
import DocCardList from '@theme/DocCardList';
|
||
import DocCard from '@theme/DocCard';
|
||
import Icons from '@theme/Icon';
|
||
|
||
# Commerce automation
|
||
|
||
This document guides you on how you can implement different forms of commerce automation within your Medusa project.
|
||
|
||
## Overview
|
||
|
||
Commerce automation is essential for businesses that want to save costs, provide a better user experience, and avoid manual, repetitive tasks that can lead to human errors. Businesses can utilize it in different domains, including marketing, customer support, order management, and more.
|
||
|
||
Medusa provides the necessary architecture and resources to implement commerce automation related to order management, customer service, and more. It mainly utilizes an event bus that allows you to listen to specific event triggers and perform asynchronous actions.
|
||
|
||
This document gives a high-level overview of how you can implement different types of commerce automation.
|
||
|
||
---
|
||
|
||
## Re-Stock Notifications
|
||
|
||
Customers may be interested in a product that is currently out of stock.
|
||
|
||
Medusa already provides a [Re-stock notifications plugin](../plugins/other/restock-notifications.md). You can also implement this by creating custom entities, triggering custom events, and handling these events with a subscriber.
|
||
|
||
<DocCardList colSize={6} items={[
|
||
{
|
||
type: 'link',
|
||
href: '/plugins/other/restock-notifications',
|
||
label: 'Restock Notifications Plugin',
|
||
customProps: {
|
||
icon: Icons['bolt-solid'],
|
||
description: 'Learn how to install the Restock Notification plugin.',
|
||
}
|
||
},
|
||
{
|
||
type: 'link',
|
||
href: '/development/entities/create',
|
||
label: 'Create an Entity',
|
||
customProps: {
|
||
icon: Icons['academic-cap-solid'],
|
||
description: 'Learn how to create a custom entity in the Medusa backend.',
|
||
}
|
||
},
|
||
{
|
||
type: 'link',
|
||
href: '/development/events/#emitting-events',
|
||
label: 'Emit Events',
|
||
customProps: {
|
||
icon: Icons['academic-cap-solid'],
|
||
description: 'Learn how to emit a custom event in the Medusa backend.',
|
||
}
|
||
},
|
||
{
|
||
type: 'link',
|
||
href: '/development/events/create-subscriber',
|
||
label: 'Create a Subscriber',
|
||
customProps: {
|
||
icon: Icons['academic-cap-solid'],
|
||
description: 'Learn how to create a subscriber that handles events.',
|
||
}
|
||
},
|
||
]} />
|
||
|
||
---
|
||
|
||
## Automated Customer Support
|
||
|
||
Customer support is essential to build a store's brand and customer loyalty. This can include integrating with third-party services or automating notifications sent to customers when changes happen related to their orders, returns, exchanges, and more.
|
||
|
||
You can use Medusa's Notification Service to handle notifications triggered by actions on customer orders or profiles.
|
||
|
||
For example, when an order's status is updated, the `order.updated` event is triggered. You can create a Notification Service that handles this event by emailing the customer about the status update.
|
||
|
||
The [Event reference](../development/events/events-list.md) includes an extensive list of events triggered by the Medusa core.
|
||
|
||
Medusa also provides official notification plugins that integrate with third-party services, such as [SendGrid](../plugins/notifications/sendgrid.mdx) or [TwilioSMS](../plugins/notifications/twilio-sms.md).
|
||
|
||
<DocCardList colSize={6} items={[
|
||
{
|
||
type: 'link',
|
||
href: '/plugins/notifications',
|
||
label: 'Notification Plugins',
|
||
customProps: {
|
||
icon: Icons['bolt-solid'],
|
||
description: 'Check out available notification plugins.',
|
||
}
|
||
},
|
||
{
|
||
type: 'link',
|
||
href: '/development/notification/create-notification-provider',
|
||
label: 'Create Notification Service',
|
||
customProps: {
|
||
icon: Icons['academic-cap-solid'],
|
||
description: 'Learn how to create a notification service.',
|
||
}
|
||
},
|
||
]} />
|
||
|
||
<Details>
|
||
<Summary>Example: Sending an email for new notes</Summary>
|
||
|
||
Here’s an example of a subscriber that uses the SendGrid plugin to send an email to the customer when the order has a new note:
|
||
|
||
```ts title="src/subscribers/new-note.ts"
|
||
import {
|
||
type SubscriberConfig,
|
||
type SubscriberArgs,
|
||
ProductVariantService,
|
||
NoteService,
|
||
OrderService,
|
||
} from "@medusajs/medusa"
|
||
|
||
export default async function handleNoteCreated({
|
||
data, eventName, container, pluginOptions,
|
||
}: SubscriberArgs<Record<string, string>>) {
|
||
const noteService: NoteService = container.resolve(
|
||
"noteService"
|
||
)
|
||
const orderService: OrderService = container.resolve(
|
||
"orderService"
|
||
)
|
||
const sendgridService = container.resolve("sendgridService")
|
||
|
||
// retrieve note by id
|
||
const note = await noteService.retrieve(data.id, {
|
||
relations: ["author"],
|
||
})
|
||
|
||
if (!note || note.resource_type !== "order") {
|
||
return
|
||
}
|
||
|
||
// retrieve note's order
|
||
const order = await orderService.retrieve(
|
||
note.resource_id
|
||
)
|
||
|
||
if (!order) {
|
||
return
|
||
}
|
||
|
||
sendGridService.sendEmail({
|
||
templateId: "order-update",
|
||
from: "hello@medusajs.com",
|
||
to: order.email,
|
||
dynamic_template_data: {
|
||
// any data necessary for your template...
|
||
note_text: note.value,
|
||
note_author: note.author.first_name,
|
||
note_date: note.created_at,
|
||
order_id: order.display_id,
|
||
},
|
||
})
|
||
}
|
||
|
||
export const config: SubscriberConfig = {
|
||
event: NoteService.Events.CREATED,
|
||
context: {
|
||
subscriberId: "note-created-handler",
|
||
},
|
||
}
|
||
```
|
||
|
||
</Details>
|
||
|
||
---
|
||
|
||
## Automatic Data Synchronization
|
||
|
||
As your commerce store grows, you'll likely need to synchronize data across different systems. For example, you can synchronize data with an ERP system or a data warehouse.
|
||
|
||
You can implement automatic synchronization in Medusa using scheduled jobs. A scheduled job runs at a specified date and time interval in the background of your Medusa backend. Within the scheduled job, you can synchronize your internal or external data.
|
||
|
||
<DocCard item={{
|
||
type: 'link',
|
||
href: '/development/scheduled-jobs/create',
|
||
label: 'Create a Scheduled Job',
|
||
customProps: {
|
||
icon: Icons['academic-cap-solid'],
|
||
description: 'Learn how to create a scheduled job in Medusa.',
|
||
}
|
||
}} />
|
||
|
||
<Details>
|
||
<Summary>Example: Synchronizing products with a third-party service</Summary>
|
||
|
||
Here’s an example of synchronizing products with a third party service using a [loader](../development/loaders/create.md):
|
||
|
||
```ts title="src/loaders/sync-products.ts"
|
||
import {
|
||
Logger,
|
||
ProductService,
|
||
StoreService,
|
||
MedusaContainer,
|
||
} from "@medusajs/medusa"
|
||
import {
|
||
ProductSelector,
|
||
} from "@medusajs/medusa/dist/types/product"
|
||
|
||
export default async (
|
||
container: MedusaContainer,
|
||
config: Record<string, unknown>
|
||
): Promise<void> => {
|
||
const logger = container.resolve<Logger>("logger")
|
||
logger.info("Synchronizing products...")
|
||
const productService = container.resolve<ProductService>(
|
||
"productService"
|
||
)
|
||
const storeService = container.resolve<StoreService>(
|
||
"storeService"
|
||
)
|
||
// retrieve store to get last sync date
|
||
const store = await storeService.retrieve()
|
||
|
||
const productFilters: ProductSelector = {}
|
||
|
||
if (store.metadata.last_sync_date) {
|
||
productFilters.updated_at = {
|
||
gt: new Date(
|
||
store.metadata.last_sync_date as string
|
||
),
|
||
}
|
||
}
|
||
|
||
const updatedProducts = await productService.list(
|
||
productFilters
|
||
)
|
||
|
||
updatedProducts.forEach((product) => {
|
||
// assuming client is an initialized connection
|
||
// with a third-party service
|
||
client.sync(product)
|
||
})
|
||
|
||
await storeService.update({
|
||
metadata: {
|
||
last_sync_date: new Date(),
|
||
},
|
||
})
|
||
|
||
logger.info("Finish synchronizing products")
|
||
}
|
||
```
|
||
|
||
Notice that here it’s assumed that:
|
||
|
||
1. The last update date is stored in the `Store`'s metadata object. You can instead use a custom entity to handle this.
|
||
2. The connection to the third-party service is assumed to be available and handled within the `client` variable.
|
||
|
||
</Details>
|
||
|
||
---
|
||
|
||
## Order Management Automation
|
||
|
||
Using Medusa's architecture and commerce features, you can automate a large amount of order management functionalities.
|
||
|
||
Using the event bus service, you can handle events within an order workflow to automate actions. For example, when an order is placed, you can listen to the `order.placed` event and automatically creates a fulfillment if predefined conditions are met.
|
||
|
||
Another example is automatically capturing payment when an order is fully shipped, and the `order.shipment_created` is triggered.
|
||
|
||
<DocCardList colSize={4} items={[
|
||
{
|
||
type: 'link',
|
||
href: '/development/events',
|
||
label: 'Event Bus',
|
||
customProps: {
|
||
icon: Icons['academic-cap-solid'],
|
||
description: 'Learn about the event architecture system in the Medusa backend.',
|
||
}
|
||
},
|
||
{
|
||
type: 'link',
|
||
href: '/development/events/create-subscriber',
|
||
label: 'Create a Subscriber',
|
||
customProps: {
|
||
icon: Icons['academic-cap-solid'],
|
||
description: 'Learn how to create a subscriber to listen to events.',
|
||
}
|
||
},
|
||
{
|
||
type: 'link',
|
||
href: '/development/events/events-list',
|
||
label: 'Events Reference',
|
||
customProps: {
|
||
icon: Icons['academic-cap-solid'],
|
||
description: 'Check out triggered events in Medusa and their expected payloads.',
|
||
}
|
||
},
|
||
]} />
|
||
|
||
---
|
||
|
||
## Automated RMA Flow
|
||
|
||
Businesses must optimize their Return Merchandise Authorization (RMA) flow to ensure a good customer experience and service. By automating the flow, customers can request to return their received items, and businesses can quickly support them.
|
||
|
||
Medusa's commerce features are geared towards automating RMA flows and ensuring a good customer experience.
|
||
|
||
For example, customers can create returns for their orders without direct involvement from the store operator. The store operator will then receive a notification regarding the return and handle it accordingly. The same applies to order exchanges.
|
||
|
||
Medusa also allows the store operator to edit orders, receive customer approval for the edits, and authorize additional payment if necessary, all within a seamless workflow.
|
||
|
||
Similar to the examples mentioned earlier, each action triggers events you can listen to and perform additional actions based on your use case.
|
||
|
||
<DocCardList colSize={4} items={[
|
||
{
|
||
type: 'link',
|
||
href: '/development/events',
|
||
label: 'Event Bus',
|
||
customProps: {
|
||
icon: Icons['academic-cap-solid'],
|
||
description: 'Learn about the event architecture system in the Medusa backend.',
|
||
}
|
||
},
|
||
{
|
||
type: 'link',
|
||
href: '/development/events/create-subscriber',
|
||
label: 'Create a Subscriber',
|
||
customProps: {
|
||
icon: Icons['academic-cap-solid'],
|
||
description: 'Learn how to create a subscriber to listen to events.',
|
||
}
|
||
},
|
||
{
|
||
type: 'link',
|
||
href: '/development/events/events-list',
|
||
label: 'Events Reference',
|
||
customProps: {
|
||
icon: Icons['academic-cap-solid'],
|
||
description: 'Check out triggered events in Medusa and their expected payloads.',
|
||
}
|
||
},
|
||
{
|
||
type: 'link',
|
||
href: '/modules/orders/returns',
|
||
label: 'Order Returns',
|
||
customProps: {
|
||
icon: Icons['academic-cap-solid'],
|
||
description: 'Learn about the Order Returns architecture and features.',
|
||
}
|
||
},
|
||
{
|
||
type: 'link',
|
||
href: '/modules/orders/swaps',
|
||
label: 'Order Swaps',
|
||
customProps: {
|
||
icon: Icons['academic-cap-solid'],
|
||
description: 'Learn about the Order Swaps architecture and features.',
|
||
}
|
||
},
|
||
{
|
||
type: 'link',
|
||
href: '/modules/orders/#order-edits',
|
||
label: 'Order Edits',
|
||
customProps: {
|
||
icon: Icons['academic-cap-solid'],
|
||
description: 'Learn about the Order Edits architecture and features.',
|
||
}
|
||
},
|
||
]} />
|
||
|
||
---
|
||
|
||
## Customer Segmentation
|
||
|
||
Businesses use Customer Segmentation to organize customers into different groups and then apply different price rules to these groups. For example, you may group customers by their product preferences, the number of orders they've placed, or geographical location.
|
||
|
||
Based on your use case and the segmentation logic, you can use different Medusa automation development tools to detect a customer's segment. Medusa also provides a Customer Groups feature that allows you to segment customers, whether you do it manually or automatically.
|
||
|
||
For example, if you're grouping customers with over twenty orders, you can use a Subscriber that listens to the `order.placed` event and checks the number of orders the customer has so far. Then, you can add that customer to the VIP group using Medusa's Customer Groups feature. Finally, you can utilize the Price Lists feature to provide different prices or discounts for customers in the VIP group.
|
||
|
||
<DocCardList colSize={4} items={[
|
||
{
|
||
type: 'link',
|
||
href: '/modules/customers/customer-groups',
|
||
label: 'Customer Groups',
|
||
customProps: {
|
||
icon: Icons['academic-cap-solid'],
|
||
description: 'Learn about the Customer Groups architecture and features.',
|
||
}
|
||
},
|
||
{
|
||
type: 'link',
|
||
href: '/development/events',
|
||
label: 'Subscribers and Events',
|
||
customProps: {
|
||
icon: Icons['academic-cap-solid'],
|
||
description: 'Learn about events in Medusa and how to listen to events.',
|
||
}
|
||
},
|
||
{
|
||
type: 'link',
|
||
href: '/modules/price-lists',
|
||
label: 'Price Lists',
|
||
customProps: {
|
||
icon: Icons['academic-cap-solid'],
|
||
description: 'Learn about the Price Lists architecture and features.',
|
||
}
|
||
},
|
||
]} />
|
||
|
||
<Details>
|
||
<Summary>Example: Add customer to VIP group</Summary>
|
||
|
||
Here’s an example of a subscriber that listens to the `order.placed` event and checks if the customer should be added to the VIP customer group based on their number of orders:
|
||
|
||
```ts title="src/subscribers/add-custom-to-vip.ts"
|
||
import {
|
||
type SubscriberConfig,
|
||
type SubscriberArgs,
|
||
OrderService,
|
||
CustomerService,
|
||
CustomerGroupService,
|
||
} from "@medusajs/medusa"
|
||
|
||
export default async function handleOrderPlaced({
|
||
data, eventName, container, pluginOptions,
|
||
}: SubscriberArgs<Record<string, string>>) {
|
||
const orderService: OrderService = container.resolve(
|
||
"orderService"
|
||
)
|
||
const customerService: CustomerService = container.resolve(
|
||
"customerService"
|
||
)
|
||
const customerGroupService: CustomerGroupService =
|
||
container.resolve("customerGroupService")
|
||
|
||
// check if VIP group exists
|
||
const vipGroup = await customerGroupService.list({
|
||
name: "VIP",
|
||
}, {
|
||
relations: ["customers"],
|
||
})
|
||
if (!vipGroup.length) {
|
||
return
|
||
}
|
||
|
||
// retrieve order and its customer
|
||
const order = await orderService.retrieve(data.id)
|
||
|
||
if (!order || !order.customer_id ||
|
||
vipGroup[0].customers.find(
|
||
(customer) => customer.id === order.customer_id
|
||
) !== undefined) {
|
||
return
|
||
}
|
||
|
||
// retrieve orders of this customer
|
||
const [, count] = await orderService.listAndCount({
|
||
customer_id: order.customer_id,
|
||
})
|
||
|
||
if (count >= 20) {
|
||
// add customer to VIP group
|
||
customerGroupService.addCustomers(
|
||
vipGroup[0].id,
|
||
order.customer_id
|
||
)
|
||
}
|
||
}
|
||
|
||
export const config: SubscriberConfig = {
|
||
event: OrderService.Events.PLACED,
|
||
context: {
|
||
subscriberId: "order-placed-handler",
|
||
},
|
||
}
|
||
```
|
||
|
||
</Details>
|
||
|
||
|
||
---
|
||
|
||
## Marketing Automation
|
||
|
||
In your commerce store, you may utilize marketing strategies that encourage customers to make purchases.
|
||
|
||
For example, you may send a newsletter when new products are added to your store. In Medusa, this can be handled by listening to the [product events](../development/events/events-list.md#product-events), such as `product.created`, using a Subscriber, then sending an email to subscribed customers with tools like [SendGrid](../plugins/notifications/sendgrid.mdx) or [Mailchimp](../plugins/notifications/mailchimp.md).
|
||
|
||
You can alternatively have a Scheduled Job that checks if the number of new products has exceeded a set threshold, then send out the newsletter.
|
||
|
||
<DocCardList colSize={4} items={[
|
||
{
|
||
type: 'link',
|
||
href: '/development/events',
|
||
label: 'Events and Subscribers',
|
||
customProps: {
|
||
icon: Icons['academic-cap-solid'],
|
||
description: 'Learn about events in Medusa and how to listen to events.',
|
||
}
|
||
},
|
||
{
|
||
type: 'link',
|
||
href: '/development/scheduled-jobs/overview',
|
||
label: 'Scheduled Jobs',
|
||
customProps: {
|
||
icon: Icons['academic-cap-solid'],
|
||
description: 'Learn about scheduled jobs and how you can create one.',
|
||
}
|
||
},
|
||
{
|
||
type: 'link',
|
||
href: '/development/events/events-list',
|
||
label: 'Events Reference',
|
||
customProps: {
|
||
icon: Icons['academic-cap-solid'],
|
||
description: 'Check out triggered events in Medusa and their expected payloads.',
|
||
}
|
||
},
|
||
]} />
|
||
|
||
<Details>
|
||
<Summary>Example: Sending a newsletter email after adding ten products</Summary>
|
||
|
||
Here’s an example of listening to the `product.created` event in a subscriber and send a newsletter if the condition is met:
|
||
|
||
```ts title="src/subscribers/send-products-newsletter.ts"
|
||
import {
|
||
type SubscriberConfig,
|
||
type SubscriberArgs,
|
||
ProductService,
|
||
StoreService,
|
||
CustomerService,
|
||
} from "@medusajs/medusa"
|
||
import {
|
||
ProductSelector,
|
||
} from "@medusajs/medusa/dist/types/product"
|
||
|
||
export default async function handleOrderPlaced({
|
||
data, eventName, container, pluginOptions,
|
||
}: SubscriberArgs<Record<string, string>>) {
|
||
const productService: ProductService = container.resolve(
|
||
"productService"
|
||
)
|
||
const storeService: StoreService = container.resolve(
|
||
"storeService"
|
||
)
|
||
const customerService: CustomerService = container.resolve(
|
||
"customerService"
|
||
)
|
||
const sendgridService = container.resolve("sendgridService")
|
||
|
||
// retrieve store to have access to last send date
|
||
const store = await storeService.retrieve()
|
||
|
||
const productFilters: ProductSelector = {}
|
||
if (store.metadata.last_send_date) {
|
||
productFilters.created_at = {
|
||
gt: new Date(store.metadata.last_send_date as string),
|
||
}
|
||
}
|
||
|
||
const products = await productService.list(
|
||
productFilters
|
||
)
|
||
|
||
if (products.length > 10) {
|
||
// get subscribed customers
|
||
const customers = await customerService.list({
|
||
metadata: {
|
||
is_subscribed: true,
|
||
},
|
||
})
|
||
sendgridService.sendEmail({
|
||
templateId: "product-newsletter",
|
||
from: "hello@medusajs.com",
|
||
to: customers.map((customer) => ({
|
||
name: customer.first_name,
|
||
email: customer.email,
|
||
})),
|
||
dynamic_template_data: {
|
||
// any data necessary for your template...
|
||
products,
|
||
},
|
||
})
|
||
|
||
await storeService.update({
|
||
metadata: {
|
||
last_send_date: new Date(),
|
||
},
|
||
})
|
||
}
|
||
}
|
||
|
||
export const config: SubscriberConfig = {
|
||
event: ProductService.Events.CREATED,
|
||
context: {
|
||
subscriberId: "product-create-handler",
|
||
},
|
||
}
|
||
```
|
||
|
||
</Details>
|
||
|
||
---
|
||
|
||
## Automation Development Toolkit
|
||
|
||
The use cases mentioned in this guide exemplify what commerce automation you can perform or implement with Medusa. Medusa provides the necessary development tools and mechanisms to automate tasks and manual work.
|
||
|
||
<DocCardList colSize={6} items={[
|
||
{
|
||
type: 'link',
|
||
href: '/development/events',
|
||
label: 'Events and Subscribers',
|
||
customProps: {
|
||
icon: Icons['academic-cap-solid'],
|
||
description: 'Learn about events in Medusa and how to listen to events.',
|
||
}
|
||
},
|
||
{
|
||
type: 'link',
|
||
href: '/development/notification/overview',
|
||
label: 'Notification Service',
|
||
customProps: {
|
||
icon: Icons['academic-cap-solid'],
|
||
description: 'Learn about notification services and how to create one.',
|
||
}
|
||
},
|
||
{
|
||
type: 'link',
|
||
href: '/development/scheduled-jobs/overview',
|
||
label: 'Scheduled Jobs',
|
||
customProps: {
|
||
icon: Icons['academic-cap-solid'],
|
||
description: 'Learn about scheduled jobs and how you can create one.',
|
||
}
|
||
},
|
||
{
|
||
type: 'link',
|
||
href: '/plugins/notifications',
|
||
label: 'Notification Plugins',
|
||
customProps: {
|
||
icon: Icons['bolt-solid'],
|
||
description: 'Check out available notification plugins.',
|
||
}
|
||
},
|
||
]} />
|