Email SDK
Adapters

Mailgun

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

Capabilities

Repeated headersIdempotencySchedulingPersonalized
YesnoneYesnative

These values come from the adapter's exported capabilities declaration. The field support matrix covers normalized message fields.

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
Provider docs

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

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

Personalized sends

Mailgun implements native sendPersonalized, so all recipients and variables travel in one request through Mailgun's recipient-variables field.

await email.sendPersonalized({
  message: {
    from: "Acme <hello@mg.acme.com>",
    subject: "Hi %recipient.name%",
    html: '<a href="https://acme.com/unsub?id=%recipient.id%">Unsubscribe</a>',
  },
  recipients: [
    { to: "ada@example.com", variables: { name: "Ada", id: "u_1" } },
    { to: "linus@example.com", variables: { 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.

Compare Mailgun with other providers

Side-by-side message-field support from the SDK capability matrix, with working code for both adapters:

On this page