Mailgun notification provider plugin for MedusaJS v2
Sends transactional emails via the Mailgun HTTP API. Supports stored templates (with localization), inline HTML/text, file attachments, and includes an Admin UI page for sending test emails and verifying event coverage.
Requires MedusaJS v2.3.0 or later.
In a hurry? Quickstart! From install to first email in ~15 minutes.
pnpm add @mdgar/medusa-notification-mailgun mailgun.js# ornpm install @mdgar/medusa-notification-mailgun mailgun.js# oryarn add @mdgar/medusa-notification-mailgun mailgun.js
is a peer dependency — install it alongside the plugin.
Add the plugin to . Two entries are needed: a entry to load the admin UI and API routes, and a entry to register the notification provider.
import { defineConfig } from "@medusajs/framework/utils"module.exports = defineConfig({// ...plugins: ["@mdgar/medusa-notification-mailgun",],modules: [{resolve: "@medusajs/medusa/notification",options: {providers: [{resolve: "@mdgar/medusa-notification-mailgun/providers/notification-mailgun",id: "mailgun",options: {channels: ["email"],api_key: process.env.MAILGUN_API_KEY,domain: process.env.MAILGUN_DOMAIN,from: process.env.MAILGUN_FROM, // optionalregion: "us", // optional, "us" | "eu"},},],},},],})
| Option | Required | Default | Description |
|---|---|---|---|
| Yes | — | Your Mailgun API key | |
| Yes | — | Your verified Mailgun sending domain | |
| No | Default sender address used when is not passed per-notification | ||
| No | Mailgun API region: or |
| Variable | Required | Description |
|---|---|---|
| Yes | Your Mailgun API key | |
| Yes | Your verified Mailgun sending domain | |
| No | Default sender address | |
| No | Set to to use the EU API endpoint. Omit for US. |
Set these in your file:
MAILGUN_API_KEY=key-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxMAILGUN_DOMAIN=mg.yourdomain.comMAILGUN_FROM=no-reply@yourdomain.com# MAILGUN_REGION=eu # uncomment if your account is on the EU region
Reference these in via — the checklist endpoint reads credentials from the same plugin options object, not from the environment directly.
The provider integrates with Medusa's built-in notification system. Call from a subscriber or workflow:
const notificationService = container.resolve(Modules.NOTIFICATION)await notificationService.createNotifications({to: "customer@example.com",channel: "email",template: "order-confirmation",data: {subject: "Your order is confirmed",order_id: "ord_123",customer_name: "Alice",},})
The object controls how the email is built and carries template variables. All values must be strings.
| Field | Type | Description |
|---|---|---|
| Email subject line. Optional — if omitted, Mailgun uses the subject defined in the stored template. Required when not using a stored template. | ||
| Selects a Mailgun template version (e.g. , ). Only used when is set. | ||
| Inline HTML body. Used when no is set. | ||
| Plain-text body. Used when neither nor is set. | ||
| Per-notification sender address override. Takes precedence over the top-level field only when the top-level field is not set. | ||
| Sets the header on the outgoing message. | ||
| any other | Additional keys are passed to Mailgun as template variables. |
The provider selects the message body using this priority order:
Use or when you want to generate content dynamically in code rather than maintain a template in the Mailgun dashboard.
await notificationService.createNotifications({to: "customer@example.com",channel: "email",template: "order-confirmation",data: {subject: "Your order is confirmed",order_id: "ord_123",},})
Template variables are forwarded to Mailgun via and available as inside Mailgun's Handlebars templates.
Create multiple versions of a template in the Mailgun dashboard, tagging each with a locale (e.g. , , ). Pass in to select the matching version:
await notificationService.createNotifications({to: "customer@example.com",channel: "email",template: "order-confirmation",data: {locale: "fr",subject: "Votre commande est confirmée",order_id: "ord_123",},})
When is present, the plugin sets Mailgun's parameter. If omitted, Mailgun uses the template's default version.
await notificationService.createNotifications({to: "customer@example.com",channel: "email",data: {subject: "Welcome!",html: "<h1>Welcome to our store</h1><p>Thanks for signing up.</p>",},})
await notificationService.createNotifications({to: "customer@example.com",channel: "email",data: {subject: "Your receipt",text: "Thanks for your order. Your total was $42.00.",},})
Pass base64-encoded file content in the field:
await notificationService.createNotifications({to: "customer@example.com",channel: "email",data: { subject: "Your invoice", text: "See attached." },attachments: [{filename: "invoice.pdf",content: "<base64-encoded content>",},],} as any)
Pass a field on the notification to override the plugin-level default for a single send:
await notificationService.createNotifications({to: "customer@example.com",channel: "email",from: "billing@yourdomain.com",data: { subject: "Invoice", text: "..." },} as any)
is also accepted, but is ignored when a plugin-level is set in . The top-level field shown above is the preferred method.
Medusa fires events for commerce operations (order placed, shipment created, password reset, etc.) but sends no email by default. To send email on an event you need a Mailgun template and a subscriber that calls when the event fires.
See for the complete how-to guide: subscriber patterns for each event, the full event reference, and suggested template variables.
New to the plugin? The quickstart walks through the full setup end-to-end.
The plugin adds a Mailgun page to the Medusa admin sidebar (envelope icon). The route is .
The page has two tabs:
Event Checklist (default) — runs and displays per-event status as a table. Shows whether each tracked event has a subscriber, what template name was detected in the subscriber, and whether that template exists in Mailgun. For events with a confirmed Mailgun template, the template name is displayed below the event name in the table row.
Send Test — form to send a test email to a registered admin user. Fields: recipient (dropdown of admin users), subject, optional message body, optional template name, optional from-address override, optional reply-to address, and optional key-value template variables.
Sends a test email through the Mailgun notification provider.
| Field | Type | Required | Description |
|---|---|---|---|
| (email) | Yes | Recipient address. Must be a registered admin user email. | |
| No | Email subject line. If omitted, Mailgun uses the template's own subject. | ||
| No | Mailgun template name. If omitted, or is used for the body. | ||
| (email) | No | Sender address override. Defaults to the plugin's configured . | |
| (email) | No | Reply-To address. When set, replies are directed to this address instead of the sender. | |
| No | Template variables or body content (, ). All values must be strings. |
Constraint: must be the email address of a registered Medusa admin user. The endpoint looks up the address in the user service before sending. Arbitrary addresses are rejected.
If no template is specified and contains neither nor , the plugin sends a plain-text fallback: (or just if no subject is provided).
{ "success": true, "notification_id": "noti_01..." }
curl -X POST https://yourstore.com/admin/mailgun/send-email \-H "Authorization: Bearer <admin-jwt>" \-H "Content-Type: application/json" \-d '{"to": "admin@yourstore.com","subject": "Hello from Mailgun","template": "welcome","data": { "customer_name": "Alice" }}'
Returns a diagnostic report for all tracked Medusa events. For each event, the endpoint checks:
Per-event status values:
| Status | Meaning |
|---|---|
| Subscriber found, a static template name was detected in the file, and that template exists in Mailgun. | |
| Subscriber found and a static template name was detected, but that template does not exist in Mailgun yet. | |
| Subscriber found, but no static template name was detected. The subscriber may be using inline HTML or plain text. | |
| No subscriber found for this event. |
The top-level rolls up the worst result across all events, excluding . events do not cause a or rollup.
# Fail if any event is missing a subscriber or Mailgun templatecurl -sf -H "Authorization: Bearer $MEDUSA_ADMIN_TOKEN" \"$MEDUSA_BACKEND_URL/admin/mailgun/checklist" \| jq -e '.status == "pass"'# Fail only if a subscriber is missing; allow missing templatescurl -sf -H "Authorization: Bearer $MEDUSA_ADMIN_TOKEN" \"$MEDUSA_BACKEND_URL/admin/mailgun/checklist" \| jq -e '.status != "fail"'
See for the full response shape, field descriptions, and additional CI patterns.
# Install dependenciespnpm install# Build the pluginpnpm run build# Start in watch/develop modepnpm run dev# Run testspnpm test
This plugin uses the official Medusa plugin toolchain ( / ).
To test the plugin in a local Medusa project before publishing:
# In this plugin directory — build first, then linkpnpm run buildpnpm link --global# In your Medusa projectpnpm link --global @mdgar/medusa-notification-mailgun
After any source change, run in the plugin directory again, or keep running to rebuild continuously.
The test suite uses Jest and ts-jest. Run with:
pnpm test
Coverage includes:
MIT