Why teams leave Mailgun
- Aging dashboard UX. Mailgun's UI is a relic. Relayly's is a modern SPA with sub-second navigation.
- No real free tier. Mailgun's 5-day trial is a tease. Relayly Free is a permanent 3,000 sends/month.
- Pricing creeps. Foundation 50k → $35/mo. Add a dedicated IP → $59/mo extra. Total $94/mo for what costs $25/mo at Relayly.
- Reputation is opaque. Mailgun gives you logs and asks you to figure it out. Relayly exposes per-(IP × domain × provider) reputation in the dashboard.
- RFC 8058 not auto-configured. Gmail/Yahoo bulk-sender requirements need manual config. Relayly enables it on every send by default.
What carries across
| Mailgun concept | Relayly equivalent | Auto-migrates? |
|---|---|---|
| Mailing Lists | lists | Yes — full export with member counts and access levels. |
| Members (subscribers) | list_members | Yes — including custom variables. |
| Suppressions (bounces, unsubs, complaints) | Suppressions | Yes — full per-domain export. |
| Domains + DKIM keys | domains | Manual re-verify (DKIM keys can't cross providers). |
| Routes (inbound) | Inbound routes | Pattern-matching translates 1:1. Action handlers may need adjustment. |
| Templates (Mustache) | Liquid templates | Auto-translation handles the common cases. Complex helpers flagged. |
| Stats / analytics | Read-only archive | Optional. Last 30/60/90 days as analytics-only metadata. |
| Webhooks | Webhooks | Same event names; signature header changes. Adapter is 4 lines. |
The codebase swap
API shapes are similar enough that find/replace handles most call sites. The key differences:
// Mailgun.js import formData from "form-data"; import Mailgun from "mailgun.js"; const mg = new Mailgun(formData).client({ username: "api", key: process.env.MAILGUN_API_KEY, }); await mg.messages.create("yourdomain.com", { from: "hello@yourdomain.com", to: ["user@example.com"], subject: "Welcome", html: "<p>Hi</p>", });
// Relayly Node SDK import Relayly from "@relayly/node"; const r = new Relayly({ apiKey: process.env.RELAYLY_API_KEY }); await r.email.send({ from: { email: "hello@yourdomain.com" }, to: [{ email: "user@example.com" }], subject: "Welcome", html: "<p>Hi</p>", });
Inbound routes — direct port
Mailgun routes are a strength of the product, and Relayly inbound routes preserve the model directly. Pattern matching uses similar regex syntax (we expose match_recipient(".*@inbound.example.com"), identical to Mailgun's match_recipient).
The action side is where you'll spend 5-10 minutes per route. Mailgun's forward(...) action becomes a Relayly forwarding rule. store() + later retrieval becomes a webhook to your endpoint with the parsed mail.
The cutover playbook
- Run the export script. It's read-only against Mailgun and re-runnable.
- Review the manifest. Anything flagged as "needs review" gets a copy-paste remediation snippet.
- Apply to Relayly. Idempotent SQL + REST imports. Re-run safely.
- Dual-send for a week. Compare inbox rates at Gmail / O365 / Yahoo through the dashboard.
- Flip the API key. Search-and-replace your codebase, deploy, monitor 24h.
- Pause Mailgun. Don't delete the account — keep it for 30 days as an unsubscribe-list reciprocity reference.
- After 30 quiet days, terminate Mailgun. You're done.
Pricing-side check
| Volume | Mailgun | Relayly | Annual delta |
|---|---|---|---|
| 5k sends/mo, no IP | $0 (Foundation Free, sunset) | $0 (Free, permanent) | Free vs trial |
| 50k sends/mo, no IP | $35/mo | $25/mo | + $120/year |
| 50k sends/mo, with IP | $94/mo | $25/mo (IP included) | + $828/year |
| 500k sends/mo, with IP | $235/mo | $100/mo | + $1,620/year |
Mailgun pricing as of 2026-05 from mailgun.com/pricing. Mailgun's free Foundation tier was sunset in 2024; trial-only now.
Start the migration
Sign up free, run the export script, dual-send for a week. We'll spot you 10,000 verification sends.
Start free