Email SDK
Adapters

Mailgun

Send through the Mailgun Messages API with domain-scoped multipart sends, h:/v:/o: field mapping, and full field support.

The Mailgun adapter calls the Mailgun Messages API with plain fetch. It is the one adapter that posts multipart form data instead of JSON, using Mailgun's prefixed field names: h: for headers, v: for metadata variables, o: for sending options. Every send is scoped to the domain you configure.

Mailgun logo
Mailgun
@opencoredev/email-sdk/mailgun
OfficialNot API testedRequest tested
Open website

Configure

Grab an API key from the Mailgun dashboard and note the sending domain it belongs to. Both are required.

lib/email.ts
import { createEmailClient } from "@opencoredev/email-sdk";
import { mailgun } from "@opencoredev/email-sdk/mailgun";

export const email = createEmailClient({
  adapters: [
    mailgun({
      apiKey: process.env.MAILGUN_API_KEY!,
      domain: process.env.MAILGUN_DOMAIN!,
    }),
  ],
});

Prop

Type

EU domains need the EU base URL

Domains created in Mailgun's EU region only respond on baseUrl: "https://api.eu.mailgun.net". Against the default US origin they fail with a 404.

Send

Mailgun maps every common field: cc, bcc, replyTo, headers (as h:Name), attachments (multipart attachment/inline parts by disposition), tags (values as o:tag), and metadata (as v:key variables, surfaced later in webhooks and logs). sendAt becomes a native scheduled send via o:deliverytime (RFC 2822); Mailgun accepts up to 3 days out on most plans, 7 with extended storage.

const result = await email.send({
  from: "Acme <hello@mg.acme.com>",
  to: "user@example.com",
  replyTo: "support@acme.com",
  subject: "Your receipt from Acme",
  html: "<p>Thanks for your order.</p>",
  metadata: { orderId: "ord_123" },
  tags: [{ name: "type", value: "receipt" }],
});

console.log(result.id); // Mailgun message id, also exposed as result.messageId

The response id is Mailgun's queued message id from the response body. Match it against delivery events in webhooks or the Mailgun logs.

Batch sends with recipient variables

Pass recipientVariables and Mailgun personalizes every recipient in a single call through its native recipient-variables field (up to 1000 recipients). Mailgun substitutes %recipient.key% tokens in subject, html, and text on its side:

await email.send({
  from: "Acme <hello@mg.acme.com>",
  to: ["ada@example.com", "linus@example.com"],
  subject: "Hi %recipient.name%",
  html: '<a href="https://acme.com/unsub?id=%recipient.id%">Unsubscribe</a>',
  recipientVariables: {
    "ada@example.com": { name: "Ada", id: "u_1" },
    "linus@example.com": { name: "Linus", id: "u_2" },
  },
});

Verify from the CLI

MAILGUN_API_KEY="key-..." MAILGUN_DOMAIN="mg.acme.com" \
  npx email-sdk doctor --adapter mailgun
MAILGUN_API_KEY="key-..." npx email-sdk send \
  --adapter mailgun \
  --domain mg.acme.com \
  --from "Acme <hello@mg.acme.com>" \
  --to user@example.com \
  --subject "Mailgun smoke test" \
  --text "It works" \
  --dry-run

Credentials also work as flags: --api-key, --domain, and --base-url (or MAILGUN_BASE_URL) override the environment. Drop --dry-run for one real send to prove the domain's DNS records and the account are actually ready.

On this page