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.
@opencoredev/email-sdk/mailgunConfigure
Grab an API key from the Mailgun dashboard and note the sending domain it belongs to. Both are required.
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.messageIdThe 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 mailgunMAILGUN_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-runCredentials 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.
