← Back to blog
Advanced 35 min read

DNS-Based Email Authentication and DMARC: A Technical Reference

GRB Digital
  • DMARC
  • SPF
  • DKIM
  • Email Authentication
  • DNS
  • ARC

Grounded in RFC 7208 (SPF), RFC 6376 (DKIM), RFC 9989/9990/9991 (DMARC, formerly RFC 7489), and RFC 8617 (ARC). Current as of June 2026.


A note on standardization status (read this first)

As of June 2026, the canonical DMARC specification is no longer RFC 7489. In May 2026 the IETF DMARC Working Group published the “DMARCbis” effort as three Standards-Track RFCs that obsolete both RFC 7489 (the 2015 Informational document) and RFC 9091 (the experimental PSD extension):

  • RFC 9989: the core DMARC protocol (record syntax, policy evaluation, alignment, the DNS Tree Walk). Per the IETF Datatracker (draft-ietf-dmarc-dmarcbis), authored by T. Herr, Ed. (Valimail) and J. Levine, Ed. (Standcore LLC), intended status Standards Track, and it “obsoletes RFCs 7489 and 9091.” Published as a Proposed Standard in May 2026.
  • RFC 9990: aggregate (RUA) reporting.
  • RFC 9991: failure (RUF) reporting.

Operationally this means three things. First, DMARC is now a Proposed Standard with formal IETF consensus rather than an Independent Submission, so auditors and compliance frameworks can cite a Standards-Track document. Second, existing records keep working, the version string is still v=DMARC1, and RFC 7489-era receivers and RFC 9989 receivers both parse the same records (each ignores tags it does not recognize). Third, a handful of real mechanical changes matter: the Public Suffix List is replaced by a DNS Tree Walk, three tags (pct, rf, ri) are now historic, three tags (np, psd, t) are added, and the guidance around p=reject has been materially tightened. This article uses RFC 9989 as the reference and flags where RFC 7489 behavior still differs in the deployed receiver population.


SECTION 1: EMAIL AUTHENTICATION FUNDAMENTALS

Email authentication is built from three independent DNS-published mechanisms layered on top of each other. SPF and DKIM are the underlying authentication primitives; DMARC is the policy-and-reporting layer that ties them to the one identifier a human actually sees, the From: header. ARC is an experimental fourth mechanism that tries to preserve authentication results across intermediaries. None of them inspects message content or judges whether mail is “good”; they authenticate domains, not intent.

The identifiers involved

Email carries several different “from” identities, and understanding which is which is the single most important prerequisite for understanding DMARC.

IdentifierRFC termWhere it livesAuthenticated by
MAIL FROM / Return-PathRFC5321.MailFromSMTP envelope (MAIL FROM: command)SPF
HELO/EHLORFC5321.HELOSMTP greetingSPF (secondary)
DKIM signing domaind= tagDKIM-Signature: headerDKIM
Author / header-fromRFC5322.FromFrom: message headerDMARC (via alignment)

The envelope MAIL FROM and the visible From: header are different fields and frequently contain different domains, that gap is exactly what DMARC’s alignment concept exists to close.

SPF (RFC 7208)

SPF lets a domain publish, in DNS, the set of hosts authorized to use that domain in the MAIL FROM (and optionally HELO) identity. It is published as a single TXT record (the dedicated SPF RR type was abandoned; RFC 7208 §3.1 mandates TXT only) at the domain root, beginning with v=spf1.

Record anatomy:

example.com.  TXT  "v=spf1 +mx a:colo.example.com/28 include:_spf.google.com -all"

A record is a left-to-right ordered list of mechanisms, each carrying a qualifier. Evaluation (the check_host() function of RFC 7208 §4) walks the mechanisms in order and returns the qualifier of the first match.

Mechanisms:

MechanismMatchesCosts a DNS lookup?
allalways matches (terminal)No
include:domainrecursively evaluates another domain’s SPFYes
a / a:domainthe A/AAAA record(s) of the domainYes
mx / mx:domainthe MX hosts’ addressesYes (plus per-host A lookups)
ptrreverse DNS of the connecting IP (deprecated, do not use)Yes
ip4: / ip6:a literal address or CIDR blockNo
exists:domainwhether a constructed name resolves (used with macros)Yes

Qualifiers prefix a mechanism and define the result when it matches:

QualifierResultMeaning
+ (default)Passauthorized
-Failnot authorized (hard)
~SoftFailprobably not authorized (weak)
?Neutralno assertion

The eight result types defined in RFC 7208 §2.6 are none, neutral, pass, fail, softfail, temperror (transient DNS failure, retry may succeed), and permerror (record cannot be interpreted, operator action required). The two redirect= and exp= modifiers handle delegation and explanation strings.

The 10-lookup limit (RFC 7208 §4.6.4). This is the most operationally consequential SPF constraint. SPF implementations MUST limit the count of mechanisms/modifiers that perform DNS lookups to 10 per check; exceeding this returns permerror, which DMARC treats as an SPF fail. The mechanisms that count are include, a, mx, ptr, exists, and the redirect modifier; ip4, ip6, and all are free. The initial lookup to fetch the SPF record itself does not count. Crucially the limit is on terms that trigger lookups, not raw queries, an mx term can expand into many A/AAAA queries (capped separately at 10 per MX), and nested includes multiply recursively. The scale of breakage is real: DMARCguard’s SPF Supply Chain Study scanned 5,499,028 domains and found that “148,655 exceed the SPF 10-lookup limit. That is 4.8% of all SPF-enabled domains running with broken email authentication right now.” (A 2025 large-scale academic study, “Lazy Gatekeepers,” separately found the too-many-DNS-lookups error in 23.42% of misconfigured SPF records.)

The void-lookup limit. A second, less-known constraint: RFC 7208 §4.6.4 caps void lookups (queries returning NXDOMAIN or NODATA) at 2. A third void lookup also produces permerror even if the total stays under 10. The usual cause is a stale include: pointing at a decommissioned provider. The RFC uses SHOULD for this limit while the 10-mechanism limit is MUST, but most major receivers enforce both.

Structural facts worth internalizing: one and only one SPF record per name (multiple v=spf1 records is itself a permerror); a single TXT string maxes at 255 octets and records should fit in 512 octets to avoid DNS truncation issues; and SPF inherently breaks on forwarding, because the forwarder’s IP is not in the original domain’s SPF record.

flowchart TD
    A[Receiver gets MAIL FROM domain + client IP] --> B[Fetch v=spf1 TXT record]
    B --> C{Record found?}
    C -->|No| N[Result: none]
    C -->|Multiple| PE[Result: permerror]
    C -->|Yes| D[Evaluate mechanisms left to right]
    D --> E{Mechanism matches?}
    E -->|Yes| F[Return its qualifier:<br/>+pass / -fail / ~softfail / ?neutral]
    E -->|No, next term| G{Lookup count > 10<br/>or voids > 2?}
    G -->|Yes| PE
    G -->|No| D
    D --> H["Reach 'all' -> its qualifier"]

DKIM (RFC 6376)

DKIM attaches a cryptographic signature to a message as a header field, asserting that the signing domain takes responsibility for the message and that the signed content has not changed in transit. It does not encrypt and it does not guarantee end-to-end integrity of unsigned parts, it asserts only that the signed portions match.

Signing process (RFC 6376 §5): the signer selects a private key and a selector, canonicalizes the chosen headers and body, computes a body hash (bh=), signs the concatenation of selected headers plus the DKIM-Signature itself, and prepends a DKIM-Signature: header.

Key publication. The public key lives in a TXT record at selector._domainkey.signingdomain. The selector namespace (RFC 6376 §3.1) lets a domain run multiple concurrent keys (by location, by date, by vendor) which is what makes seamless key rotation possible: publish the new selector, start signing with it, retire the old one after mail in transit has cleared.

DKIM-Signature tags:

TagMeaning
v=version (1)
a=signing algorithm (e.g. rsa-sha256)
c=canonicalization, as header/body (e.g. relaxed/relaxed)
d=the signing domain identifier (SDID), the identity DMARC aligns against
s=selector (locates the public key in DNS)
h=colon-separated list of signed header fields
bh=body hash
b=the signature itself

The optional i= (AUID) names an agent/user within the d= domain; l= limits the number of body octets signed (security-hazardous, content can be appended below the signed region).

Canonicalization (RFC 6376 §3.4). Because relays legitimately rewrite whitespace and re-fold headers, DKIM defines two normalization algorithms for each of header and body:

  • simple: byte-exact; tolerates almost no modification. The default if unspecified.
  • relaxed: lowercases header names, unfolds continuation lines, collapses runs of whitespace to a single space, strips trailing whitespace, and removes trailing empty body lines.

Operators overwhelmingly use relaxed/relaxed, because simple produces false verification failures the moment a message passes through a mailing list or gateway that touches whitespace.

Key sizes (RFC 6376 §3.3.3). Signers MUST use at least 1024-bit RSA for long-lived keys; verifiers must validate 512–2048-bit keys. In current practice 2048-bit is the baseline; 1024-bit is considered weak; 512-bit is trivially breakable. The constraint pulling the other way is that a 2048-bit public key may not fit a single 255-octet TXT string and requires a multi-string TXT record.

sequenceDiagram
    participant S as Signer (MSA/MTA)
    participant DNS as DNS
    participant V as Verifier (receiver)
    S->>S: Canonicalize headers+body (c=)
    S->>S: Compute bh=, sign selected h= headers -> b=
    S->>S: Prepend DKIM-Signature (d=, s=, h=, bh=, b=)
    S->>V: Deliver message
    V->>DNS: Query s._domainkey.d (TXT)
    DNS->>V: Public key
    V->>V: Recompute body hash, verify b= over h=
    V->>V: Result: pass / fail

Alignment: the concept that makes DMARC work (RFC 9989 §4.4)

SPF authenticates the envelope MAIL FROM domain. DKIM authenticates the d= domain. Neither is necessarily the domain a user sees in From:. A spammer can trivially pass SPF and DKIM for their own throwaway domain while forging your domain in From:. Alignment is the requirement that the SPF- or DKIM-authenticated domain match the RFC5322.From (Author) domain. DMARC passes if at least one of aligned-SPF or aligned-DKIM passes.

Two alignment modes per mechanism, controlled by aspf= and adkim=:

  • relaxed (default, r): the authenticated domain and the From domain must share the same Organizational Domain. mail.example.com aligns with example.com.
  • strict (s): an exact FQDN match is required.

Worked examples:

  • DKIM d=example.com, From alerts@news.example.com: aligned in relaxed, not in strict (RFC 9989 §4.4.1).
  • SPF MAIL FROM cbg.bounces.example.com, From payments@example.com: aligned in relaxed, not strict (RFC 9989 §4.4.2).
  • DKIM d=mailchimp.com, From you@yourbrand.com: not aligned in either mode, this is the canonical reason ESP mail fails DMARC until you configure a custom signing domain.
flowchart TD
    M[Message arrives] --> SPF[Evaluate SPF on MAIL FROM]
    M --> DKIM[Evaluate each DKIM signature]
    SPF --> SA{SPF pass AND<br/>MAIL FROM domain aligns<br/>with From? aspf=r/s}
    DKIM --> DA{Any DKIM sig valid AND<br/>d= aligns with From? adkim=r/s}
    SA -->|Yes| PASS[DMARC PASS]
    DA -->|Yes| PASS
    SA -->|No| CHK{Either path passed?}
    DA -->|No| CHK
    CHK -->|No| FAIL["DMARC FAIL -> apply policy"]

DMARC (RFC 9989): the policy layer

DMARC is a TXT record published at _dmarc.example.com. It does three jobs: declares the Author Domain’s handling preference for failing mail, requests reports, and (via the Tree Walk) defines how subdomains inherit policy.

Policy and control tags:

TagPurposeDefault
vversion, must be DMARC1, must be first, case-sensitive(required)
ppolicy for the Author/Organizational Domain: none / quarantine / reject(required); absent p is treated as p=none
sppolicy for subdomainsinherits p
nppolicy for non-existent subdomains (NXDOMAIN), new in RFC 9989inherits sp else p
adkimDKIM alignment mode r/sr
aspfSPF alignment mode r/sr
fofailure-reporting options0
ruaURI(s) for aggregate reports(none)
rufURI(s) for failure reports(none)
ttest mode y/n, new in RFC 9989, replaces pctn
psdpublic-suffix-domain flag y/n/u, new in RFC 9989u

Tags now historic (RFC 9989 §9.3, marked “historic,” referencing RFC 7489):

TagWasWhy removed
pctpercentage of mail to apply policy toimplemented inconsistently; only 0 and 100 reliable across receivers
rffailure report formatonly afrf ever defined
riaggregate report intervalreceivers ignored it; daily is the norm

Receivers ignore unknown tags (RFC 9989 §4.8: “Unknown tags MUST be ignored”), so a record still carrying pct/rf/ri continues to function, they are simply inert.

Failure-handling options (fo), interpreted only when ruf is present:

ValueA failure report is generated when…
0 (default)both SPF and DKIM fail to produce an aligned pass
1either SPF or DKIM produces something other than an aligned pass
dDKIM signature failed evaluation (regardless of alignment)
sSPF evaluation failed (regardless of alignment)

Values combine with colons: fo=1:d:s. fo=1 is the most informative for debugging because it fires on any single-mechanism failure.

ARC (RFC 8617): preserving authentication across intermediaries

ARC is Experimental (RFC 8617, July 2019). It addresses the structural problem that forwarding breaks SPF (the forwarder’s IP isn’t authorized) and mailing lists break DKIM (footers, subject tags, and header rewrites invalidate the signature). When the original authentication breaks at an intermediary, ARC lets that intermediary record what it saw before it made changes, cryptographically, so the final receiver can choose to trust the chain.

ARC adds three header fields, each carrying an instance number i= so multiple hops form an ordered chain (an “ARC Set” per hop):

HeaderAbbrevContents
ARC-Authentication-ResultsAARsnapshot of the SPF/DKIM/DMARC results the intermediary observed
ARC-Message-SignatureAMSa DKIM-like signature over the message as the intermediary forwards it
ARC-SealASa DKIM-like signature over the prior ARC headers, seals the chain integrity; has no h= of message headers

The chain validation status is cv=none|pass|fail. A receiver evaluating a message that fails DMARC may consult a valid ARC chain and decide to deliver anyway, attributing trust to the sealing domain.

Adoption status and the operational caveat. ARC is deployed by Google, Microsoft 365, Yahoo, and Fastmail and is implemented in mailing-list managers like Mailman and Sympa. But it is trust-based and unenforced: RFC 8617 itself cautions that every sealer must be treated with suspicion, since a malicious actor can seal spam, and an intact passing chain can be replayed. ARC tells you what a previous hop claims it saw; it does not make that hop honest. RFC 9989 also formally recognizes ARC as an extension technology but does not require honoring it. There is also IETF discussion of moving ARC to Historic and folding pieces into a future DKIM revision, so its long-term status is unsettled.

How it all fits in a mail flow

sequenceDiagram
    participant Author
    participant MSA as Sending MSA/MTA
    participant DNS
    participant rMTA as Receiving MTA
    participant DMARC as DMARC Verifier
    participant Mailbox
    Author->>MSA: Compose (From: user@example.com)
    MSA->>MSA: DKIM-sign (d=example.com)
    MSA->>rMTA: SMTP (MAIL FROM: bounce@example.com)
    rMTA->>DNS: SPF TXT (MAIL FROM domain)
    rMTA->>DNS: DKIM key (s._domainkey.example.com)
    rMTA->>DNS: DMARC TXT (_dmarc.example.com via Tree Walk)
    rMTA->>DMARC: SPF result + DKIM result + From domain
    DMARC->>DMARC: Alignment check (aspf/adkim)
    DMARC->>DMARC: pass = deliver / fail = apply p=
    DMARC->>Mailbox: Deliver / quarantine / reject
    rMTA-->>DNS: (later) send RUA/RUF to rua=/ruf=

Reporting mechanisms

Aggregate reports (RUA, RFC 9990). Sent typically once per 24 hours by each participating receiver, as gzip-compressed XML, to the addresses in rua=. They are a statistical summary, not message content: per source IP, the message count, the SPF and DKIM results, the alignment outcome, and the policy disposition applied. The XML skeleton:

<feedback>
  <report_metadata>
    <org_name>google.com</org_name>
    <report_id>...</report_id>
    <date_range><begin>...</begin><end>...</end></date_range>
  </report_metadata>
  <policy_published>
    <domain>example.com</domain>
    <p>quarantine</p><sp>none</sp>
    <adkim>r</adkim><aspf>r</aspf>
  </policy_published>
  <record>
    <row>
      <source_ip>192.0.2.44</source_ip>
      <count>85</count>
      <policy_evaluated>
        <disposition>none</disposition>
        <dkim>pass</dkim><spf>fail</spf>
      </policy_evaluated>
    </row>
    <identifiers><header_from>example.com</header_from></identifiers>
    <auth_results>
      <dkim><domain>example.com</domain><result>pass</result></dkim>
      <spf><domain>mail.example.net</domain><result>pass</result></spf>
    </auth_results>
  </record>
</feedback>

Two reading rules prevent confusion: the policy_evaluated block reports DMARC-relative results as the receiver applied them (including any local override and its <reason>), whereas auth_results reports the raw SPF/DKIM outcomes. RFC 9990 updates the schema (namespace dmarc-2.0) with optional fields like discovery_method, test-mode signaling in policy_published, and an optional envelope_from, while remaining backward-compatible with RFC 7489 parsers. RFC 9990 also removed the report-size-limit suffix on reporting URIs.

Failure reports (RUF, RFC 9991). Near-real-time, per-message reports triggered by ruf= and shaped by fo=. They contain message headers and can contain PII, which is why RFC 9989/9991 explicitly flag privacy concerns and why the major mailbox providers (Google, Microsoft, Yahoo) largely do not send them. In practice, RUF is of limited value because so few receivers emit it.


SECTION 2: THE DMARC IMPLEMENTATION SPECTRUM

DMARC policy is best understood not as a ladder you must climb but as a set of operational states, each of which means something specific about what receivers do and what you can learn. The three p= values are the same three values for sp= and np=.

p=none: monitoring

Receivers apply no DMARC-based disposition; mail flows exactly as it would without DMARC. What you gain is telemetry: aggregate reports begin arriving, revealing every IP sending mail that claims your domain, and whether each stream produces aligned SPF/DKIM. RFC 9989 §5.4 is explicit that “discovered policies of p=none MUST NOT modify existing mail handling processes.” Operationally, p=none is the discovery instrument, its entire purpose is to build a sender inventory and find misalignment before any mail is at risk. Its limitation is that it provides no protection; a domain at p=none is published but not defended, and indefinite parking there is the norm, not the exception: DMARCeye’s Q1 2026 platform data found that 39.9% of domains with a valid DMARC policy remain permanently at p=none, providing zero protection against spoofing despite being engaged enough to run a monitoring tool.

p=quarantine: soft enforcement

Failing mail is treated as suspicious, typically routed to spam/junk. This is the first state where misconfiguration has user-visible consequences, so it requires that report monitoring already be in place and that the known legitimate senders already align. Under RFC 7489, pct= was the safety valve for partial quarantine; RFC 9989 removes pct in favor of t=y test mode (see below).

p=reject: hard enforcement

The Domain Owner asks receivers to reject failing mail outright, ideally during the SMTP transaction. This is the only state that actually stops exact-domain spoofing at the inbox. RFC 9989 substantially reframes the prerequisites and the receiver’s obligations, see the dedicated discussion below.

flowchart LR
    N["p=none<br/>(observe, no action)"] -->|inventory complete,<br/>senders aligned| Q["p=quarantine<br/>(failing -> spam)"]
    Q -->|stable pass rates,<br/>no surprise drops| R["p=reject<br/>(failing -> blocked)"]
    N -.->|t=y test mode| N
    Q -.->|t=y test mode| Q
    R -.->|t=y test mode<br/>applies one level down| R

The RFC 9989 reframing of p=reject (this is a real change)

Three normative shifts in RFC 9989 §7.4 and §8 change how p=reject behaves and who is advised to publish it:

  1. Receivers must not blindly reject. “Mail Receivers MUST NOT reject incoming messages solely on the basis of a p=reject policy… In the absence of other knowledge and analysis, Mail Receivers MUST treat such failing mail as if the policy were p=quarantine rather than p=reject.” In other words, p=reject is now formally a strong signal, not an unconditional command.
  2. Mailing-list domains are advised away from reject. Domains “that host users who might post messages to mailing lists SHOULD NOT publish… p=reject,” and those that do should first run p=none for at least a month, then p=quarantine for an equally long period, comparing dispositions.
  3. Reject implies DKIM. “Domains that publish p=reject MUST NOT rely solely on SPF… and MUST apply valid DKIM signatures to their messages”, because SPF alone breaks on forwarding while aligned DKIM survives it.

The operational reading: enforcement readiness is now defined less by a percentage dial and more by (a) a complete, DKIM-aligned sender inventory and (b) an honest assessment of mailing-list exposure.

Subdomain policy (sp=) and the new np=

sp= lets the Organizational Domain set a different policy for its subdomains than for itself. The common pattern is a strict parent and lenient subdomains during rollout (p=reject; sp=none) or the reverse for a tightly controlled estate. If sp= is absent, subdomains inherit p.

np= (new in RFC 9989) sets a policy specifically for non-existent subdomains, names that return NXDOMAIN. RFC 9989’s verbatim definition: it “indicates the message handling preference of the Domain Owner or PSO for mail using non-existent subdomains of the prevailing Organizational Domain and not passing DMARC validation. It applies only to non-existent subdomains of the Organizational Domain queried and not to either existing subdomains or the domain itself.” This closes a real attack: spoofing random-string.example.com, a subdomain that was never provisioned. np=reject blocks that class without changing how real subdomains are handled. If absent, np falls back to sp (or p).

Test mode (t=): what replaced pct

t=y signals that the Domain Owner is testing and wants the receiver to apply a policy one level weaker than published: with t=y, p=quarantine is treated as none, and p=reject is treated as quarantine (RFC 9989 §4.7). It does not affect report generation and has no effect when the policy is already none. This is a binary on/off, deliberately replacing the unreliable percentage semantics of pct. The trade-off versus the old pct ramp: you lose fine-grained 10%→25%→50% staging, but in practice that staging was rarely used. DMARCeye’s Q1 2026 data found that of the domains that do enforce, 93.8% apply their policy to 100% of traffic with no staged rollout, and only 6.2% use pct<100, confirming the percentage dial was little-used and inconsistently honored across receivers.

Policy override mechanisms: sender-side vs receiver-side

These are frequently conflated and are fundamentally different:

  • Sender-side controls are what the Domain Owner expresses in the record: t=y, sp=/np=, alignment modes. They shape what you ask receivers to do.
  • Receiver-side local policy overrides are decisions the receiver makes to not honor your policy, and RFC 9989 explicitly permits them. A receiver may deliver mail that failed p=reject (e.g., it recognizes a forwarder, or has a local allowlist), and it records this in the aggregate report’s policy_evaluated block with a <reason> such as forwarded, local_policy, mailing_list, or trusted_forwarder. This is why your reports will show disposition: none on records that nominally failed, the receiver overrode your policy.

Understanding this distinction matters because override <reason> data is one of the richest diagnostic signals in a report: a cluster of forwarded/mailing_list overrides tells you that legitimate indirect mail is failing and would have been blocked at a stricter receiver.

Interpreting aggregate report data: a triage guide

For each <record>, the diagnostic logic is:

SPF (aligned)DKIM (aligned)DMARCInterpretation
passpasspassHealthy first-party or correctly configured third-party mail
failpasspassNormal for ESP/forwarded mail relying on DKIM alignment, fine
passfailpassDKIM broke (often body modification) but SPF aligned, investigate but not urgent
failfailfailEither spoofing to block, an unconfigured legitimate sender to fix, or a forwarder you can’t control

The skill is sorting that bottom row into its three sub-cases using the source IP, reverse DNS, and volume. A high-volume unfamiliar IP failing both is likely forgery; a recognizable SaaS provider failing both is an alignment gap you can fix; a residential or known-mail-host IP with low volume is often forwarding.

Common failure patterns

PatternSymptom in reportsRoot cause
Misaligned ESPSPF/DKIM pass for provider domain, DMARC failNo custom signing/return-path domain configured
SPF permerrorSPF result permerror across sources>10 lookups or >2 void lookups
Forwarding breakageSPF fail, DKIM pass-or-fail, <reason>forwardedForwarder’s IP not authorized; possible body edits
Mailing-list breakageDKIM fail, subject/footer modifiedList rewrote the signed message
Shadow IT senderUnknown IP, low pass rateA team adopted a SaaS tool without DNS configuration
New-key rotation gapSudden DKIM fail on a known streamSigned with a selector whose key isn’t (yet) published

SECTION 3: SUBDOMAINS & NON-MAILING DOMAINS

Subdomain hierarchies and dormant domains are where DMARC’s policy-resolution rules become subtle, and where RFC 9989’s biggest mechanical change (the DNS Tree Walk) lives.

How DMARC resolves policy: the DNS Tree Walk (RFC 9989 §4.10)

Under RFC 7489, a receiver that found no record at _dmarc.<author-domain> consulted the Public Suffix List (a community-maintained file at publicsuffix.org) to guess the Organizational Domain, then looked there. This had real defects: different receivers used different PSL snapshots and could disagree about the same domain’s Organizational Domain, the list had no formal update cadence, and the protocol depended on an out-of-band registry it never specified.

RFC 9989 replaces this with a bounded DNS Tree Walk. The receiver queries _dmarc. at the Author Domain, then at successively higher parent labels, until it finds a record:

flowchart TD
    A["Query _dmarc.a.mail.example.com"] --> B{Valid DMARC record?}
    B -->|Yes| USE[Use it as policy]
    B -->|No| C["Query _dmarc.mail.example.com"]
    C --> D{Valid record?}
    D -->|Yes| USE2[Use it]
    D -->|No| E["Query _dmarc.example.com"]
    E --> F{Valid record?}
    F -->|Yes| USE3[Use it]
    F -->|No| G["Query _dmarc.com ... up to 8 queries total"]
    G --> H{Found / limit hit?}
    H -->|Limit| NONE[No DMARC applied]

Three precise rules from RFC 9989 §4.10:

  • The walk is capped at 8 DNS queries. RFC 9989 §4.10 (verbatim): “To guard against such abuse of the DNS, a shortcut is built into the process so that Author Domains with more than eight labels do not result in more than eight DNS queries.” For Author Domains with more than eight labels, the algorithm shortens to seven labels before walking. “Observed data at the time of publication showed that Author Domains with up to seven labels were in usage, and so eight was chosen as the query limit.”
  • A record carrying psd=n or psd=y stops the walk. psd=n means “this is the Organizational Domain”; psd=y means “this is a Public Suffix Domain, so the Organizational Domain is one label below.” (psd=u, the default, means “use the Tree Walk to determine the Organizational Domain.”)
  • Organizational Domain selection (§4.10.2): prefer a psd=n record; else, if a psd=y record is found above the start, the Org Domain is one label below it; else use the record found at the name with the fewest labels.

The operational consequence is important: a receiver running RFC 9989 may resolve a different Organizational Domain than a receiver still on the PSL. RFC 9989 itself states the way to avoid this ambiguity is Strict Alignment plus publishing an explicit DMARC record at every domain and subdomain you actually send from. That is the single most robust subdomain practice during the transition years.

Subdomain inheritance and override

The resolved policy applies to a subdomain unless a more specific record exists. The precedence for a subdomain news.example.com:

  1. An explicit _dmarc.news.example.com record wins outright.
  2. Otherwise the Tree Walk finds _dmarc.example.com, and the subdomain is governed by that record’s sp= (if present) or p= (if not), or np= if the subdomain does not exist.

Non-mailing domains and parked domains

A domain (or subdomain) that never sends mail is still a spoofing target. The defensive posture for a non-sending name has three records:

example-parked.com.        TXT  "v=spf1 -all"
_dmarc.example-parked.com. TXT  "v=DMARC1; p=reject; sp=reject; rua=mailto:dmarc@example.com"
*._domainkey.example-parked.com. TXT  "v=DKIM1; p="    ; null/empty DKIM key

v=spf1 -all asserts that no host is authorized. p=reject; sp=reject tells receivers to reject anything claiming the domain. The empty-key (p=) DKIM record is a revocation signal. RFC 9989’s np=reject complements this for never-provisioned subdomains.

Null MX (RFC 7505). A domain that receives no mail can publish a null MX, a single MX record with priority 0 and target .:

example-parked.com.  MX  0 .

This tells sending MTAs immediately and authoritatively that the domain accepts no mail, eliminating delivery retries against a fallback A record. It is an inbound-side complement to the outbound SPF/DMARC lockdown.

Subdomain sprawl and segregation strategy

Large organizations accumulate subdomains across departments, regions, acquisitions, and SaaS integrations. Two structural approaches, with a real trade-off:

StrategyHowTrade-off
ConsolidatedOne Organizational-Domain record; subdomains inherit via sp=Simple to maintain; but a single sp= can’t express per-subdomain nuance, and the Tree Walk falls back to the parent for any subdomain lacking its own record
SegregatedExplicit _dmarc record on each sending subdomainPer-stream policy and reporting; isolates reputation (e.g. mkt.example.com failures don’t touch the corporate domain); but more records to manage and audit

A common hybrid: a strict Organizational record (p=reject; sp=reject), explicit lenient records on the specific subdomains that send through third parties, and np=reject to slam the door on invented subdomains. Segregation also isolates reputation: marketing blasts from mkt.example.com don’t drag down the deliverability of transactional mail from example.com.

Third-party / delegated subdomains (ESP and marketing platforms)

When a marketing platform sends “as” your domain, you need its mail to align with your From domain. Two delegation patterns:

  • CNAME delegation. You publish CNAMEs (for the DKIM selector and a custom Return-Path/bounce subdomain) pointing at the provider. The provider then controls the actual SPF/DKIM record content behind those names and can rotate keys without involving you. This gives DMARC-aligned authentication under relaxed alignment because the bounce subdomain and DKIM d= live under your domain. Caveats: a CNAME counts toward the SPF 10-lookup budget, and CNAMEs on the apex can collide with MX and other records.
  • Full subdomain (NS) delegation. You delegate an entire subdomain (e.g. news.example.com) to the provider’s nameservers; they own all of its DNS. Cleaner isolation, but you cede control of that zone.

The recommended pattern most ESPs converge on is a dedicated sending subdomain (e.g. mail.client.com) with its own SPF, DKIM, and DMARC, which both aligns and isolates reputation. The anti-pattern is letting the provider sign only with its domain (d=esp.com), that never aligns and guarantees DMARC failure for your From domain.

BIMI and brand protection at the subdomain level

BIMI (Brand Indicators for Message Identification) displays a brand logo in supporting inboxes, but it is gated on enforcement. The hard prerequisite: the sending domain’s DMARC policy must be p=quarantine or p=reject, p=none never qualifies. Most major providers (notably Gmail) additionally require that enforcement apply to all mail, meaning a pct value below 100 (in legacy records) disqualifies the logo even when p=reject is set. The record lives at default._bimi.<domain> and references an SVG logo (l=) and, for Gmail and Apple Mail, a Verified Mark Certificate (VMC) or Common Mark Certificate (CMC) (a=) proving rights to the logo, a VMC requires a registered trademark, and the trademark process itself can take 6–12 months. For subdomains, the practical guidance is to enforce DMARC at both the organizational domain and the sending subdomain, avoid sp=none where subdomains need BIMI, and publish at the Organizational Domain so VMC verification can be inherited. The operational reading of BIMI: it is a consequence of authentication maturity, not a security control in itself, the logo is the visible reward for having reached enforcement.

Abandoned / transitioning subdomains

A subdomain being decommissioned or migrated is a window of risk. The defensive sequence: once a subdomain stops sending, lock it down with v=spf1 -all, an explicit _dmarc record with p=reject, a null DKIM key, and a null MX if it also stops receiving, exactly the parked-domain posture. The common misconfiguration is leaving a permissive SPF include or a live DKIM selector in place after a service is retired, which is precisely what creates void-lookup failures and reusable signing surfaces.


SECTION 4: THE TOOLING LANDSCAPE

The tooling ecosystem is large, the community-maintained dmarcvendors.com directory lists dozens of analytics platforms, hosted SPF/DKIM services, validators, and self-hosted projects. What matters for an engineer is not vendor ranking but functional category and the build-vs-buy trade-off in each. The categories below are organized by the job they do.

Functional categories

CategoryJob to be doneOpen-source / self-hostedHosted / commercial examples
Report analysis & visualizationIngest RUA XML, identify senders, dashboard trends, alert on anomaliesparsedmarc + Elasticsearch/OpenSearch + Kibana/Grafana; DMARC-SRG; dmarcts-report-viewerDmarcian, Valimail, PowerDMARC, EasyDMARC, Red Sift OnDMARC, URIports, Postmark DMARC Digests, Cloudflare DMARC Management
SPF optimizationStay under the 10-lookup limit; flatten or macro-expandWordToTheWise SPF minimizer, cfspflat, spf-toolsAutoSPF, hosted “PowerSPF”-style services, Fraudmarc, Valimail
DKIM managementKey generation, multi-selector rollout, rotationopenssl + DNS automationESP-integrated rotation (Microsoft 365, Mailgun auto-rotation)
Validation & testingCheck records, inspect headers, simulate flowsScott Kitterman’s pyspf validator, Vamsoft SPF testerMXToolbox, dmarcian SPF Surveyor, learndmarc.com, reflector services
Hosted DMARC record managementManage the published record dynamicallyNoneMost analytics vendors offer “hosted DMARC” via CNAME to their record

Report analysis & visualization

This is where most of the work happens. Raw RUA is unreadable at scale, even a small domain receives dozens to hundreds of report files per month, each gzip-compressed XML keyed by source IP. The functional requirements that distinguish a usable solution from a parser: sender identification (mapping IPs to named organizations via reverse DNS), trend analysis over time, anomaly/alerting on new senders or pass-rate drops, and storage/retention.

parsedmarc is the de facto open-source base: a Python parser that reads RUA/RUF from a directory or IMAP/Graph/Gmail inbox and ships structured data to Elasticsearch, OpenSearch, Splunk, or PostgreSQL for premade Kibana/Grafana dashboards. Notably, parsedmarc already parses both the RFC 7489 schema and the new RFC 9990 schema. The honest operational caveat from practitioners running it: the most fragile point is IMAP polling, which can fail silently, credentials expire or the mailbox server changes settings, and the dashboard keeps showing stale data for days. The standard mitigation is an external healthcheck that alerts if the document count hasn’t grown in 48 hours.

SPF optimization: flattening vs. dynamic, the central trade-off

The 10-lookup limit forces a choice when an organization’s include chain grows too long:

  • Flattening resolves all include/a/mx mechanisms down to literal ip4:/ip6: ranges, collapsing DNS lookups toward zero. The trade-off is maintenance and correctness risk: providers change their IP ranges without notice, and a stale flattened record silently fails legitimate mail. Flattening trades a lookup-count problem for a freshness problem, which is why automated/hosted flatteners that re-resolve on a schedule exist.
  • SPF macros (the exists: mechanism with macro expansion such as %{i}) delegate resolution dynamically, keeping the published record short. The trade-off is complexity and limited tooling support, many validators mis-evaluate macros, and the logic is opaque.
  • Dedicated subdomains for distinct sending services spread sending across multiple SPF namespaces, each with its own budget.

There is no free option here: flattening trades freshness for lookup count; macros trade simplicity for dynamism; subdomains trade a single record for structural complexity.

DKIM management: keys, selectors, rotation

The functional concerns: key length (2048-bit RSA baseline per M3AAWG; 1024-bit minimum fallback; note 2048-bit may require a multi-string TXT record), multi-selector setups (separate selectors per mail stream/vendor so a compromise or rotation is contained), and rotation cadence. The M3AAWG DKIM Key Rotation Best Common Practices (Updated March 2019) states verbatim: “In this 2019 update, the recommended key rotation cycle has been revised from quarterly to every six months”, i.e., DKIM keys should be rotated at least every six months. The overlap technique is the entire trick: deploy the new public key in DNS at least 48 hours before activating the private key, sign with the new selector once propagation is confirmed, and retire the old DNS record only after at least 7 days (up to 30) so in-transit mail still verifies. The recurring organizational failure mode is that nobody owns rotation, DNS sits with IT, signing config with the messaging team, policy with security, so keys go stale for years.

Validation & testing

Three sub-functions: record validators (syntax, lookup count, alignment-mode checks), header inspection (reading Authentication-Results on real received mail to see actual SPF/DKIM/DMARC outcomes), and reflector services that you email to receive a full authentication report back. The end-to-end test that matters before tightening policy is: send representative mail from every legitimate stream, inspect headers and aggregate reports, and confirm each stream produces an aligned pass.

Hosted vs. self-hosted: the decision

DimensionSelf-hosted (parsedmarc + ELK/Grafana)Hosted platform
Cost shapeLow licensing; ongoing engineering time (storage, upgrades, dashboards, alerting)Subscription scaling with domains/volume
Data controlReports stay internal, strong for privacy/regulatory needsReport metadata flows to the vendor
Operational burdenYou own ingestion reliability (IMAP), index management, healthchecksVendor owns uptime and parsing
Sender identificationManual / community GeoIP + reverse DNSCurated sender databases, named-vendor mapping
Best fitPrivacy-heavy teams with existing logging/SRE skills and timeBusiness-critical domains, MSPs, multi-tenant, teams needing shared workflows and alerting

A practitioner heuristic that holds up: a weekly email digest is fine for a personal domain; once mail is business-critical, volume is rising, or more than one person must understand the data, the hidden labor of self-hosting (alerting, classification, retention, upgrades, fixing sender ownership) usually outweighs the licensing savings. Hybrid approaches (self-hosted ingestion feeding a SIEM the security team already runs) are common where DMARC telemetry needs to live alongside other security data.


SECTION 5: A PRACTICAL ROADMAP

This is a description of the technical work and its dependencies, framed as what each phase accomplishes operationally. Treat the timelines as typical ranges, not commitments, the dominant variable is always the number of sending sources and how many are third parties you don’t directly control. Note also the external pressure shaping urgency: Google and Yahoo require DMARC for bulk senders at 5,000 messages per day, and DMARC is also referenced by PCI DSS 4.0 and the EU’s NIS2 regime.

Phase 1: Baseline audit (typically the first few weeks)

Activities: discover existing SPF/DKIM/DMARC records across all domains and subdomains; build a sender inventory, every internal system, every SaaS/ESP, every regional or departmental sender; map the mail flow; and stand up report ingestion by publishing a p=none record with a working rua= endpoint (hosted platform or self-hosted parser).

Key insight: you cannot inventory senders by asking around, shadow IT and forgotten integrations only surface in aggregate reports. The p=none reporting phase is the discovery mechanism. RFC 9989 §8 now codifies “set up a mailbox to receive aggregate reports and collect and analyze those reports” as a MUST for full participation.

Exit metric: reports flowing and a stable, near-complete list of sending sources with their current alignment status.

Phase 2: Remediation (the long pole; weeks to a few months)

Activities, in dependency order:

  1. Construct/consolidate SPF: one record per domain, within the 10-lookup and 2-void-lookup limits; flatten or subdomain as needed.
  2. Deploy DKIM: generate 2048-bit keys, publish selectors, enable signing on every stream; for third parties, configure custom signing domains so d= aligns.
  3. Align third-party senders: the slowest work, because it depends on each vendor’s capabilities and on coordinating CNAME/return-path/DKIM delegation. Some vendors simply can’t align; those become exceptions.
  4. Keep p=none while monitoring reports until every legitimate stream shows an aligned pass.

Key insight: this phase is gated by the least cooperative third party, not by your own infrastructure. Alignment of first-party mail is usually quick; the long tail is a marketing tool, a billing platform, or an acquired business unit that needs vendor coordination.

Exit metric: all known-legitimate mail produces aligned SPF or aligned DKIM; the only remaining failures in reports are forgery, forwarding, or documented exceptions.

Phase 3: Refinement & enforcement (weeks, paced by report stability)

Activities: tighten policy in stages (p=nonep=quarantinep=reject) using t=y test mode to apply a one-level-weaker policy while validating; refine sp= and add np=reject; set up RUF only if a receiver you care about emits it (most don’t) and you can handle the PII; document exceptions/overrides; and establish ongoing monitoring as a steady-state function, not a project.

Key insight: the protocol no longer gives you a percentage dial (pct is gone). RFC 9989’s own staged guidance for reject-bound domains is a month at p=none, then a comparably long period at p=quarantine, comparing dispositions before reject. Move to the next state only when reports are “boring”, predictable sources, stable pass rates, no surprise high-volume failures.

Exit metric: p=reject (or p=quarantine where mailing-list exposure makes reject inadvisable per RFC 9989 §7.4) with sustained clean reports; optionally, BIMI becomes available once enforcement is reached.

Dependency graph

flowchart TD
    A[Discover existing records] --> B[Sender inventory]
    B --> C[Publish p=none + rua endpoint]
    C --> D[Collect aggregate reports]
    D --> E[Construct/consolidate SPF<br/>within 10-lookup limit]
    D --> F[Deploy DKIM 2048-bit + selectors]
    F --> G[Align third-party senders<br/>CNAME / custom return-path]
    E --> H[All legit mail aligned?]
    G --> H
    H -->|No| D
    H -->|Yes| I[p=quarantine / t=y test]
    I --> J[Reports stable?]
    J -->|No| D
    J -->|Yes| K[p=reject + sp= + np=reject]
    K --> L[Steady-state monitoring]
    K --> M[Optional: BIMI + VMC]

Cross-team and external dependencies

Email authentication is unusual in that the work spans three teams who rarely share tooling: DNS (often network/IT), mail/signing config (messaging/platform), and policy (security). The classic failure mode (stale DKIM keys, orphaned SPF includes) is organizational, not technical. External dependencies are worse: every third-party sender is a separate coordination thread, on its own schedule, with its own support process. Change management matters because the transition from p=none to enforcement is the point where a missed sender turns into rejected business mail.

Timeline drivers and risk

AcceleratorDelay factor
Few sending sources, all first-partyMany third-party senders requiring coordination
Existing report aggregation toolingMailing-list exposure (forces slower, DKIM-dependent path)
Single DNS authority, automatedSubdomain sprawl / multiple business units
Vendors that support custom DKIM/return-pathVendors that can’t align (permanent exceptions)

Quick wins: publish p=none with reporting today (zero deliverability risk, immediate visibility); add np=reject (cheap protection against invented-subdomain spoofing with no effect on real mail); lock down parked/non-sending domains with v=spf1 -all, p=reject, null DKIM, and null MX. Long-tail work: aligning every third-party sender and safely reaching p=reject on the primary sending domain.

Risk-and-mitigation summary:

RiskMitigation
Tightening policy blocks legitimate mailDon’t advance a state until reports are clean for a sustained period; use t=y test mode
Forwarded/mailing-list mail rejectedPrefer DKIM alignment (survives forwarding); consider p=quarantine over reject where lists matter (RFC 9989 §7.4); evaluate ARC at receivers that honor it
SPF silently breaks (permerror)Monitor lookup count; prefer DKIM as the primary alignment path; flatten with automation if needed
Tree-walk vs PSL ambiguity during transitionPublish explicit _dmarc records at every sending domain and subdomain
Stale DKIM keys / orphaned includesAssign clear ownership; rotate on a fixed cadence; audit includes when retiring services

Closing synthesis

The mechanics reduce to a small number of load-bearing ideas. SPF and DKIM authenticate domains that the user never sees; DMARC’s contribution is alignment (binding one of those authenticated domains to the visible From:) plus a feedback loop that makes the whole system observable. Everything operationally hard about DMARC flows from two facts: the authenticated identifiers can legitimately differ from the From domain (which is why third-party senders and forwarding are perennial pain), and the protocol gives you visibility before it gives you enforcement (which is why the discipline is inventory-then-align-then-tighten, not flip-the-switch).

RFC 9989 (DMARCbis) doesn’t change that shape. It hardens the edges: a deterministic Tree Walk instead of a fragile external list, a binary test-mode instead of an unreliable percentage, an explicit policy for non-existent subdomains, and a candid acknowledgment (written into the standard itself) that p=reject is a strong signal receivers may temper, that mailing-list domains should be cautious about it, and that reject without DKIM is a mistake. The throughline, present since RFC 7489 and reaffirmed in RFC 9989, is that a DMARC pass is not a statement that mail is safe or wanted, it is a statement that the From domain was used by someone authorized to use it. Treating it as anything more is the most common conceptual error in the field.


If your DNS is scattered across registrars, or you’re planning a nameserver migration, see our guide on migrating DNS from GoDaddy to Cloudflare for how to move SPF/DKIM/DMARC records without breaking mail. If you’d rather have someone audit and implement this end to end, that’s exactly the kind of work GRB Digital does as part of our cybersecurity and managed IT services.