Zero-Downtime Mail Provider Migration: Dual Delivery, MX Cutover, and TTL Planning
Flipping an MX record is one DNS edit. Not losing mail during the 30-90 minutes where DNS caches disagree is the hard part. Here is how to run two providers in parallel and drain the old one to zero without a single 5xx bounce.
EvilMail TeamJuly 27, 202612 min read
The failure mode nobody plans for
Here is the migration that goes wrong, and it goes wrong the same way every time.
You changed the MX record at 14:00. You provisioned the new mailboxes that morning. At 14:05 you disabled the old provider's accounts, because you've moved off it. And now mail is bouncing.
The reason is simple and brutal: DNS convergence is not atomic. At 14:05, roughly half your senders still resolve the old MX because the old TTL hasn't expired in their resolver cache; the other half already see the new one. The senders hitting the old provider deliver to mailboxes you just turned off, so the old provider answers 550 5.1.1 <user> unknown and hard-bounces the message. That mail is gone. It doesn't retry and it doesn't reroute — a 5xx is a permanent rejection, so the sending MTA drops the message and generates a bounce telling the sender your address doesn't exist.
Worse, some of those senders keep hitting the old MX for hours. A sending MTA does not re-query DNS on a whim; it respects the TTL of the record it cached, and a meaningful fraction of MTAs cache more aggressively than they should or pin the resolved host for the life of a queue run. You do not control when the last straggler switches over.
So drop the word "cutover" from your vocabulary. You do not switch a mail domain from provider A to provider B. You stand both up in parallel, point DNS at the new one, and let the old one
Zero-Downtime Mail Provider Migration Guide (MX, TTL, Dual Delivery) — EvilMail Blog
drain asymptotically
to zero over the next 24-72 hours. Success is defined negatively: zero 5xx bounces, zero silent drops, zero SPF/DKIM/DMARC failures during the overlap. And it is won or lost in the prep week, not on cutover day.
Two strategies: dual-MX vs. dual-delivery relay
There are exactly two honest ways to build the overlap, and they trade off differently.
Equal-priority dual MX. You list the old and new provider at the same MX preference. Per RFC 5321, senders randomize across equal-preference hosts, so inbound sprays across both roughly evenly.
dns
example.com. 300 IN MX 10 mx.oldprovider.net.
example.com. 300 IN MX 10 mx.newprovider.net.
This is easy to reason about, but it demands that both mailstores are authoritative at the same time. A message can land on either side, so every mailbox has to exist on both, and you need continuous IMAP sync reconciling the two copies. In practice the sync lag means a user can receive a message on the old side and not see it on the new side for minutes.
Dual-delivery via relay. You keep the old provider as the primary MX and configure it to *also* forward a copy of every inbound message to the new provider. The new mailstore fills up before you touch the MX record at all. When you finally cut the MX, the new side is already warm and populated. The cost is duplicate-message risk: if you forward-all and then also serve mail directly, users can see doubles.
For a domain with live users, dual-delivery-then-cutover is the default. You validate the new provider under real production mail flow for days before DNS ever changes, and the MX flip becomes anticlimactic because the target already has every message. On Postfix the blunt version is one line in main.cf:
But always_bcc copies *everything*, outbound included. For anything real, use recipient_bcc_maps or a transport_maps entry pointing the copy at the new provider's smarthost, so you can scope it per-recipient and exclude noise. Reserve dual-MX for greenfield or low-volume domains where reconciliation is cheap.
TTL planning: the week before you touch anything
This is the part the "lower TTL, change MX, done" advice gets fatally wrong. Lowering a TTL is not instantaneous. The new low value only becomes globally effective after the previous high value has fully expired everywhere. If your MX TTL is 3600 and you drop it to 300 right now, resolvers that cached the record 10 minutes ago keep serving it under the old TTL for another 50 minutes. You cannot retroactively shorten a record already sitting in someone's cache.
That is why TTL lowering is a T-7 job, not a cutover-day job.
T-8 days — audit current TTLs. dig MX example.com and read the second column. Typical starting points: MX 3600, SPF/DMARC TXT often 86400, DKIM 3600.
T-7 days — lower MX and SPF TTL to 300. Then *wait a full old-TTL period* (up to 24h if SPF was on 86400) for the low value to propagate everywhere.
T-1 day — verify the low TTL is globally live by querying multiple independent resolvers.
If all of them report TTL 300 (and counting down consistently on repeat queries), you are converged. If one still shows 3600, wait — you are not done propagating.
One thing TTL doesn't fix: relay-side caching. Postfix re-resolves per delivery and honors TTL, but plenty of MTAs in the wild cache MX aggressively or hold it for the duration of a queue run. Budget for stragglers up to 24-48h regardless of what your TTL says. TTL sets the floor on convergence speed, not the ceiling.
Provisioning the target before cutover
Nothing moves until the new provider is a complete, verified replica of the old one's *config surface*:
Every mailbox, alias, and distribution list exists and accepts mail.
Catch-all is replicated. This is the silent killer. If the old side had a catch-all and the new side doesn't, every message to an address that isn't explicitly provisioned — sales+q3@, typo'd names, dormant aliases — silently drops or bounces the moment DNS points at the new provider. Recreate it first.
SPF authorization for the new provider is in place (covered below).
DKIM keypair is generated and the public key is published and resolving before cutover.
DMARC is left in alignment-safe monitoring.
Then seed history with imapsync so users don't open an empty mailbox after cutover:
Run this once as a bulk seed a day or two before cutover. The Message-Id header lets you run it again later as a delta pass without duplicating anything already copied.
SPF, DKIM, and DMARC during the overlap
This is where deliverability regressions actually come from, and the rule is the same for all three: add the new provider's authorization before cutover, remove the old provider's authorization last.
SPF must list both providers' mechanisms for the *entire* drain window. Mail is still leaving the old relay for hours after you flip MX — pull the old include too early and that in-flight mail fails SPF and lands in spam or gets rejected.
Watch the 10 DNS-lookup limit: two provider includes can each expand into several lookups, and blowing past 10 causes a permerror that fails SPF for everyone. Flatten or trim other mechanisms if you're close.
DKIM is the easy one — selectors coexist. Publish the new provider's selector as a separate TXT record and leave the old one alone. Verify the new one resolves before cutover:
DMARC — do not tighten it mid-migration. Keep it at p=none (or wherever it already sits) with reporting on. Moving to p=reject during the overlap is how you reject your own legitimately-signed-but-mid-transition mail.
By now the risky work is done. The MX flip is the least eventful step. Run it in this order:
1.Confirm the low TTL is globally live — dig MX against 8.8.8.8, 1.1.1.1, 9.9.9.9, and your authoritative NS. All must read 300.
2.Confirm the target is fully provisioned and its DKIM selector resolves.
3.Change the MX record. One edit. If you did dual-delivery, the new side is already full.
4.Start watching both providers' inbound logs immediately. You want to see new-side volume climb and old-side volume decay in real time.
5.Keep the old provider accepting and storing. Do not disable a single old mailbox. This is the entire point.
6.Send canaries from Gmail, Outlook, and Yahoo. Confirm they land on the new side and pass SPF/DKIM/DMARC — read the Authentication-Results header.
7.Run a final imapsync delta pass to sweep any mail that hit the old side during the drain into the new mailboxes.
Because the MX TTL is 300, a botched cutover is reversible in about five minutes — revert the MX and the world drifts back to the old provider. That safety net is exactly why you do not raise the TTL back up yet.
Draining and decommissioning
Watch the old provider's inbound volume decay. Realistically, 90%+ of senders switch within the 300s TTL window. The rest are retry queues and aggressively-caching MTAs producing a long tail that runs 24-72h. Do not touch anything until that tail is flat at near-zero.
Only after 72 hours of near-zero old-side traffic, in this exact order:
Raise MX and SPF TTLs back to 3600.
Remove the old provider's SPF include.
Remove the old DKIM selector — but only after confirming no mail still in flight was signed with it.
Deprovision the old mailboxes. This is the last step, never the first.
The removal ordering is the whole discipline of the migration. Everything you add, you add first; everything you remove, you remove last.
Checklist
Pre (T-8 to T-1)
Audit MX/SPF/DKIM/DMARC TTLs; lower MX + SPF to 300 at T-7 and wait a full old-TTL period.
Provision all mailboxes, aliases, and catch-all on the new provider.
Publish and verify the new DKIM selector; add the new provider's SPF include.
Stand up a dual-delivery relay (recipient_bcc_maps) or plan dual-MX with IMAP sync.
Run the bulk imapsync seed.
Cutover (T-0)
Verify TTL 300 across 8.8.8.8 / 1.1.1.1 / 9.9.9.9 / authoritative NS.
Change MX. Watch both inbound logs.
Keep the old side accepting — disable nothing.
Canary tests from Gmail/Outlook/Yahoo; check Authentication-Results.
imapsync delta pass.
Post (T+72h)
Confirm old-side traffic is flat at zero for 72h.
Raise TTLs, remove the old SPF include, remove the old DKIM selector, then deprovision the old mailboxes.