Skip to content
Docs

Guide

Move from Mailgun to Emailit. Map domains, keys and routes, convert form-encoded API calls to JSON, and move SMTP, webhooks, suppressions and templates.

Updated Oct 1, 2026

This guide maps Mailgun concepts, API calls, webhooks, suppressions and templates to their Emailit equivalents. Read Migrate to Emailit first for the overall order and how to run both providers in parallel.

Concepts

Mailgun Emailit
Account and subaccounts Account and workspaces. Each workspace has its own domains, keys, members and credits.
Domain, with its own API path /v3/<domain>/… Sending domain. There’s one send endpoint, and Emailit picks the domain from the from address.
Private API key Full Access API key
Domain sending key Sending Only API key restricted to one domain
SMTP credentials per domain Your API key, used as the SMTP password
Templates per domain, with versions Templates per workspace, with an alias and versions
Webhooks per domain Webhooks per workspace
Routes Inbound email with the email.received webhook, or the Forward received email automation
Suppressions per domain: bounces, unsubscribes, complaints One suppression list per workspace
Mailing lists Audiences
Tags and custom variables meta
Logs and events Email APIEmails, Email APIEvents and Email APILogs
Email validation Email verification

Update your API calls

Mailgun’s POST /v3/<domain>/messages takes form fields with basic authentication. Emailit’s POST /v2/emails takes JSON with a bearer token:

Before: Mailgun
curl -s --user "api:$MAILGUN_API_KEY" \
  https://api.mailgun.net/v3/mg.acme.com/messages \
  -F from='Acme <hello@mg.acme.com>' \
  -F to='ada@example.com' \
  -F subject='Your receipt' \
  -F text='Thanks for your order.' \
  --form-string html='<p>Thanks for your order.</p>'
After: Emailit
curl https://api.emailit.com/v2/emails \
  -H "Authorization: Bearer $EMAILIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Acme <hello@mg.acme.com>",
    "to": "ada@example.com",
    "subject": "Your receipt",
    "text": "Thanks for your order.",
    "html": "<p>Thanks for your order.</p>"
  }'
Mailgun Emailit
Basic auth api:<key> Authorization: Bearer secret_…
multipart/form-data fields A JSON body
from, subject, text, html The same names
to, cc, bcc (repeated or comma-separated) to, cc, bcc as a string or an array of up to 50 each
h:Reply-To reply_to
h:X-My-Header headers: { "X-My-Header": "…" }
v:order-id, h:X-Mailgun-Variables meta: { "order-id": "…" }, returned in webhook events
template and t:variables template (an ID or alias) and variables
attachment, inline (file uploads) attachments[] with base64 content or a url, plus content_type. Add content_id for inline images.
o:deliverytime (RFC 2822 date) scheduled_at (ISO 8601, Unix time or plain English)
o:tracking, o:tracking-opens, o:tracking-clicks tracking: { "loads": true, "clicks": true }
o:tag meta
o:testmode Not available
recipient-variables (batch sending) Not available. Send one request per recipient with its own variables.
Response { "id": "<…>", "message": "Queued. Thank you." } 200 with id (em_…), message_id, status: "accepted" and ids per recipient

If you sent from a subdomain such as mg.acme.com, add that exact subdomain in Emailit. Subdomains are verified separately from the parent domain. Mailgun’s EU and US API hosts both map to the single Emailit endpoint. See Send an email.

Switch SMTP settings

Setting Mailgun Emailit
Host smtp.mailgun.org, or the EU host smtp.emailit.com
Port 587, 465, 2525 or 25 587 (STARTTLS), 465 (TLS), 2525, 2587 or 25
Username Your SMTP login, such as postmaster@mg.acme.com emailit
Password Your SMTP password Your Emailit API key

Emailit doesn’t read X-Mailgun-* headers. Remove them, and set tracking on the domain instead. See SMTP settings.

Map webhook events

Mailgun event Emailit event
accepted email.accepted (API only)
delivered email.delivered
failed with severity temporary email.attempted
failed with severity permanent email.bounced
opened email.loaded
clicked email.clicked
complained email.complained
unsubscribed email.unsubscribed, for campaign email only
Route that forwards to a URL email.received, then fetch the content with GET /emails/{id}

The request format changes:

  • Mailgun posts one event per request, with the details in event-data. Emailit posts a JSON array of up to 100 events. Loop over the array.
  • The event name is in type, and the email is in data.object. Use data.object.id, the em_ ID from the send response, to match events to messages. Your meta values are in data.object.meta.
  • Mailgun signs a timestamp and token inside the body. Emailit signs the whole raw body: verify X-Emailit-Signature against X-Emailit-Timestamp and your whsec_ secret. See Request signature.
JavaScript
for (const event of req.body) {
  const email = event.data.object;
  if (event.type === 'email.bounced') markBounced(email.to, email.id);
  if (event.type === 'email.complained') unsubscribe(email.to);
}

Move suppressions

  1. Export the Bounces, Complaints and Unsubscribes lists of every Mailgun domain you send from, from the control panel or with the suppressions API (/v3/<domain>/bounces, /complaints and /unsubscribes).

  2. Build one CSV with the columns email,type,reason:

    CSV
    email,type,reason
    old-address@example.com,recipient,mailgun bounce
    angry@example.com,recipient,mailgun complaint

    Use the type recipient for addresses that must never receive email. It blocks API, SMTP and campaign sends. The types bounce, complaint and unsubscribe only stop campaigns.

  3. In Email APISuppressions, select Import and upload the file. Each file can have up to 10,000 rows and can be at most 8 MB. Duplicates are skipped.

Emailit has one suppression list per workspace, so addresses from all your Mailgun domains go into the same list. There’s no allowlist. See Manage suppressions.

Move templates

Copy each template’s HTML from Mailgun, then import it in Email MarketingTemplates or create it with the Templates API. Give it an alias and send it with "template": "<alias>" and variables.

Mailgun templates use Handlebars. Temple covers the common parts:

Mailgun (Handlebars) Emailit (Temple)
{{first_name}} {{first_name}}
{{{html_block}}} {{html_block}}. Temple never escapes HTML, so escape user input yourself.
{{#if plan}}…{{else}}…{{/if}} The same
{{#unless plan}}…{{/unless}} {{#if plan}}{{else}}…{{/if}}
{{#each items}}…{{/each}} Not supported. Render the list in your code and pass it as one variable.
{{#equal plan "pro"}}…{{/equal}} Not supported. Pass a boolean such as is_pro and use {{#if is_pro}}.
No built-in default {{first_name|"there"}} adds a fallback

See Temple and Import and export templates.

Change DNS

Add each domain in Email APIDomains and publish the Emailit records. They use their own names (emailit._domainkey, emailit.<domain>, and optionally go and inbound), so they don’t conflict with Mailgun’s DKIM record or its email.<domain> tracking CNAME. You don’t need to change your root SPF record for Emailit. Keep your DMARC record. See DNS records.

After the cutover, remove Mailgun’s DKIM and tracking records, and remove include:mailgun.org from your SPF record. If you receive mail through Mailgun routes, keep its MX records until you’ve moved that traffic to Emailit inbound, which receives on a subdomain such as inbound.acme.com.

Next steps

Was this page helpful?

Thanks for the feedback.

Thanks, we read every message.