# Moving mail and DNS without losing messages

> A mail cutover rarely fails with an error. Cached MX records, two SPF records, deleted keys and an early strict policy lose mail while every screen says done.

Published 2026-10-28 · https://aiskills402.com/blog/email-dns-cutover-lost-mail

Moving a domain's mail and DNS fails quietly. Nothing throws an error, the dashboard shows green, and a few days later someone mentions that a message they were waiting for never came. The usual causes are not exotic: a resolver still holding the old routing, two sender policies where only one is allowed, a key deleted before anyone copied it, a strict policy published before anyone knew who sends mail as the domain, and a test that proves less than it seems to. Below, each cause comes in the order a cutover meets it, paired with a check that is able to fail. The examples use Cloudflare's mail features, but the DNS and authentication parts apply to any provider.

## Before touching anything: write the records down

The first step is dull and it is the one that cannot be redone later. Copy each record of the zone into a file, with its name, type, value and lifetime, before the first deletion. Most values can be re-entered from the provider's instructions, so the file mainly serves as a way back. The exception is a signing key. A DKIM record in DNS holds only the public half; the private half lives on the old mail server. If the server is switched off and the record deleted, there is nothing to restore, and every message that was signed with that key stops verifying the moment the record disappears.

Two habits follow from this. Keep the file outside the zone you are editing, so a bad change cannot take it away. And leave alone any record whose purpose you cannot name. A verification token or a certificate challenge looks like clutter, but removing it breaks something that nobody remembers owning. General practice, not from a vendor page: delete in a second pass, a day or more after the switch, when you have seen the new setup carry real mail.

## Why the first test mail proves nothing

Every DNS record carries a lifetime. The original DNS specification calls it the time a resolver may keep a record before asking its authoritative source again ([RFC 1035](https://www.rfc-editor.org/rfc/rfc1035)). That interval is the reason a cutover is not instant. A sending server that looked up your mail exchanger an hour before the change may keep delivering to the old host until its cached answer expires, and the sender, not you, decides when that is.

Cloudflare's own documentation allows a full day for a DNS change to spread worldwide, while domains on its DNS usually settle within 5 to 15 minutes ([Cloudflare, DNS records](https://developers.cloudflare.com/email-routing/setup/email-routing-dns-records/)). Read the wide end of that range as the safe planning figure, because the senders you care about are not on your DNS.

The practical consequence is about interpretation. One test message sent minutes after the change, and not arriving, tells you almost nothing: the setup may be wrong, or that sender may simply still be using its cache. The reverse is also true, since a message that arrives may have gone through the old path. If you can, shorten the lifetime of the old mail records a day or two ahead of the move. Then run the test again once the earlier, longer lifetime is over, using a sender that never wrote to this domain. Cloudflare's setup page adds one more detail: send the test from an account other than the destination, because some providers drop mail that appears to come from the very mailbox it is going to ([Cloudflare, get started](https://developers.cloudflare.com/email-routing/get-started/enable-email-routing/)).

## Routing rules and destination addresses

A forwarding rule pairs an address pattern with one destination, which is either a verified mailbox or a Worker with a mail handler. Cloudflare states that you must add and verify a destination before you can create a rule, and that a rule pointing at an unverified address stays disabled ([Cloudflare, destination addresses](https://developers.cloudflare.com/email-routing/setup/email-routing-addresses/)). Deleting a destination also disables every rule that used it. That produces a quiet failure: you clean up an old address, and a working rule turns itself off.

Two things to check on paper before the cutover. First, that each pattern appears once. If you want one address to reach several people, one way is to send the address to a Worker and let its code forward to each person, instead of stacking several rules on the same pattern (general practice; the cited pages do not say how overlapping rules behave, so test it). Second, that every destination shows as verified in the list, not merely typed in.

## Sending from code: who can actually receive

If your site or monitoring sends mail from a Worker, the rules differ from routing. Cloudflare documents that you can send directly to verified destination addresses through its REST API or through the Workers binding, free of charge even when only Email Routing is configured, and that such sends do not count toward the monthly quota or the daily sending limits ([Cloudflare, destination addresses](https://developers.cloudflare.com/email-routing/setup/email-routing-addresses/)). That is a sentence about notifications to yourself. The same page mentions a monthly quota and daily limits for other sends, which points to a separate Email Sending feature with its own domain onboarding; we did not read its limits or pricing for this guide. So a contact form that promises the visitor a receipt, built on the Routing-only path, will see the visitor's address refused. Decide on purpose: send the alert to your own verified inbox and show the visitor an on-screen success message, or check what Email Sending allows for your plan, or use a provider that sends to arbitrary recipients.

Two code habits matter just as much. When recipients are sent in one `Promise.all`, the first refusal makes the combined promise reject at once. The other sends were already started, but your code sees a single error and no per-address result, so it cannot tell who got the message. Prefer `Promise.allSettled`, or a catch per address, attempt every recipient, and call it a failure only if no one was reached. And put a test or staging marker in the subject as plain words at the start, not in square brackets: mail clients that group conversations by subject often strip a leading bracketed tag, so the staging and production alerts can merge into one thread. That grouping behaviour is general observation, not something a vendor page promises, so check your own client.

## SPF: exactly one record per name

A domain must not publish more than one record that a check could select. The Sender Policy Framework specification forbids it, and its selection rules say what happens: after discarding TXT records that do not begin with the exact version marker, if more than one remains, the result is a permanent error ([RFC 7208, section 3.2 and 4.5](https://www.rfc-editor.org/rfc/rfc7208)). In plain terms, every check fails.

This bites during a cutover because the old host left its record and the new provider's instructions say "add this TXT record". Now there are two. Cloudflare's documentation tells you to merge an existing record rather than add a second one, and it warns that a policy is limited to 10 DNS lookups in total, so a merge that includes too many other providers may need flattening ([Cloudflare, DNS records](https://developers.cloudflare.com/email-routing/setup/email-routing-dns-records/)). The check is simple and can fail: query the TXT records at the root name and count those that start with the version marker. The count must be one.

## DMARC: watch first, enforce later

A DMARC record tells receivers what to do with mail that fails authentication, and it asks for reports. The specification defines three policy values: none, which requests no particular action; quarantine, which asks receivers to treat failing mail as suspicious; and reject, which asks them to refuse it ([RFC 7489](https://www.rfc-editor.org/rfc/rfc7489)). Its own deployment example for a new owner starts with the none policy plus a reporting address, precisely so that the owner can learn which sources send as the domain and whether they authenticate, without changing how any message is handled ([RFC 7489, appendix B](https://www.rfc-editor.org/rfc/rfc7489)).

Publishing quarantine or reject on day one inverts that order. A forgotten newsletter tool, a billing system or an old server sending as your domain is then blocked before you knew it existed. Start with none, read the reports, fix the senders you find, and move up one step at a time. The same logic applies to the ending of an SPF record: a soft failure marker is the cautious choice until you know every system that sends, and the hard one belongs last.

## A cutover order that holds up

1. Back up the whole zone into a separate file, lifetimes included.
2. Shorten the lifetime of the old mail records ahead of the move.
3. Remove the old provider's mail exchangers only where the new routing demands it.
4. Add the new records, folding sender entries into one policy.
5. Confirm each destination is verified and each pattern appears once.
6. Publish the monitoring-only policy with a reporting address.
7. Wait out the old lifetime, send from a fresh account, and judge by what reaches the mailbox itself. A routing log is a hint, and an empty log does not prove that nothing was delivered.
8. Delete leftovers in a later pass.

## When this does not apply

The order above assumes one domain, a few mailboxes and a provider that leaves DNS in your hands. A larger organisation with several mail systems, shared domains and compliance duties needs a staged migration plan that no short list replaces. Vendor screens, plan limits and beta features change often, and the Cloudflare pages cited here were read on 28 October 2026, so check them again before relying on a name or a limit. We ran nothing new here; it draws on public documentation and standards, with common practice labelled as such, and the examples illustrate rather than measure. Some providers also cache aggressively or ignore lifetimes, so the waiting time in your case can exceed anything a document promises.

Mail settings share a trait with browser security settings: both fail without an error message, which is why our piece on [CORS configuration mistakes](/blog/cors-config-mistakes) reads like a sibling of this one.

## Checking a plan before the cutover

When a second reader helps, paste the plan, script or configuration into our [email-dns-cutover-review](https://aiskills402.com/skills/email-dns-cutover-review) skill before the switch. Among other checks, it looks for the quiet failures described above: mail to addresses a Worker cannot reach, a batch that gives up on one bad recipient, two forwarding rules on one address, old exchangers that block the move, doubled sender policies, deletions with no saved copy, bracketed environment tags, a routing log used as evidence, and a strict policy published before any report. For every hit you get the place, the reason and the change, then one verdict line; a sound plan simply gets a statement that nothing was found. It judges the pasted material alone and never proposes a new mail architecture. The store itself is at https://aiskills402.com.
