Mailgun Routes match incoming mail with expression rules and forward it on. MailKite gives you address-level routing in a dashboard, a structured JSON payload, HMAC signatures, and built-in retries — without hand-writing match(...) expressions.
Mailgun is a mature sending platform and Routes are flexible once you know the expression syntax. The cost is the plumbing: regex/filter rules, store-and-notify actions, and stitching retries yourself.
Point an address (or a whole domain, or catch-all) at a webhook in the dashboard. No match_recipient(...) / match_header(...) rule language to learn or maintain.
Parsed text, HTML, threadId, and auth results arrive as JSON — you don't choose between 'forward the raw MIME' and 'store and notify'.
Failed deliveries retry automatically and are replayable in one click, instead of being wired up per-route.
No per-domain cost and no hard cutoff when you cross your quota — run every product from one account.
| MailKite | Mailgun Routes | |
|---|---|---|
| Routing model | Address / domain / catch-all in UI | Expression-rule engine (regex) |
| Inbound payload | Parsed JSON | Raw MIME or form fields per action |
| Automatic retries | Yes, built in | Configurable per route |
| One-click replay | Yes (paid) | Limited |
| HMAC-signed webhooks | Yes, every plan | Signature verification available |
| Per-domain fees | None — unlimited | Plan/volume tiered |
| Send from same account | Yes — shared quota | Yes — sending is the core product |
Competitor capabilities change — we re-audit these tables regularly. Spot something out of date? Tell us and we'll fix it.
Point support@myapp.ai at a URL in the dashboard once. Every message arrives parsed, signed, and retried — no expression rules, no store-and-notify wiring.
# You author and maintain rules like:
match_recipient("support@myapp\\.ai")
→ forward("https://your-app/webhook")
match_header("subject", ".*invoice.*")
→ store(notify="https://your-app/webhook")
# then parse the delivered MIME or form fields
# and wire up your own retry handling. POST /your-webhook Content-Type: application/json
x-mailkite-signature: t=…,v1=… (HMAC-SHA256 — verify locally)
{
"id": "msg_2Hk9…",
"type": "email.received",
"from": { "address": "ada@example.com" },
"to": [{ "address": "support@myapp.ai" }],
"subject": "Re: invoice #1042",
"text": "Looks good — approved!",
"html": "<p>Looks good — approved!</p>",
"threadId": "<a1b2c3@mail.example.com>",
"auth": { "spf": "pass", "dkim": "pass", "dmarc": "pass", "spam": "ham" },
"attachments": [
{ "filename": "po.pdf", "contentType": "application/pdf",
"size": 18213, "url": "https://api.mailkite.dev/att/2Hk9…/0?sig=…" }
]
} MailKite's routing lives in a dashboard, not a rule DSL — and every domain and alias is free.
Add our MX records or start on a managed subdomain. Set up catch-all with one toggle if you want everything.
Translate each match rule into an address or catch-all in the dashboard — no expression syntax to port.
Consume the parsed JSON payload, verify the HMAC signature, and rely on built-in retries instead of custom logic.
Most rules reduce to 'this address (or the whole domain) goes to this webhook.' Content-based branching moves into your own handler, where it's easier to test than an expression DSL. Catch-all covers the wildcard cases.
Yes — deliveries retry automatically with backoff, and you can replay any message in one click from the dashboard on paid plans.
Absolutely — many teams start by moving just inbound. You can also consolidate sending onto MailKite later, since it shares the same quota.
The honest Mailgun Routes alternative →
The long-form take on our blog — receipts, runnable code, and where we won't overclaim.
Point a domain, drop in a webhook URL, receive your first email. Unlimited domains, no credit card.