MX Record Priority: How Email Failover Actually Works
mail.example. 300 IN MX 10 mx-a.example.
mail.example. 300 IN MX 10 mx-b.example.
mail.example. 300 IN MX 20 mx-c.example.
These records create two delivery tiers, not a three-step ranking. The two exchangers at preference 10 are equally preferred. A sending SMTP server chooses between them, and the preference-20 exchanger becomes an alternative if the preference-10 destinations cannot accept the delivery.
Lower MX numbers are preferred. Equal numbers belong to the same tier. The number is not a timeout, a percentage, or a server weight.
Read preference as tiers, not positions
The record set above is easier to reason about when grouped like this:
tier 10: mx-a.example, mx-b.example
tier 20: mx-c.example
RFC 5321 requires lower-numbered MX records to be considered first. When several destinations share a preference and there is no clear reason to favor one, the sender must randomize them to spread load.
That last detail is easy to miss. If both primary hosts have preference 10, DNS record order does not make the first line the permanent winner. An SMTP implementation that always picks the first answer can quietly concentrate traffic on one host.
Equal preference is still not a promise of a precise 50/50 split. Different senders resolve at different times, cache different answers, apply their own reachability knowledge, and make independent choices. MX is mail routing, not a weighted traffic-balancing system.
What happens when the preferred tier fails?
The sender needs a list of usable destination addresses, not just one hostname. An MX target can resolve to more than one IP address, and a domain can publish several MX targets. RFC 5321 says the SMTP client must be able to try and retry the relevant alternatives in order until a delivery attempt succeeds, subject to local limits.
A preference-20 server is therefore a later route, but “later” does not mean “after DNS expires.” The sender can move through available alternatives during its delivery work. If the message still cannot be delivered after a temporary failure, it remains queued for a later SMTP retry.
Permanent recipient rejection is different. If a server answers that one mailbox does not exist, another MX host for the same domain will normally have the same recipient directory. Treating backup MX as a way to evade a clear mailbox rejection is not reliable verification.
TTL changes the cached map, not the failover timer
Each example record has a TTL of 300 seconds. DNS defines TTL as the time a resource record may remain cached before the source should be consulted again.
TTL answers one question: “When should I refresh this DNS answer?” MX preference answers another: “Which destinations should I prefer inside the answer I already have?” SMTP retry policy answers a third: “When should I try a failed delivery again?”
Waiting for TTL expiry before trying the next MX would combine unrelated mechanisms. A sender may try another destination from the cached MX set while that set is still valid. Conversely, lowering TTL before a migration makes record changes visible sooner after caches refresh, but it does not make a broken mail server fail over more quickly by itself.
Can you assign any priority numbers?
Yes. The gaps carry no special meaning. These two configurations express the same preference order:
MX 10 mx-primary.example.
MX 20 mx-backup.example.
MX 1 mx-primary.example.
MX 500 mx-backup.example.
The MX preference field is a 16-bit integer. Senders compare the values. They do not interpret a gap of 10 as a ten-second delay or a tenfold preference.
Use a route plan that preserves equal-priority choices
If you inspect MX data in application code, avoid flattening it into a sorted list that gives equal records a false permanent order. This helper returns explicit tiers and accepts an injectable random function, which makes the same-tier behavior testable:
export function buildMxPlan(records, random = Math.random) {
if (!Array.isArray(records) || records.length === 0) {
throw new Error('No MX records: evaluate implicit-MX fallback separately')
}
const normalized = records.map(({ priority, exchange }) => ({
priority,
exchange: exchange.toLowerCase().replace(/\.$/, '')
}))
const nullRecords = normalized.filter(record => record.exchange === '')
if (nullRecords.length) {
if (normalized.length !== 1 || nullRecords[0].priority !== 0) {
throw new Error('A null MX must be the only MX record and use priority 0')
}
return { kind: 'null_mx', tiers: [] }
}
const groups = new Map()
for (const record of normalized) {
const tier = groups.get(record.priority) || []
tier.push(record.exchange)
groups.set(record.priority, tier)
}
const tiers = [...groups]
.sort(([left], [right]) => left - right)
.map(([priority, exchanges]) => {
const shuffled = [...exchanges]
for (let index = shuffled.length - 1; index > 0; index -= 1) {
const swapWith = Math.floor(random() * (index + 1))
;[shuffled[index], shuffled[swapWith]] =
[shuffled[swapWith], shuffled[index]]
}
return { priority, exchanges: shuffled }
})
return { kind: 'ordinary', tiers }
}
I ran three tests against the complete version: lower preferences sort first, equal preferences stay in one randomized tier, and a null MX mixed with ordinary exchangers fails closed.
tests 3
pass 3
fail 0
The function is a routing inspector, not an SMTP client. It deliberately stops when no MX records exist because an empty answer may trigger the implicit-MX fallback to A or AAAA records. A null MX is different: it explicitly says the domain accepts no email. The null MX versus no MX guide owns that branch in detail.
Check the live record set before diagnosing delivery
DNS dashboards show intended configuration. Resolvers show what a sender can currently retrieve. The free MX lookup displays a domain’s current exchangers, priorities, and TTLs in priority order. It does not contact a mailbox, so it cannot prove that an address exists or that a server will accept a message.
When the records look right but mail still fails, continue with the SMTP evidence. A connection timeout, temporary 4xx reply, and permanent 5xx rejection are different outcomes. The SMTP verification guide explains where those signals fit.
The practical reading is compact: group equal values, prefer the lowest tier, keep higher tiers available, and treat TTL as cache lifetime. That model is enough to spot the most common MX priority mistakes without pretending DNS alone proves deliverability.