Chapter 08 · Part II · Running a zone

DNS for email

Email leans on DNS twice. First to find where a message should go, and then, on arrival, to decide whether the message is really from who it says. Both happen through ordinary records in the sender's and recipient's zones: MX for the route, and three TXT records called SPF, DKIM and DMARC for the trust. If you need a refresher on record types and how a lookup travels, start with networks · DHCP & DNS; this chapter assumes it and goes into the mail-specific machinery, including how to protect a domain that never sends mail at all.

10DNS-querying terms one SPF check may use (include, a, mx, ptr, exists, redirect). The eleventh makes the whole check a permanent error. RFC 7208 §4.6.4
MX 0 .the null MX record: preference 0 pointing at the root, meaning "this domain accepts no mail." RFC 7505
3policies a domain owner can ask for when mail fails DMARC: none, quarantine, reject. RFC 9989

Common mix-up: SPF does not check the From line you see in your mail program. It checks the hidden envelope sender (the SMTP MAIL FROM, which later shows up as Return-Path). A message can pass SPF perfectly while its visible From names a completely different domain. Tying the check back to the visible From is the job of DMARC, which is why the three records only protect you together.

Step one: where does it go?

Finding the mail server

When you send a message to ana@example.com, your mail server only cares about the part after the at sign. It asks DNS for the MX records (mail exchanger) of example.com. Each MX record holds two things: a preference, a plain number, and the name of a host that accepts mail for the domain. The sender sorts the answers by preference, lowest first, and works down the list until a server takes the message.

The numbers are only compared with each other. Preferences 10 and 20 behave exactly like 1 and 2, or 100 and 2000; the gaps just leave room to slot a server in between later without renumbering. When two records share a preference, RFC 5321 says the sender must pick between them at random, which spreads load across a pool of equal servers for free.

An MX record names a host, not an address. The sender then looks up that host's A or AAAA records to get something it can connect to. That second step is where two classic mistakes live. An MX target must be a name that has its own address records: pointing MX at a CNAME falls outside the mail standard, and putting an IP address in the MX field does not work at all, because the field is a domain name. Both errors tend to work with one sender's software and fail with another's, which makes them miserable to diagnose.

; simulated answer, reserved names and documentation addresses
;; QUESTION SECTION:
;example.com.                IN  MX

;; ANSWER SECTION:
example.com.        3600  IN  MX  10 mx1.example.com.
example.com.        3600  IN  MX  20 mx2.example.com.

;; ADDITIONAL SECTION:
mx1.example.com.    3600  IN  A   192.0.2.10
mx2.example.com.    3600  IN  A   192.0.2.11

Two edge cases matter as much as the normal one. If a domain has no MX records at all but the name exists, RFC 5321 §5.1 tells the sender to treat the domain's own A or AAAA record as an implicit MX with preference 0. That rule dates from before MX existed, and it means a domain with only a web server address can still receive mail attempts, whether anyone is listening on port 25 or not.

That is why RFC 7505 added the null MX: a single record, MX 0 ., whose target is the root itself. It says, in a way no sender can misread, that this domain accepts no mail. A sender that sees it should fail the message at once (RFC 7505 suggests reply code 556) instead of queuing it for days. And if the domain does not exist at all (an NXDOMAIN answer), the sender must report an error straight away; there is nothing to retry.

What DNS returnsWhat the sender doesWhere it is defined
MX setSort by preference, lowest first; shuffle equal preferences; try each host until one acceptsRFC 5321 §5.1
No MX, but A/AAAAImplicit MX: deliver to the domain's own address, preference 0RFC 5321 §5.1
MX 0 .Null MX: the domain takes no mail. Fail now, do not queueRFC 7505
NXDOMAINThe domain does not exist. Report an errorRFC 5321 §5.1
Temporary failureDNS timed out or SERVFAIL. Queue the message and retry laterRFC 5321 §5.1
Go deeper: retries, backup MX and what "down" means

RFC 5321 requires a sender to be able to try each address in the list and says it should try at least two. When every server refuses or fails to answer, nothing bounces yet: the message stays in the sender's queue. The standard says the retry interval should generally be at least 30 minutes and that the give-up time generally needs to be at least 4 to 5 days, which is why a mail outage usually shows up as late mail rather than lost mail.

A "backup MX" at a higher preference used to be standard advice for exactly that reason. Today it is often less useful than it looks: if the backup accepts anything for the domain without the same spam and recipient checks as the primary, it becomes the path of least resistance for junk. If you run one, it needs the same filtering and the same list of valid recipients.

The distinction between temporary and permanent matters everywhere in mail. A DNS timeout is temporary; NXDOMAIN and null MX are permanent. Getting that wrong in either direction either loses mail or keeps a dead message in the queue for a week.

Instrument 1

Take a server down

Pick a domain, switch servers off, and press Send. The sender sorts the MX set the way RFC 5321 says, shuffling ties, then tries each host in turn. Watch what happens when every server is down, when there is no MX at all, and when the domain publishes a null MX. All names and addresses here are simulated.


      
Try order–
Attempts–
Outcome–

Step two: who sent it?

Every message has two senders

To understand mail authentication you have to know that a message carries its sender's name in two different places. During the SMTP conversation, before any of the message is sent, the sending server announces an envelope sender with the MAIL FROM command. That address is where bounces go, and it is what the post office would see on the outside of the envelope. Then comes the message itself, the letter inside, with its own From: header, the one your mail program shows. Nothing in SMTP requires the two to match, and for legitimate reasons they often do not: a newsletter service sends with its own bounce address while the From shows the brand.

Each of the three DNS-published checks looks at a different identifier. SPF checks the envelope domain against the connecting IP address. DKIM checks a cryptographic signature made on behalf of a domain named inside the signature, the d= domain. DMARC then asks the question people actually care about: does either of those passing results belong to the domain in the visible From?

The first check

SPF: a list of allowed senders

The Sender Policy Framework (RFC 7208) lets a domain publish, in a TXT record at its own name, the list of machines allowed to send mail using it as the envelope sender. A receiving server takes the connecting IP address and the MAIL FROM domain, fetches that domain's TXT records, finds the one that begins v=spf1, and reads it left to right. The first mechanism that matches the IP decides the result.

Each mechanism can carry a qualifier that says what a match means: + pass (the default if you write nothing), - fail, ~ softfail, and ? neutral. So -all at the end means "anything not listed before this point is not authorized," while ~all means "probably not authorized, but do not be harsh about it." If nothing matches and there is no redirect=, the result is neutral.

; simulated: example.com's SPF record, one TXT string
example.com. TXT "v=spf1 mx include:_spf.example.net ip4:203.0.113.0/26 -all"
; mx                    my own MX hosts may send     (1 lookup)
; include:_spf.example.net  whoever it lists may send  (1 lookup + its own)
; ip4:203.0.113.0/26    these 64 addresses may send  (no lookup)
; -all                  everyone else: fail
TermMatches whenCosts a lookup?
ip4: / ip6:The connecting address is inside the given networkNo
a[:domain]The address is one of the domain's A/AAAA records (optionally widened by /cidr)Yes
mx[:domain]The address belongs to one of the domain's MX hostsYes, plus up to 10 address lookups for the hosts
include:domainThat domain's own SPF check returns passYes, plus whatever it costs inside
exists:domainThe name has any A record (used with macros)Yes
ptrReverse DNS matches. RFC 7208 says SHOULD NOT be used: slow and unreliableYes
allAlways. Belongs lastNo
redirect=domainModifier: if nothing matched, use that domain's record insteadYes

The word include misleads almost everyone. It does not paste the other record in. It runs a complete, separate SPF check against the other domain and counts as a match only if that check passes. A -all inside an included record therefore cannot fail your mail; it just makes the include "not match," and evaluation carries on with your next term.

Because includes nest, one innocent-looking record can fan out into dozens of queries. RFC 7208 caps this: the terms that cause DNS queries (include, a, mx, ptr, exists and redirect) may be used at most 10 times in a single evaluation, counting everything inside every include. The eleventh is a permerror, a permanent error that says the record itself is broken. It also caps void lookups, queries that come back empty or NXDOMAIN, at two by default. Every large organization that signs up for a few mail services eventually hits the ten, usually without noticing until a receiver starts treating its mail as unauthenticated.

Go deeper: permerror, temperror and the other results

SPF has seven results, and the difference between the two errors matters. temperror means a transient DNS problem, like a timeout: the receiver should usually defer the message with a 4xx reply so the sender retries. permerror means the published records cannot be interpreted: two v=spf1 records at the same name, a syntax error, a missing record behind an include, or too many lookups. Nothing the receiver does will fix that; only the domain owner can.

The full list: pass (authorized), fail (explicitly not authorized), softfail (probably not authorized), neutral (no statement), none (no SPF record, or no usable domain), temperror and permerror. What a receiver does with each is up to its own policy. Under DMARC, only an aligned pass helps you, which is one reason softfail versus fail matters less than people argue about.

Common causes of hitting the limit, and fixes: remove includes for services you no longer use; replace a vendor's include with its published address ranges if they are stable (and accept that you now have to track changes); use a subdomain as the envelope sender for each service, so each gets its own record and its own budget of ten. "Flattening" tools that rewrite includes into IP lists work, but turn your record into a copy that silently goes stale.

SPF also has a weakness by design: it checks the hop that delivered the message to the receiver. When mail is forwarded, the forwarding server's address is not in the original sender's record, and SPF fails. DKIM, next, survives forwarding much better.

Instrument 2

Evaluate an SPF record

This is a real SPF evaluator running against a small simulated DNS. Choose a scenario or type your own sending address, MAIL FROM domain and record. Press Step to walk through the check one action at a time, the way a receiving server does it: every DNS-querying term ticks the gauge, includes open a nested check, and the first match decides. Edit the record to fix the broken scenarios. Simulated DNS: reserved names and documentation addresses only.

Show the rest of the simulated DNS
Result–
DNS lookups used–
Void lookups–
Step–

    The second check

    DKIM: a key in the DNS

    DomainKeys Identified Mail (RFC 6376) works on the message rather than the connection. The sending system hashes the body and a chosen list of header fields, signs the result with a private key, and adds a DKIM-Signature header. Two tags in that header tell the receiver where to find the matching public key: d=, the signing domain, and s=, the selector. The receiver glues them together with the fixed label _domainkey and fetches a TXT record:

    ; in the message (simulated, values shortened)
    DKIM-Signature: v=1; a=rsa-sha256; d=example.com; s=s2026;
        c=relaxed/relaxed; h=from:to:subject:date:message-id;
        bh=(hash of the body); b=(the signature)
    
    ; the receiver queries   s2026._domainkey.example.com   TXT
    s2026._domainkey.example.com. TXT
        "v=DKIM1; k=rsa; p=MIIBIjANBgkq…"   ; public key, base64

    If the body still hashes to bh= and the signature verifies with the published key, the receiver knows two things: the signed parts have not changed since signing, and someone holding example.com's private key signed them. DKIM passes for d=example.com. Unlike SPF, that result does not depend on which server delivered the message, so it survives ordinary forwarding. It does not survive a mailing list that rewrites the subject or adds a footer, because then the hash no longer matches.

    Selectors are what make keys manageable. A domain can publish as many keys as it likes, one per selector: one for its own servers, one for each mail service that signs on its behalf, and a new one each time it rotates. Key rotation is a three-step DNS exercise: publish the new key under a new selector, switch the signer over, and once mail signed with the old selector has had time to be delivered and checked, publish the old selector with an empty p=, which RFC 6376 defines as a revoked key.

    Go deeper: key sizes, algorithms and what the signature covers

    RFC 8301 updated DKIM's cryptography: signers must not use rsa-sha1, must use RSA keys of at least 1024 bits, and should use at least 2048. A 2048-bit key in base64 is longer than the 255-character limit of a single TXT string, so it is published as several strings in one record, which receivers join back together. (TXT string syntax is Chapter 05.) RFC 8463 added Ed25519 signatures, which use much shorter keys; many senders sign twice, once with each algorithm, for compatibility.

    The h= tag lists which headers are signed. From is always required. A common hardening step is "over-signing": listing a header like From or Subject one more time than it appears, so that anyone adding a second copy breaks the signature. The c= tag picks canonicalization, simple or relaxed, which decides how much whitespace rewriting the signature tolerates.

    DKIM keys sit in DNS without DNSSEC in most zones, so the receiver is trusting the DNS answer as much as the cryptography. Chapter 11 covers how signed zones close that gap.

    The check that ties it together

    DMARC: alignment and policy

    SPF and DKIM each prove something about a domain. Neither proves anything about the domain in the From line. DMARC (Domain-based Message Authentication, Reporting, and Conformance) closes that gap with one rule: a message passes DMARC if SPF passed for a domain aligned with the From domain, or DKIM passed for a domain aligned with the From domain. Either one is enough.

    Alignment comes in two strengths. Strict means the domains are identical. Relaxed, the default, means they share the same organizational domain, so a DKIM signature from mail.example.com aligns with a From of news.example.com, because both belong to example.com. Each check has its own setting: aspf for SPF and adkim for DKIM.

    The domain owner publishes the policy as a TXT record at _dmarc under the From domain:

    ; simulated
    _dmarc.example.com. TXT
        "v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@example.com"
    ;  v=DMARC1   must come first, or the record is ignored
    ;  p=         what to do with mail that fails: none | quarantine | reject
    ;  rua=       where receivers send daily aggregate reports
    ;  optional:  sp=  existing subdomains     np=  non-existent subdomains
    ;             adkim= / aspf=  r or s       t=y  testing, one level softer
    ;             fo= and ruf=  failure reports

    The three policies are requests, not commands. none asks for nothing; the owner just wants the reports. quarantine says failing mail is suspicious, which usually means the spam folder. reject says failing mail is not legitimate and can be refused during the SMTP conversation. Receivers combine the request with their own judgment, and the standard says they should.

    The reports are what make DMARC deployable. With rua=, receivers send the domain owner a daily XML summary: which IP addresses sent mail using its From domain, how many messages, and whether SPF and DKIM passed and aligned. A domain owner starts at p=none, reads the reports until every legitimate stream (the office mail, the billing system, the newsletter service, the help desk) shows up aligned, and only then moves to quarantine and reject. Skipping that step is the classic way to lose your own invoices.

    Go deeper: DMARC is now RFC 9989, and the DNS Tree Walk

    For a decade DMARC was RFC 7489, an Informational document. In May 2026 the IETF published the revision known as DMARCbis as three Standards Track documents: RFC 9989 (the protocol), RFC 9990 (aggregate reports) and RFC 9991 (failure reports). Together they obsolete RFC 7489. The changes that matter in DNS:

    • The DNS Tree Walk replaces the public suffix list. RFC 7489 found the organizational domain with an outside list of public suffixes (like com or co.uk). RFC 9989 instead queries _dmarc records up the tree, from the domain toward the root, and picks the organizational domain from what it finds. To stop abuse with absurdly deep names, a walk makes at most eight queries.
    • New tags: np= for subdomains that do not exist, psd= so a public suffix operator can mark its own record, and t= for testing mode.
    • Removed tags: pct= (apply the policy to a percentage of mail) is gone, replaced by t=y, which asks receivers to apply one level softer than published. rf= and ri= are gone too.
    • A caution about reject: RFC 9989 says domains whose users post to mailing lists should not publish p=reject, because lists that modify messages break DKIM, and receivers then unsubscribe innocent list members after the bounces.

    Policy discovery, in order: a record at the From domain itself; otherwise the record at its organizational domain (where sp= or np= applies, depending on whether the subdomain exists); otherwise a record at its public suffix domain. If none exists, DMARC does not apply to the message at all.

    Instrument 3

    Check alignment yourself

    Set the From domain, what SPF and DKIM authenticated, and the policy that example.com publishes. The checker runs the RFC 9989 DNS Tree Walk over a small simulated set of _dmarc records to find each domain's organizational domain, decides alignment, and applies the policy. Flip the method to the older RFC 7489 public suffix list to compare. Simulated records; the suffix list is a tiny labeled sample, not the real one.

    example.com publishes:

    
          
          
    Org domain of From–
    SPF aligned?–
    DKIM aligned?–
    DMARC result–

    
        
    Protecting the delivery itself

    Locking the transport: MTA-STS and TLS-RPT

    SPF, DKIM and DMARC are about who sent a message. A separate problem is whether the delivery to you is private. Server-to-server SMTP uses STARTTLS opportunistically: if both sides offer encryption they use it, and if not, they quietly carry on in plain text. Someone who can tamper with the connection can strip the offer, and the sender will never know. Worse, because MX records are usually unsigned, a forged DNS answer can point the sender at the wrong server altogether.

    MTA-STS (RFC 8461) lets a receiving domain say "always use TLS to reach me, with a valid certificate, and only to these MX hosts." It takes two pieces. A TXT record at _mta-sts.example.com announces that a policy exists and gives it an id. The policy itself is a small text file served over HTTPS at a fixed address, so it is protected by the web certificate system rather than by DNS. A sender fetches the policy, caches it for max_age seconds (up to about a year), and checks the id on later deliveries to notice changes.

    ; DNS (simulated)
    _mta-sts.example.com.   TXT  "v=STSv1; id=20261022T000000"
    _smtp._tls.example.com. TXT
        "v=TLSRPTv1; rua=mailto:tls-reports@example.com"
    
    ; https://mta-sts.example.com/.well-known/mta-sts.txt
    version: STSv1
    mode: enforce          ; or testing, or none
    mx: mx1.example.com
    mx: mx2.example.com
    max_age: 604800        ; one week, in seconds

    Start MTA-STS in mode: testing, which asks senders to report failures but deliver anyway. The reports come from TLS-RPT (RFC 8460): a TXT record at _smtp._tls.example.com naming where senders should send a daily summary of successful and failed TLS connections. Read them until they are clean, then switch to enforce. When you change the policy file, change the id in the TXT record too, or senders will keep using their cached copy.

    There is a second, DNS-native way to do the same job: DANE for SMTP, which publishes the server's certificate fingerprint in TLSA records protected by DNSSEC. It depends on a signed zone, so it lives in Chapter 11.

    Putting it together

    Domains that never send mail

    Most organizations own more domains than they use for mail: old brand names, typo-protection registrations, a domain held for a future project. Each one is a From address someone else can borrow, and with no records published, receivers have nothing to check it against. Locking a parked domain down takes three records, and nothing else:

    ; parked.example — sends no mail, receives no mail (simulated)
    parked.example.          3600  IN  MX   0 .                          ; null MX: accept nothing
    parked.example.          3600  IN  TXT  "v=spf1 -all"                ; no IP may send as this domain
    _dmarc.parked.example.   3600  IN  TXT  "v=DMARC1; p=reject;
                                             rua=mailto:dmarc-reports@example.com"

    The null MX stops anyone from delivering to the domain, including bounces aimed at it. v=spf1 -all says no server is authorized, so SPF fails everywhere. DMARC p=reject covers the visible From and, with no sp= or np=, every subdomain below it, existing or not. There is no DKIM to publish: with no selector records, every signature claiming the domain fails, which is exactly what you want. Pointing rua= at a domain you do watch tells you when someone tries. (If the report address is in a different domain, that domain has to publish a small authorization record agreeing to receive them; RFC 9990 describes it.)

    For a domain that does send mail, the same pieces add up to a short checklist, and the largest mailbox providers now require most of it. Google's published sender guidelines require every sender to Gmail to set up SPF or DKIM, have valid forward and reverse DNS for its sending addresses (Chapter 07), and use TLS. Senders of more than 5,000 messages a day to Gmail accounts must set up SPF and DKIM, publish a DMARC record (p=none is acceptable), align the From domain with either the SPF or the DKIM domain, and support one-click unsubscribe for marketing mail.

    RecordNameSending domainParked domain
    MXexample.comYour inbound servers, by preference0 . (null MX)
    SPF (TXT)example.com, and each envelope domainEvery service that sends as you; stay under 10 lookups; end with -all or ~allv=spf1 -all
    DKIM (TXT)selector._domainkey.example.comOne key per signer, 2048-bit RSA or Ed25519; rotate by selectorNothing
    DMARC (TXT)_dmarc.example.comStart p=none with rua=, fix what reports show, then quarantine, then reject where it fitsv=DMARC1; p=reject
    MTA-STS_mta-sts.example.com + HTTPS policytesting, then enforceNot needed
    TLS-RPT (TXT)_smtp._tls.example.comA mailbox you actually readNot needed
    Go deeper: a worked example, from first record to reject

    Say example.com uses its own servers for staff mail and a newsletter service that sends from its own addresses. The tidy setup:

    1. Give the newsletter service its own envelope subdomain, news.example.com, with an SPF record that includes only that service. The main record keeps its own budget of ten lookups.
    2. Have the service sign with d=example.com under its own selector, say nl1._domainkey.example.com. Now DKIM aligns strictly, and SPF aligns in relaxed mode, because news.example.com and example.com share the organizational domain.
    3. Publish _dmarc.example.com "v=DMARC1; p=none; rua=mailto:…" and read a few weeks of reports. Every source should show an aligned pass. The ones that do not (the old billing system, the help desk tool) get fixed or moved.
    4. Move to p=quarantine, wait, then decide on p=reject, remembering RFC 9989's warning about users who post to mailing lists. Add np=reject early: nobody has a legitimate reason to send from a subdomain that does not exist.

    Try exactly this setup in Instrument 3 with the "Newsletter via a service" preset, then break it with "Forwarded" and "Mailing list" to see which half of DMARC keeps the message alive.

    Where this stops: this chapter is about configuring your own domain. How attackers abuse domains that skip these steps, and how defenders spot it, is in Chapter 13. The PTR side of mail, forward-confirmed reverse DNS, is in Chapter 07.

    Cheat sheet

    Terms from this chapter

    MX record
    A record naming a host that accepts mail for a domain, with a preference number. Senders try the lowest preference first and shuffle ties.
    Implicit MX
    The fallback when a domain has no MX records: senders deliver to the domain's own A/AAAA address as if it had an MX of preference 0 (RFC 5321 §5.1).
    Null MX
    The record MX 0 ., meaning the domain accepts no mail. Senders should fail at once instead of queuing (RFC 7505).
    Envelope sender
    The address given in SMTP's MAIL FROM command, used for bounces and checked by SPF. Often different from the visible From header.
    SPF
    Sender Policy Framework: a TXT record listing which IP addresses may send mail using a domain as the envelope sender (RFC 7208).
    SPF lookup limit
    At most 10 DNS-querying terms (include, a, mx, ptr, exists, redirect) per evaluation, counted through every include; more is a permerror. Empty answers ("void lookups") are limited to two.
    DKIM selector
    The s= label that, with the signing domain, names the TXT record holding a DKIM public key: selector._domainkey.domain.
    Identifier alignment
    DMARC's test that an authenticated domain (SPF's MAIL FROM or DKIM's d=) matches the From domain: identical (strict) or sharing an organizational domain (relaxed).
    DNS Tree Walk
    RFC 9989's method for finding policy and the organizational domain: query _dmarc records upward from a name toward the root, at most eight queries.
    MTA-STS
    A policy, announced in DNS and served over HTTPS, telling senders to deliver to a domain only over verified TLS to listed MX hosts (RFC 8461). TLS-RPT (RFC 8460) collects the failure reports.
    Where this comes from

    Sources