Overview
Icon for Mailgun

Mailgun

Send transactional emails via Mailgun

@mdgar/medusa-notification-mailgun

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.

What this plugin provides

  • Notification provider — registers Copy to clipboardmailgun as a notification provider for the Copy to clipboardemail channel. Called automatically by Medusa when you use Copy to clipboardcreateNotifications() in a subscriber.
  • Admin API: send test emailCopy to clipboardPOST /admin/mailgun/send-email. Sends a test email to a registered admin user.
  • Admin API: event checklistCopy to clipboardGET /admin/mailgun/checklist. Scans your subscribers and Mailgun account to report coverage status for each tracked event.
  • Admin UI — a "Mailgun" page in the Medusa admin sidebar with two tabs: "Event Checklist" (default) and "Send Test".

Prerequisites

  • Node.js v20+
  • MedusaJS v2.3.0+
  • A Mailgun account with a verified sending domain

Installation

pnpm add @mdgar/medusa-notification-mailgun
# or
npm install @mdgar/medusa-notification-mailgun
# or
yarn add @mdgar/medusa-notification-mailgun

Copy to clipboardmailgun.js is bundled as a direct dependency of the plugin and will be installed automatically; you do not need to install it separately.

Configuration

Add the plugin to Copy to clipboardmedusa-config.ts. Two entries are needed: a Copy to clipboardplugins entry to load the admin UI and API routes, and a Copy to clipboardmodules 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, // optional

Options

Option Required Default Description Copy to clipboardapi_key Yes — Your Mailgun API key Copy to clipboarddomain Yes — Your verified Mailgun sending domain Copy to clipboardfrom No Copy to clipboardnoreply@<domain> Default sender address used when Copy to clipboardfrom is not passed per-notification Copy to clipboardregion No Copy to clipboard"us" Mailgun API region: Copy to clipboard"us" or Copy to clipboard"eu" Copy to clipboardeventMap No built-in map Copy to clipboardEventCheckConfig[] — override or extend the checklist's event→template map without forking the plugin. Entries with an Copy to clipboardevent key that matches a built-in are replaced; new entries are appended.

Customizing the checklist event map

The admin "Event Checklist" tab scans your subscribers against a built-in list of Medusa events and their expected Mailgun template names (e.g. Copy to clipboardorder.placedCopy to clipboardorder-confirmation). To add your own events or rename an expected template, pass Copy to clipboardeventMap in the provider options:

{
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,
eventMap: [
// Override a built-in: use a different template name for order.placed
{ event: "order.placed", expected_template: "my-order-confirmation" },
// Append a custom event
{ event: "loyalty.tier_upgraded", expected_template: "loyalty-upgrade" },
],
},
}

Each entry is Copy to clipboard{ event: string; expected_template: string }. The checklist endpoint merges your overrides onto the built-in map by Copy to clipboardevent key.

Environment variables

Variable Required Description Copy to clipboardMAILGUN_API_KEY Yes Your Mailgun API key Copy to clipboardMAILGUN_DOMAIN Yes Your verified Mailgun sending domain Copy to clipboardMAILGUN_FROM No Default sender address Copy to clipboardMAILGUN_REGION No Set to Copy to clipboard"eu" to use the EU API endpoint. Omit for US.

Set these in your Copy to clipboard.env file:

MAILGUN_API_KEY=key-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
MAILGUN_DOMAIN=mg.yourdomain.com
MAILGUN_FROM=no-reply@yourdomain.com
# MAILGUN_REGION=eu # uncomment if your account is on the EU region

Reference these in Copy to clipboardmedusa-config.ts via Copy to clipboardprocess.env — the checklist endpoint reads credentials from the same plugin options object, not from the environment directly.

Sending notifications

The provider integrates with Medusa's built-in notification system. Call Copy to clipboardcreateNotifications() 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 Copy to clipboarddata payload

The Copy to clipboarddata object controls how the email is built and carries template variables. All values must be strings.

Field Type Description Copy to clipboardsubject Copy to clipboardstring Email subject line. Optional — if omitted when using a stored template, Mailgun uses the subject defined in the template. Recommended when sending inline Copy to clipboardhtml/Copy to clipboardtext. Copy to clipboardlocale Copy to clipboardstring Selects a Mailgun template version (e.g. Copy to clipboard"fr", Copy to clipboard"de"). Only used when Copy to clipboardtemplate is set. Copy to clipboardhtml Copy to clipboardstring Inline HTML body. Used when no Copy to clipboardtemplate is set. Copy to clipboardtext Copy to clipboardstring Plain-text body. Used when neither Copy to clipboardtemplate nor Copy to clipboardhtml is set. Copy to clipboardfrom Copy to clipboardstring Per-notification sender address override. Takes precedence over the top-level Copy to clipboardfrom field only when the top-level field is not set. Copy to clipboardreplyTo Copy to clipboardstring Sets the Copy to clipboardReply-To header on the outgoing message. any other Copy to clipboardstring Additional keys are passed to Mailgun as template variables.

Content selection

The provider selects the message body using this priority order:

  1. Copy to clipboardtemplate — a Mailgun stored template; all Copy to clipboarddata fields are passed as Copy to clipboardh:X-Mailgun-Variables.
  2. Copy to clipboarddata.html — raw HTML body (no template).
  3. Copy to clipboarddata.text — plain-text body.

If none of Copy to clipboardtemplate, Copy to clipboarddata.html, or Copy to clipboarddata.text is provided, the provider throws Copy to clipboardINVALID_DATA rather than sending an email with a serialized DTO body.

Use Copy to clipboarddata.html or Copy to clipboarddata.text when you want to generate content dynamically in code rather than maintain a template in the Mailgun dashboard.

Stored template

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 Copy to clipboardh:X-Mailgun-Variables and available as Copy to clipboard{{variable_name}} inside Mailgun's Handlebars templates.

Localized template

Create multiple versions of a template in the Mailgun dashboard, tagging each with a locale (e.g. Copy to clipboarden, Copy to clipboardfr, Copy to clipboardde). Pass Copy to clipboardlocale in Copy to clipboarddata 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 Copy to clipboardlocale is present, the plugin sets Mailgun's Copy to clipboardt:version parameter. If omitted, Mailgun uses the template's default version.

Inline HTML

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>",
},
})

Plain text

await notificationService.createNotifications({
to: "customer@example.com",
channel: "email",
data: {
subject: "Your receipt",
text: "Thanks for your order. Your total was $42.00.",
},
})

Attachments

Pass base64-encoded file content in the Copy to clipboardattachments 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)

Overriding the sender address

Pass a Copy to clipboardfrom 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)

Copy to clipboarddata.from is also accepted, but is ignored when the top-level notification Copy to clipboardfrom field is set. Resolution order is: top-level Copy to clipboardfromCopy to clipboarddata.from → plugin-level default Copy to clipboardfromCopy to clipboardnoreply@<domain>. The top-level Copy to clipboardfrom field shown above is the preferred method.

Wiring up Medusa events to templates

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 Copy to clipboardcreateNotifications() when the event fires.

See Copy to clipboarddocs/medusa-notification-events.md 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.

Admin UI

The plugin adds a Mailgun page to the Medusa admin sidebar (envelope icon). The route is Copy to clipboard/mailgun.

The page has two tabs:

  • Event Checklist (default) — runs Copy to clipboardGET /admin/mailgun/checklist 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.

Admin API: send test email

POST /admin/mailgun/send-email
Authorization: Bearer <admin-jwt>
Content-Type: application/json

Sends a test email through the Mailgun notification provider.

Request body

Field Type Required Description Copy to clipboardto Copy to clipboardstring (email) Yes Recipient address. Must be a registered admin user email. Copy to clipboardsubject Copy to clipboardstring No Email subject line. If omitted, Mailgun uses the template's own subject. Copy to clipboardtemplate Copy to clipboardstring No Mailgun template name. If omitted, Copy to clipboarddata.text or Copy to clipboarddata.html is used for the body. Copy to clipboardfrom Copy to clipboardstring (email) No Sender address override. Defaults to the plugin's configured Copy to clipboardfrom. Copy to clipboardreply_to Copy to clipboardstring (email) No Reply-To address. When set, replies are directed to this address instead of the sender. Copy to clipboarddata Copy to clipboardobject No Template variables or body content. Typed as Copy to clipboard{ locale?: string; variables?: Record<string, unknown>; ... } with additional keys allowed via passthrough (e.g. Copy to clipboardhtml, Copy to clipboardtext).

Constraint: Copy to clipboardto 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 Copy to clipboarddata contains neither Copy to clipboardhtml nor Copy to clipboardtext, the plugin sends a plain-text fallback: Copy to clipboardTest email — subject: <subject> (or just Copy to clipboardTest email if no subject is provided).

Response

{ "success": true, "notification_id": "noti_01..." }

Example

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" }
}'

Admin API: event checklist

GET /admin/mailgun/checklist
Authorization: Bearer <admin-jwt>

Returns a diagnostic report for all tracked Medusa events. For each event, the endpoint checks:

  1. Whether a subscriber file exists in Copy to clipboardsrc/subscribers/ that references the event name.
  2. Whether that subscriber file contains a static Copy to clipboardtemplate: string literal, and what its value is.
  3. Whether that template exists in your Mailgun account (requires Copy to clipboardapi_key and Copy to clipboarddomain to be set in the plugin options in Copy to clipboardmedusa-config.ts).

Per-event status values:

Status Meaning Copy to clipboardpass Subscriber found, a static template name was detected in the file, and that template exists in Mailgun. Copy to clipboardwarn Subscriber found and a static template name was detected, but that template does not exist in Mailgun yet. Copy to clipboardinline Subscriber found, but no static template name was detected. The subscriber may be using inline HTML or plain text. Copy to clipboardfail No subscriber found for this event.

The top-level Copy to clipboardstatus rolls up the worst result across all events, excluding Copy to clipboardinline. Copy to clipboardinline events do not cause a Copy to clipboardwarn or Copy to clipboardfail rollup.

CI usage

# Fail if any event is missing a subscriber or Mailgun template
curl -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 templates
curl -sf -H "Authorization: Bearer $MEDUSA_ADMIN_TOKEN" \
"$MEDUSA_BACKEND_URL/admin/mailgun/checklist" \
| jq -e '.status != "fail"'

See Copy to clipboarddocs/checklist-endpoint.md for the full response shape, field descriptions, and additional CI patterns.

Development

# Install dependencies
pnpm install
# Build the plugin
pnpm run build
# Start in watch/develop mode
pnpm run dev
# Run tests
pnpm test

This plugin uses the official Medusa plugin toolchain (Copy to clipboardmedusa plugin:build / Copy to clipboardmedusa plugin:develop).

To test the plugin in a local Medusa project before publishing:

# In this plugin directory — build first, then link
pnpm run build
pnpm link --global
# In your Medusa project
pnpm link --global @mdgar/medusa-notification-mailgun

After any source change, run Copy to clipboardpnpm run build in the plugin directory again, or keep Copy to clipboardpnpm run dev running to rebuild continuously.

Tests

The test suite uses Jest and ts-jest. Run with:

pnpm test

Coverage includes:

  • Copy to clipboardvalidateOptions — rejects missing Copy to clipboardapi_key or Copy to clipboarddomain
  • Template path — Copy to clipboardh:X-Mailgun-Variables header, Copy to clipboardt:version locale selection
  • Inline HTML and plain-text paths
  • Sender resolution — configured address vs. Copy to clipboardnoreply@<domain> default
  • Subject omitted from payload when Copy to clipboarddata.subject is absent (defers to template subject)
  • Base64 attachment decoding
  • Mailgun API errors are wrapped in Copy to clipboardMedusaError with a sanitized, correlation-id'd message (Copy to clipboardMailgun send failed (ref: mg_…)); raw error details are logged server-side only. Copy to clipboardINVALID_DATA validation errors are re-thrown unwrapped.
  • EU region endpoint selection (Copy to clipboardhttps://api.eu.mailgun.net)
  • Return value — Copy to clipboardid field with Copy to clipboardmessage field fallback

License

MIT

You may also like

Browse all integrations

Build your own

Develop your own custom integration

Build your own integration with our API to speed up your processes. Make your integration available via npm for it to be shared in our Library with the broader Medusa community.

gift card interface

Ready to build your custom commerce setup?