Mailgun → Resend
Verify a Resend domain, point the app at the new API or SMTP host, and decide what to do with Mailgun suppressions. Resend suppressions are team-wide. A Mailgun unsubscribe on one domain is not the same thing.
Should you make this move?
Both platforms have a case. Compare what you gain with what you give up before scheduling the cutover.
Mailgun
- Flexible APIs, routing, validation, and regional infrastructure suit engineering-led email systems
- Audience, campaigns, automations, and reporting live in one publishing system
- Domain configuration, event handling, suppression behavior, and account regions require careful operation
- Automation logic, analytics history, and deliverability settings are not very portable
Resend
- A simple API, React Email, and clear domain setup make transactional sending approachable for product teams
- Audience, campaigns, automations, and reporting live in one publishing system
- Message streams, native template languages, and long IP history from older providers do not transfer in
- Automation logic, analytics history, and deliverability settings are not very portable
Resend: A simple API, React Email, and clear domain setup make transactional sending approachable for product teams. This removes a major source-side concern: Domain configuration, event handling, suppression behavior, and account regions require careful operation.
What you lose: Flexible APIs, routing, validation, and regional infrastructure suit engineering-led email systems. What you inherit: Message streams, native template languages, and long IP history from older providers do not transfer in.
Jump to a section
Know the shape of the move.
This timeline assumes
- Transactional product mail, plus optional receiving. Marketing broadcasts are in scope only if you already send them from the same domains.
- The site already documents Mailgun → Postmark and SendGrid → Resend.
- You control DNS for every domain Mailgun sends from.
- Mailgun stays able to send until Resend has handled a representative day of mail.
- You can list bounces, complaints, and unsubscribes per Mailgun domain.
What survives the move.
“Partial” and “manual” are not footnotes. They are work that must be scheduled and verified.
| Item | Outcome | Impact | What happens | Mitigation |
|---|---|---|---|---|
| Sending domains | manual | critical | Resend sends only from a domain you verify. You add each subdomain separately. Resend recommends a subdomain such as updates.example.com so transactional reputation stays away from marketing mail. | Verify the domain and send a test before you change application code. |
| DNS coexistence | manual | critical | SPF can include both providers during the move. DKIM is a new record, not a copy of Mailgun's key. Bill Nalen deleted Mailgun's DNS records after Resend verified the domain on four sites in December 2023. | Add Resend's records first. Remove Mailgun's include only after you stop sending through Mailgun. |
| The mg subdomain | partial | high | Bill Nalen left Mailgun because magic links went out with an mg subdomain, and because the pay-as-you-go plan's route cap blocked more domains. He published a 5-route limit for that plan in 2023. Check your current plan before you treat 5 as today's number. | Send from the domain you want users to see. Count inbound routes separately from outbound domains. |
| API and SMTP | manual | critical | Mailgun's form POST is not Resend's JSON API. Bill pointed NextAuth's EMAIL_SERVER at smtp://resend:[email protected]:587. That was his December 2023 setup. Confirm the current SMTP host in Resend's docs before you copy it. | Change the provider behind one environment variable so rollback is a config change. |
| Templates | manual | high | Mailgun templates do not become React Email components. Resend's Navattic case study says an engineer migrated off Mailgun and converted several templates to React Email and Tailwind in a few pull requests. | Rebuild the templates you still send. Render one of each to a test inbox. |
| Suppressions | partial | critical | Mailgun's complaint API is per sending domain. The Java client docs say bounces, unsubscribes, and complaints are not an account-wide list. Resend suppressions apply to the whole team, on every domain, for transactional and broadcast mail. Origins are bounce, complaint, and manual. | Export each Mailgun domain. Import bounces and complaints. Do not import marketing unsubscribes as Resend suppressions unless you want transactional mail blocked too. |
| Unsubscribe versus suppression | manual | critical | A Mailgun unsubscribe often means one list on one domain. A Resend suppression skips every future send to that address until you remove it. Resend's bulk API adds up to 100 addresses per call, and the dashboard accepts a CSV. | Keep list unsubscribes in your own audience table if Resend should still send password resets. |
| Inbound routes | manual | high | Mailgun routes can parse and forward inbound mail. Resend can receive on a domain you configure, but the route actions do not move. | List every route. Recreate receiving only where a product feature still needs it. |
| Webhooks | manual | high | Mailgun event names are not Resend's. Resend documents suppression.added, suppression.removed, and email.suppressed in the suppression changelog. | Point a new endpoint at Resend and run one bounce and one delivery before cutover. |
| Region and tracking | manual | medium | Resend lets you pick a sending region, open and click tracking, enforced TLS, and a custom return-path per domain. Mailgun's US or EU account does not copy those toggles. | Set region and tracking on purpose. Leave click tracking off for password mail if that matches today's Mailgun domain. |
| Dedicated IPs | lost | high | A warmed Mailgun dedicated IP does not transfer. The new domain starts without that history. | If you depend on a dedicated IP, ask Resend what they offer before you cut volume over in one day. |
| Logs | lost | medium | Historical Mailgun events stay in Mailgun. Resend's log starts when you send through Resend. | Export the suppression lists and any event history you still audit. |
Where each thing goes.
| Source | Destination | Method | Notes |
|---|---|---|---|
| Sending domain | Verified Resend domain | manual | One DNS verification per subdomain. |
| SPF and DKIM | Resend DNS records | manual | Add before you remove Mailgun. |
| From address | From address on that domain | transform | Resend sends as any address on a verified domain. |
| API send | Resend email API | manual | JSON body, not Mailgun form fields. |
| SMTP | Resend SMTP | manual | Confirm the current host. Bill used smtp.resend.com:587. |
| Template | React Email or HTML | manual | Rewrite. |
| Bounce or complaint | Team suppression | transform | Export per Mailgun domain. Import as bounce or complaint where you can, otherwise manual. |
| Unsubscribe | Audience state, not always a suppression | manual | A suppression blocks all mail from the team. |
| Inbound route | Resend receiving | manual | Recreate only the routes you still need. |
| Webhook | Resend webhook | manual | New event names. |
Make the move recoverable.
Create the source-of-truth backup
Keep Mailgun able to send, and keep a copy of every suppression list.
- Export bounces, complaints, and unsubscribes for each sending domain.
- Save route definitions and template source.
- Record the current SPF and DKIM records.
- Send a Resend test from the new domain to a mailbox you control.
- Leave Mailgun's DNS in place until that test passes.
Proof to capture: A suppression file per domain, a DNS snapshot, and a Resend test message id.
Domain verification
Make Resend a legal sender before the app switch.
- Add the domain or subdomain.
- Publish the records Resend shows you.
- Wait until the dashboard marks it verified.
- Send one message to yourself.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
Suppression import
Block known bad addresses without blocking password resets by accident.
- Export each Mailgun domain.
- Import bounces and complaints.
- Hold unsubscribes in a separate file and decide with the product owner.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
The things most likely to hurt.
These are operating limits. Treat every “Stop if” condition as a blocked migration, not a suggestion.
Unsubscribes become global suppressions
A marketing unsubscribe list is uploaded to Resend suppressions.
- Consequence
- Password resets and receipts to those people are skipped.
- Mitigation
- Import bounces and complaints only, unless you have written otherwise.
Stop if: The unsubscribe file was included in the suppression import.
SPF is stripped early
Mailgun's include is removed while Mailgun is still the sender.
- Consequence
- Mail fails SPF and goes to spam, while the API still says delivered.
- Mitigation
- Remove Mailgun DNS only after the app no longer calls Mailgun.
Stop if: Production still has a Mailgun API key and the SPF include is gone.
Both providers send
A worker and a retry queue still use Mailgun.
- Consequence
- Users get two messages, or one from the old mg domain.
- Mitigation
- Switch the provider in one config change and drain the old queue first.
Stop if: A Mailgun log shows a production send after the switch.
Do the work in this order.
- Days 1–3 · inventory
Domains and routes
4–8 hours active2–3 days elapsedDNS access waiting- List domains, from-addresses, templates, routes, and webhooks.
- Separate bounces, complaints, and unsubscribes.
Depends on: Mailgun admin and DNS access
Stop / go checkpointVerify Resend?
Go when: Every sending domain has an owner and a suppression export plan.
Stop when: Inbound mail has no replacement and the product still needs it.
- Days 3–5 · backup
Export suppressions
3–6 hours active1–2 days elapsedAPI pagination waiting- Export each domain's lists.
- Snapshot DNS.
Depends on: Domain list
Stop / go checkpointImport suppressions?
Go when: The three lists are separate files.
Stop when: Unsubscribes are mixed into the bounce file.
- Days 5–12 · pilot
Verify and rewrite
10–24 hours active4–8 days elapsedDNS and template review waiting- Verify the Resend domain.
- Import bounces and complaints.
- Send each template to a test inbox.
Depends on: Suppression files
Stop / go checkpointSwitch the app?
Go when: SPF, DKIM, and a test message pass.
Stop when: A template is still only in Mailgun and production sends it.
- Days 12–15 · cutover
Switch the sender
3–8 hours active1–2 days elapsedQueue drain waiting- Drain Mailgun retries.
- Point the app at Resend.
- Watch both logs.
Depends on: Passed test send
Stop / go checkpointLeave Mailgun idle?
Go when: New mail is only in the Resend log.
Stop when: Mailgun still shows production sends.
- Days 14–20 · observe
One sending day
3–6 hours active3–6 days elapsedBounces and a password reset waiting- Compare bounces with the old rate.
- Keep Mailgun's DNS until you are ready to remove it.
Depends on: App switch
Stop / go checkpointRemove Mailgun DNS?
Go when: A full day of mail looks normal and rollback is still possible.
Stop when: Spam placement or missing mail shows up.
Cut over with a way back.
Cutover
Send production mail through Resend without deleting Mailgun's ability to send.
- Pause producers and drain the Mailgun queue.
- Switch the API key or SMTP URL in one change.
- Send a password reset and one template to a test user.
- Watch Resend and Mailgun logs for the next hour.
- Leave Mailgun DNS in place.
Proof to capture: New messages appear only in Resend. A test message is authenticated. Mailgun is idle, not deleted.
Rollback
Point the app back at Mailgun before its DNS records are removed.
- Switch the environment variable back to Mailgun.
- Confirm SPF still includes Mailgun.
- Send a test through Mailgun.
- Leave the Resend domain verified.
- Note any messages that went out twice.
Proof to capture: Production mail is leaving through Mailgun again and Resend is not receiving new sends.
- Test message fails authentication
- Mailgun still sends after the switch
- Unsubscribes were imported as suppressions
- A template renders blank
- Bounce rate jumps on the first real batch
Prove the migration worked.
Every blocking check must pass. Capture the evidence before cleanup begins.
| Pass | ID | Check | Method | Expected result | Evidence |
|---|---|---|---|---|---|
V-01Blocking | Domain verified | Read Resend's domain status and a received message's Authentication-Results. | The domain is verified and SPF and DKIM pass. | Headers. | |
V-02Blocking | File split | Confirm the imported file has no unsubscribe-only addresses. | Bounces and complaints are in. Unsubscribes are held unless signed. | Import file list. | |
V-03Blocking | API or SMTP | Send one message the way production sends. | Resend accepts it and the inbox receives it. | Message id. | |
V-04Blocking | Each live template | Render every template to a test inbox. | Links, names, and from-addresses match the signed samples. | Template checklist. | |
V-05Blocking | One event | Trigger a delivery and a suppression event. | Your endpoint stores both. | Webhook log. | |
V-06Blocking | Routes you kept | Send a message to each inbound address you still publish. | It arrives in the new receiver, or the address was retired on purpose. | Inbound test. | |
V-07Blocking | Mailgun idle | Read the Mailgun log after the switch. | No new production sends. | Mailgun log. | |
V-08Blocking | DNS still valid | Check that Mailgun's records are still published. | A rollback send would still pass SPF. | DNS snapshot. |
Remove the scaffolding safely.
Safe after: A normal sending day, with Mailgun DNS still in place until you explicitly remove it.
- Archive suppression exports and the DNS snapshot.
- Revoke the temporary Resend key used for the test.
- Keep the Mailgun account through the rollback window.
- Remove Mailgun's SPF include only after that window.
- Delete unused inbound routes.
- Review tracking toggles on the transactional subdomain.
When the plan met reality.
First-hand accounts are preferred. Vendor case studies are labeled, and every note below is an editorial paraphrase—follow the link for the full context.
In December 2023 Bill Nalen replaced Mailgun in the ShipFast template. Magic links showed an mg subdomain, and the pay-as-you-go plan's route cap, which he published as 5, blocked more domains. He set EMAIL_SERVER to an smtp URL on smtp.resend.com port 587 with the API key as the password, verified the domain, and deleted Mailgun's DNS records after Resend's records were in place. He says he did this on four domains in about 15 minutes each. He also wrote that he then had one Resend account per domain, and that Resend only sent mail. Those are his December 2023 notes. Check current domain and receiving docs before you treat them as today's product.
- Confirm the current SMTP host before you copy the EMAIL_SERVER string from this 2023 post.
- Add the new DNS records before you delete Mailgun's. He removed the old records in the same sitting.
- The published reasons were the mg subdomain and a five-route cap on the plan he was using.
- One Resend account per domain is a tradeoff he accepted in 2023, not a rule to copy without checking.
Resend's Navattic page says an engineer completed a migration from Mailgun and converted several templates to React Email and Tailwind in a few pull requests. It says the previous template system meant design compromises and time spent on nested tables. No engineer is named. The page gives no send volume, no DNS steps, and no suppression plan. It is Resend's customer story.
- Budget a template rewrite in the app. The story is about React Email, not a Mailgun template export.
- Do not read DNS or suppression steps into this page. It does not include them.
- This is a vendor case study. The migration detail is a few pull requests for templates.
- The result they describe is easier template changes, not a measured change in delivery.
Verify against the primary material.
Platform behavior changes. Check these sources and the review dates above before executing a production migration.
- Resend: verified domainsAccessed 2026-11-01
- Resend: email suppressionsAccessed 2026-11-01
- Mailgun: complaints APIAccessed 2026-11-01
- Bill Nalen: replacing Mailgun with ResendAccessed 2026-11-01