Transactional emails are one of those parts of an ecommerce platform that customers only notice when they go wrong. An order confirmation that never arrives, a shipping notification that goes to spam, a password reset email that does not send. These failures erode trust fast, and fixing them after launch is always more painful than setting them up correctly before.
Medusa.js v2 handles notifications through a clean Notification Module that decouples the act of triggering an email from the service that actually sends it. You configure a provider, whether that is Resend, SendGrid, or anything else, and Medusa routes notification events through it. This guide walks through the setup for both Resend and SendGrid, covers how to wire email to order events, and explains how to build and test templates so your transactional emails are production-ready from day one.
How the Medusa Notification Module Works
How the Medusa Notification Module Works
In Medusa v2, the Notification Module is the central system responsible for dispatching notifications across any channel. Email is one channel. SMS is another. The module delegates the actual sending work to a provider, which is a separate module you configure in your Medusa backend. When an event fires in Medusa, for example when an order is placed, a subscriber picks up that event and calls the Notification Module to create a notification. The module then passes it to whichever provider is registered for the email channel.
This separation is deliberate. Medusa does not care which email service you use. You could use Resend today and switch to SendGrid tomorrow without touching any of your subscriber logic. The swap is a configuration change. All the business logic that decides when to send an email and what data to include stays the same.
One important constraint to understand before setup: only one provider can be registered per channel. If you register SendGrid for the email channel, Resend cannot also be registered for email in the same Medusa instance. Choose one for your project and stick with it. For most teams starting fresh in 2026, Resend is the more developer-friendly choice. For teams that already have SendGrid infrastructure in place, the official SendGrid provider is the right path.
Setting Up Resend with Medusa.js
Setting Up Resend with Medusa.js
Resend is an email API built specifically for developers. It has clean SDK documentation, supports React Email for template authoring, and makes it straightforward to build exactly the email output you want. Integrating Resend with Medusa involves creating a custom module provider, configuring it in your Medusa backend, and writing a subscriber to trigger emails on commerce events.
Install Required Packages
Start by installing the Resend SDK and the React Email packages into your Medusa project. From your project root, run:
npm install resend @react-email/components @react-email/renderReact Email gives you a component-based way to build email templates using JSX. The output is rendered to HTML before being passed to Resend for delivery. This approach keeps your template code maintainable and lets you preview emails in a local development server before they go live.
Create the Resend Module Provider
In your Medusa project, create a new directory for the provider at src/modules/resend. Inside this directory, create a service.ts file that extends AbstractNotificationProviderService. Your service class must implement a send method that accepts a notification object and calls the Resend API to dispatch the email.
Inside the send method, import your rendered email template, call the Resend client with the recipient address, subject, and rendered HTML, and return the result. The Medusa container will pass your Resend API key through the module options, which you access in the constructor.
Register the Provider in medusa-config.js
Once the service is created, register it in your Medusa configuration file under the modules array. You reference the notification module and add your Resend provider to its providers list with the email channel specified:
module.exports = defineConfig({ modules: [ { resolve: "@medusajs/medusa/notification", options: { providers: [ { resolve: "./src/modules/resend", id: "resend", options: { channels: ["email"], api_key: process.env.RESEND_API_KEY, from: process.env.RESEND_FROM_EMAIL, }, }, ], }, }, ],});
Add RESEND_API_KEY and RESEND_FROM_EMAIL to your .env file. The from email must be a verified sender address in your Resend account. Without sender verification, emails will be rejected before leaving the service.
Preview Templates Locally
Add a dev script to your package.json that runs the React Email preview server pointing to your templates directory. Running this script opens a local server at localhost:3000 where you can see exactly how each email template renders before it is used in a real notification. Iterate on the template design here rather than after deployment.
Setting Up SendGrid with Medusa.js
Setting Up SendGrid with Medusa.js
SendGrid is the more established option and integrates with Medusa through an official notification module provider. It uses dynamic templates defined directly in the SendGrid dashboard, which means your email design lives in SendGrid rather than in your codebase. This works well for teams that want marketing or design stakeholders to manage email templates independently of the development team.
Install the SendGrid Notification Provider
The SendGrid provider ships as part of the Medusa package in v2. You do not need to install a separate package. Instead, reference it directly in your medusa-config.js using its module path:
module.exports = defineConfig({ modules: [ { resolve: "@medusajs/medusa/notification", options: { providers: [ { resolve: "@medusajs/medusa/notification-sendgrid", id: "sendgrid", options: { channels: ["email"], api_key: process.env.SENDGRID_API_KEY, from: process.env.SENDGRID_FROM, }, }, ], }, }, ],});
Set SENDGRID_API_KEY and SENDGRID_FROM in your environment. The API key must have at minimum Mail Send permission in your SendGrid account. Create the key under Settings > API Keys in the SendGrid dashboard, and verify your sender address under Sender Authentication before testing.
Creating Dynamic Templates in SendGrid
SendGrid uses dynamic templates to define the HTML and structure of each email type. You create a template inside the SendGrid dashboard, design it using their drag-and-drop editor or by uploading custom HTML, and reference Handlebars variables in the template body to insert dynamic data. The template ID assigned by SendGrid is what you pass in the template field when creating a notification from your Medusa subscriber.
For each notification type you want to send, you need a separate template in SendGrid. An order confirmation template, a shipping notification template, a return request acknowledgment template, and a password reset template are all common starting points. Define the Handlebars variables each template expects, such as customer name, order total, and item list, and make sure your subscriber passes matching data when triggering the notification.
Writing Subscribers to Trigger Email Notifications
Writing Subscribers to Trigger Email Notifications
Once your provider is configured, the final piece is the subscriber. A subscriber in Medusa is a function that listens for a specific event and runs whenever that event fires. To send an order confirmation email, you create a subscriber for the order.placed event.
Order Placed Subscriber
Create a file at src/subscribers/order-placed.ts in your Medusa project. The subscriber receives the event data and the Medusa container, which gives it access to all registered services. Inside the subscriber, resolve the Notification Module service from the container and call createNotifications with the customer email address, the email channel, the template ID, and the dynamic data the template needs.
import { SubscriberArgs, SubscriberConfig } from "@medusajs/framework"import { Modules } from "@medusajs/framework/utils"import { INotificationModuleService } from "@medusajs/framework/types"export default async function orderPlacedHandler( { event: { data }, container }: SubscriberArgs<{ id: string }>) { const notificationService: INotificationModuleService = container.resolve(Modules.NOTIFICATION) await notificationService.createNotifications({ to: data.email, channel: "email", template: "order-confirmation", data: { order_id: data.id }, })}export const config: SubscriberConfig = { event: "order.placed",}This subscriber will fire every time an order is placed in Medusa. The template value is the ID of the template you defined in SendGrid, or the template name you reference in your Resend provider service. The data object carries whatever dynamic content the template needs to render correctly.
Using sendNotificationsStep in a Workflow
For more complex flows, you can send notifications as part of a Medusa workflow using the built-in sendNotificationsStep. This is useful when you need to send an email as one step in a multi-step automated process, such as triggering a notification after a custom fulfillment action or after a scheduled job runs. The step accepts the same parameters as createNotifications and integrates cleanly with the workflow compensation model, meaning if a later step fails, Medusa can roll back earlier steps including the notification if needed.
Need help setting up custom notification flows in Medusa.js?
Get in touchEnvironment Configuration for Production
Environment Configuration for Production
Environment Configuration for Production
The configuration you use during local development and the configuration for production need to be handled separately. During development, use the local event bus module, which processes events synchronously in memory. This makes testing fast but it does not reflect real production behavior where events are queued and processed asynchronously.
For production, configure Redis as your event bus. Add the Redis event bus module to your Medusa configuration and point it at your production Redis instance. This ensures that if your email provider is temporarily unavailable or if the Medusa server restarts during a high-traffic period, queued events are not lost. They are processed when the server and provider are both available again.
Keep your email provider credentials out of your source code entirely. Use environment variables for the API key and sender address, and manage those secrets through your hosting provider or a secrets manager. For teams deploying to AWS, use Parameter Store or Secrets Manager. For Railway or Render deployments, use the environment variable panel in the dashboard.
Testing Email Notifications Before Go-Live
Testing Email Notifications Before Go-Live
Testing transactional emails before going live is one of the most important pre-launch steps and one of the most commonly skipped. The test setup is straightforward. In your staging environment, configure your email provider with a real API key but use a verified test sender address. Create a test order in your staging Medusa instance and confirm that the order confirmation email arrives at the expected recipient.
Verify each email type individually. Placed order, shipment created, return requested, password reset. Each of these fires on a different event with different data, so each needs its own test. Check that the dynamic variables render correctly in each template, that the formatting looks right across desktop and mobile email clients, and that the email does not land in spam.
The official Medusa documentation on the Resend integration guide and the SendGrid notification module provider both include full working code examples and the complete list of configuration options available for each provider.
At Askan Technologies, setting up production-ready email notifications is part of every Medusa store delivery. For teams building on Medusa and looking for end-to-end implementation support, our Medusa.js ecommerce development services cover everything from initial setup through production deployment and post-launch maintenance.
Choosing Between Resend and SendGrid for Your Medusa Store
Choosing Between Resend and SendGrid for Your Medusa Store
Both providers work reliably with Medusa, and the decision between them is mostly about where your team wants to manage templates and how much control you want over the email rendering layer. If your team writes code and wants to keep templates in version control alongside the rest of the project, Resend with React Email is the cleaner choice. Templates are components, they live in your repository, and changes go through your normal code review process.
If your marketing or operations team owns email content and needs to update templates without developer involvement, SendGrid dynamic templates give them that independence. The template management UI in SendGrid is accessible to non-technical users, and changes are reflected immediately without requiring a code deployment.
Either way, configure your provider correctly from the start, write subscribers for every email event your customers will experience, and test every template before launch. Transactional email is a small part of the platform to build but a significant part of the experience your customers actually see after they buy.
Written by
Raj Thilak
CQO
