Send UI-managed email notifications on Medusa store events via SMTP.
Send transactional emails from your Medusa v2 store when things happen — orders placed, shipments created, customers signed up, and more.
You pick which events matter, point them at HTML templates, and the plugin delivers over SMTP. Nothing is sent until you create an active mapping in the admin.
Medusa Website | Medusa Repository | Medusa Documentation
In your Medusa backend directory, install the plugin and Nodemailer:
1npm install @sam-ael/medusa-plugin-mailer nodemailer2# or3yarn add @sam-ael/medusa-plugin-mailer nodemailerAdd the plugin to your plugins list:
1// medusa-config.ts2module.exports = defineConfig({3 // ...4 plugins: [5 // ...6 {7 resolve: "@sam-ael/medusa-plugin-mailer",8 options: {},9 },10 ],11})Set environment variables in (see Configuration for the full list):
1MAILER_SMTP_HOST=smtp.example.com2MAILER_SMTP_PORT=5873MAILER_SMTP_USER=your-user4MAILER_SMTP_PASS=your-password5MAILER_SMTP_SECURE=false6
7MAILER_FROM_NAME_1=My Store8MAILER_FROM_ADDRESS_1=no-reply@example.com9
10MAILER_TEMPLATES_DIR=src/email_templates11
12STORE_NAME=My Store13STORE_URL=https://www.example.comRun migrations so the mapping table exists:
npx medusa db:migrateThis creates an empty table. It does not insert any mappings for you.
Start the backend and open the admin. You should see Mailer in the sidebar.
Create at least one active mapping (event → template → variables). Until you do that, the plugin will not send mail for that event.
| Variable | Required | Description |
|---|---|---|
| Yes (to send) | SMTP host | |
| No (default ) | SMTP port | |
| Yes (to send) | SMTP username | |
| Yes (to send) | SMTP password | |
| No | Set for port 465 | |
| Yes (to send) | Display name for sender profile 1 | |
| Yes (to send) | From address for sender profile 1 | |
| … | No | Extra sender profiles (pick by index in a mapping) |
| … | No | |
| Recommended | Folder of templates (relative to process cwd or absolute). Falls back to bundled templates if missing | |
| Recommended | Used as in templates | |
| or | Recommended | Used as / |
| No | Absolute image URL for | |
| No (default ) | Parallel sends when several mappings match one event | |
| No | Set for Nodemailer debug logs | |
| No | EHLO / HELO hostname |
If SMTP is incomplete, the plugin logs a warning and skips sending instead of crashing.
Think of two layers:
No active mapping ⇒ no email, even if the event fires.
Templates use Go-style variables:
1<p>Hi {{ .first_name }},</p>2<p>Order #{{ .display_id }} is confirmed.</p>Always available without mapping:
| Placeholder | Source |
|---|---|
| / | or |
| resolved recipient | |
| mapping subject (after substitution) | |
| (optional) | |
| default text if you do not map it |
Your mapping’s template variables object maps each name to a data path on the loaded entity, for example:
1{2 "first_name": "customer.first_name",3 "display_id": "display_id",4 "total": "total"5}Unmatched placeholders are removed before the message is sent.
| Type | Behavior |
|---|---|
| Prefer email from the order/customer graph | |
| Prefer the order’s field | |
| Use the fixed address on the mapping |
These names are hardcoded. Map only the ones you need.
| Event | Typical use |
|---|---|
| Order confirmation | |
| Order finished | |
| Cancellation notice | |
| Use sparingly (can be noisy) | |
| “We’re packing your order” | |
| Fulfillment canceled | |
| Return started | |
| Return received / refund note |
Prefer the Medusa core-flows names:
| Event | Typical use |
|---|---|
| Shipped + tracking | |
| Delivered |
Legacy aliases still listen if something emits them: , , .
| Event |
|---|
| , |
| , |
| , |
Your app can emit these; the plugin will handle them if you add mappings. Payloads are not graph-hydrated — put the fields you need on the event data and map paths like → .
| Event | Example fields on |
|---|---|
| , , , , | |
| same | |
| / | , , , , |
| , , , |
Example emit from your backend:
1import { Modules } from "@medusajs/framework/utils"2
3const eventBus = container.resolve(Modules.EVENT_BUS)4
5await eventBus.emit({6 name: "loyalty.points_credited",7 data: {8 email: "customer@example.com",9 amount: 100,10 balance: 500,11 description: "Thanks for your order",12 },13})| Event pattern | Hydrated data |
|---|---|
| (most) | Full order (addresses, customer, items, totals, …) |
| Order via | |
| / | Fulfillment + linked order (including tracking labels when present) |
| Custom loyalty / wallet / membership | Event payload only |
The package ships plain, unbranded HTML under . Use them as-is or copy them into your and redesign.
| Template file | Good starting point for |
|---|---|
| Alternate order confirmation | |
| Returns / refunds | |
| Email confirmation flows | |
| Account removal | |
| / | Loyalty custom events |
| / | Wallet custom events |
| Membership custom event | |
| / | Free-form messages |
Brand with and optional . Regenerate the defaults from the plugin repo with:
node scripts/generate-bundled-templates.mjsEvent:
Template:
Subject:
Recipient: Order email
Variables:
| Name | Path |
|---|---|
Event:
Template:
Subject:
Variables:
| Name | Path |
|---|---|
Run your Medusa backend:
1npm run dev2# or3npx medusa developOpen Admin → Mailer and check that SMTP shows as configured.
Create an active mapping for → (or your own template).
Either:
Confirm the message arrives and that placeholders look correct.
If nothing arrives: check SMTP env vars, that the mapping is active, and that the event name matches the list above (for shipping, prefer ).
Useful for automation or custom UIs. All routes require an authenticated admin user.
| Method | Path | Purpose |
|---|---|---|
| SMTP status and sender profiles | ||
| Template files and detected variables | ||
| List mappings | ||
| Create mapping | ||
| Update mapping | ||
| Delete mapping | ||
| Manual / test send |
You can also trigger the same pipeline from code:
1import { sendEventEmailsWorkflow } from "@sam-ael/medusa-plugin-mailer/workflows"2
3await sendEventEmailsWorkflow(container).run({4 input: {5 event_name: "order.placed",6 event_data: { id: orderId },7 },8})| What you see | What to check |
|---|---|
| No emails ever | SMTP env incomplete; Admin still says not configured |
| Event fires, no email | No active mapping for that exact event name |
| Shipment email never sends | Map , not only older fulfillment aliases |
| Empty fields in the body | Variable path wrong for that event’s data shape |
| Wrong “to” address | Guest orders use order email; adjust recipient type |
| Two emails for one action | Another subscriber in your app is also sending — turn one off |
1yarn install2yarn build # produces .medusa/server3yarn typecheck4yarn testPeer dependencies should match your host Medusa version as closely as practical.
MIT