DMARC failure troubleshooting starts in one place: the raw Authentication-Results header, not a vendor dashboard. Dashboards aggregate and summarize; they can obscure the exact token that explains why a specific message failed. The header is the ground truth. This playbook walks through every distinct failure mode, maps each one to a concrete fix path, and covers the forwarding edge case that catches even well-configured domains off guard — updated for the current DMARC standard, RFC 9989, published May 2026 as a Standards-Track replacement for the original 2015 RFC 7489.
Start Here: Reading the Authentication-Results Header
Anatomy of the Authentication-Results Header Field
The Authentication-Results header is inserted by the receiving mail server after it evaluates SPF, DKIM, and DMARC. Its structure follows RFC 7601 — unchanged by the DMARC update — and uses a consistent grammar: an authserv-id (the evaluating server's identity), followed by one or more method clauses, each containing a result and optional property sub-clauses.
A typical block looks like this:
Authentication-Results: mail.example.com;
spf=pass (mailfrom) smtp.mailfrom=bounce.sender.com;
dkim=fail (body hash did not verify) header.d=sender.com header.s=selector1;
dmarc=fail (p=quarantine) header.from=sender.com
Break it down token by token:
- authserv-id:
mail.example.com, the receiving server that wrote this header. - spf=pass: SPF evaluated the envelope sender (
smtp.mailfrom=bounce.sender.com) and found a valid record. Note the domain:bounce.sender.com, notsender.com. - dkim=fail: The DKIM signature for
header.d=sender.comusing selectorselector1did not verify. The reason is in parentheses: body hash mismatch. - dmarc=fail: Despite
spf=pass, DMARC failed. Theheader.from=sender.comtells you the domain being evaluated.
For a broader foundation on reading email headers to diagnose delivery failures, the token grammar extends well beyond authentication results, but DMARC diagnosis begins and ends with this block.
How to Spot a DMARC Fail in Three Lines
Look for three things in sequence:
dmarc=fail— confirms DMARC itself failed, not just a component.- The
spf=anddkim=results — identify which mechanisms passed or failed. - The domains in
smtp.mailfrom=,header.d=, andheader.from=— alignment mismatches show up here as differing domains.
A message with spf=pass and dmarc=fail in the same header is not contradictory. It is the most common alignment failure pattern in production.
DMARC Fail Reasons: The Four Root Causes
DMARC requires at least one aligned passing mechanism — either SPF or DKIM must both pass and align with the header.from domain. A message can carry spf=pass in the Authentication-Results block and still fail DMARC if the passing domain does not match header.from under the configured alignment mode. This core either/or logic is unchanged under RFC 9989 — it is still the mechanism most frequently misunderstood in production.
The four root causes are:
- SPF alignment fail — SPF passed, but on the wrong domain.
- SPF permerror or none — SPF itself did not pass at all.
- DKIM missing or selector not found — no valid signature exists to evaluate.
- DKIM body hash mismatch — a signature exists but failed verification.
SPF Alignment Fail vs. SPF Permerror
Alignment fail: SPF evaluated the envelope sender (the Return-Path / smtp.mailfrom domain) and returned pass. But that domain — say bounces.esp.com — does not match or subdomain-match sender.com in the From: header. Under strict alignment, the domains must be identical. Under relaxed alignment (the default), they must share an organizational domain. bounces.esp.com and sender.com share no organizational domain, so relaxed alignment also fails.
One structural change worth knowing: RFC 9989 replaces the old Public Suffix List (PSL) lookup with a DNS Tree Walk to determine the organizational domain boundary. Instead of consulting an externally maintained list, the receiver queries DNS directly for a _dmarc record, walking up one label at a time from the sending domain toward the root, stopping at the first valid record it finds. For the vast majority of straightforward domains, this produces the same organizational-domain answer as the old PSL method — but if you run a complex subdomain hierarchy, the safest practice is to publish an explicit DMARC record at every domain and subdomain you actually send from, removing any ambiguity about which policy applies.
SPF permerror: The SPF record has a syntax error, exceeds the 10 DNS lookup limit, or is missing entirely (spf=none). In these cases, SPF cannot return a useful result. DMARC treats none and permerror as non-passing — they cannot satisfy the alignment requirement regardless of what the domains look like.
DKIM Signature Fail and Body Hash Mismatch
Selector not found: The _domainkey DNS record for the selector named in the d= and s= tags does not exist or has been removed. The receiving server cannot retrieve the public key, so verification fails immediately. Authentication-Results will show dkim=fail (no key for signature) or similar.
Body hash mismatch: The signature exists, and the public key resolves, but the computed hash of the received message body does not match the hash in the bh= tag. Per RFC 6376 Section 3.5 — the DKIM specification, unaffected by the DMARC update — a body hash mismatch almost always means the message body was modified in transit: a content-scanning gateway rewrote content, a mailing list manager appended a footer, or line-length refolding altered whitespace in the canonicalized body.
DKIM domain misalignment: A signature can verify cleanly (dkim=pass) but still not satisfy DMARC if the header.d= domain does not align with header.from. A valid DKIM signature from esp-platform.com does nothing for DMARC alignment if the From: address is @yourdomain.com.
Email Forwarding: Why It Breaks DMARC (and How to Confirm It)
Forwarding is the most common source of DMARC failures for senders who have done everything else correctly. The mechanism is straightforward: when a mailbox provider or alumni redirect service forwards a message, it rewrites the envelope sender to its own domain. The original Return-Path is replaced. SPF now evaluates against the forwarder's domain, which has no relationship to the original header.from. SPF fails or passes on the wrong domain — either way, SPF alignment for the original sender is broken.
DKIM survives forwarding as long as the message body and signed headers are not modified. But forwarders frequently append disclaimer footers, rewrite Subject: lines, or add transport headers that alter the canonicalized content. Any of these changes breaks the body hash, producing dkim=fail (body hash did not verify).
A message forwarded through a university alumni redirect service illustrates the worst case: the envelope From is rewritten to the forwarder's domain (SPF alignment broken), and the forwarder appends a disclaimer footer (DKIM body hash broken). The result is dmarc=fail even though the original sender was fully authenticated at send time.
How to confirm forwarding is the cause:
- Examine the
Received:chain. Look for hops through a domain that is neither the original sender nor the final recipient's server. For full delivery path and header forensics, theReceived:chain tells you every server that handled the message. - Check
smtp.mailfrominAuthentication-Results. If it shows a domain unrelated toheader.from, a forwarder has rewritten the envelope. - Look for an
ARC-Authentication-Results:header chain. Receiving servers that implement Authenticated Received Chain (ARC, RFC 8617 — also unaffected by the DMARC update) preserve the original authentication state across forwarding hops, giving you a record of what passed before the forward. - Some implementations include
mtype=forwardedor anX-Forwarded-To:header, present in some redirect services but not standardized.
Fix Paths: Mapping Each Failure Mode to a Concrete Remedy
Fixing SPF Alignment Failures
The alignment is wrong, but SPF itself passes:
Set the Return-Path domain (the bounce address / envelope sender) to a subdomain of your header.from domain. If your From: is @yourcompany.com, configure your ESP to use bounces.yourcompany.com as the envelope sender. Add an SPF record for bounces.yourcompany.com. Under relaxed DMARC alignment (the default aspf=r), bounces.yourcompany.com aligns with yourcompany.com because they share the organizational domain.
If you cannot control the envelope sender, some legacy sending infrastructure does not expose this, set relaxed alignment explicitly in your DMARC record: v=DMARC1; p=none; aspf=r. Relaxed is the default, so this only matters if you previously set aspf=s.
SPF permerror:
Fix the SPF record syntax. The most common cause is exceeding 10 DNS lookups. Flatten includes using a tool that resolves all include: chains and publishes the resulting IP ranges directly, eliminating recursive lookups.
Fixing DKIM Failures
Selector not found:
Publish the DKIM public key at selector._domainkey.yourdomain.com as a TXT record. Verify publication with a DNS lookup: dig TXT selector1._domainkey.yourdomain.com. Confirm the p= value matches what your signing system holds for the private key.
Body hash mismatch in transit:
Identify the modifying hop by tracing the Received header chain and correlating it with the point where the body changed. Options:
- Switch to
relaxedbody canonicalization (c=relaxed/relaxedin the DKIM signature). Relaxed canonicalization normalizes whitespace and line endings, tolerating minor reformatting without breaking the hash. - If a mailing list or forwarder appends a footer, the only durable fix is ARC — see below.
- If a content-scanning gateway is the culprit, work with the gateway operator to configure it to re-sign after scanning, or route around it.
DKIM domain misalignment:
Configure your signing infrastructure to sign with a key whose d= value matches or aligns with your header.from domain. A DKIM signature from esp-platform.com must be replaced or supplemented with a signature from yourdomain.com (or a subdomain of it under relaxed adkim=r).
For legitimate forwarding paths:
ARC (RFC 8617) is the recognized remedy. ARC-compliant intermediaries seal the original authentication results before forwarding. Receiving servers that evaluate ARC can use the sealed chain to apply a policy exception for messages that passed authentication before the forwarding hop. ARC does not fix DMARC by itself — the receiving server must implement ARC evaluation and choose to trust the chain.
Using DMARC Aggregate Reports to Diagnose at Scale
Per-message header inspection tells you why one message failed. DMARC aggregate reports, delivered to the address in your rua= tag, tell you which sending sources are failing across thousands of messages per day. Aggregate reporting now lives in its own document, RFC 9990, split out from the core protocol.
Aggregate reports are XML files delivered daily by participating receivers. Each report covers a 24-hour interval and contains, for each source IP: the header.from domain evaluated, the SPF and DKIM pass/fail counts, the DMARC disposition applied (none, quarantine, reject), and the policy override reasons (forwarding, local policy, etc.). Under RFC 9990, reports gained several fields that RFC 7489 reports did not have: a discovery_method field showing whether the receiver used the legacy PSL or the new Tree Walk to find your organizational domain, a testing flag showing whether your record's t= tag was active, and (if published) the applied np= value for non-existent subdomains. If you run your own report parser, these new fields are additive — well-formed older reports still parse.
A single day's aggregate report from a major mailbox provider can surface dozens of misconfigured sending sources: a forgotten transactional mail server, a third-party CRM sending on your domain without DKIM, a partner integration using your From: domain with its own bounce path — issues that would take weeks to find through per-message header inspection alone.
Use aggregate reports to:
- Identify every IP sending mail that claims your
header.fromdomain. - Determine the SPF and DKIM pass rates per source.
- Spot sources with consistent alignment failures before you escalate policy.
Parse reports with any DMARC reporting tool, or write a transform against the RFC 9990 schema. Do not move to quarantine or reject policy without first confirming that all legitimate sending sources appear in reports with passing aligned results.
DMARC Policy: When to Move from Quarantine to Reject
This section changed the most under the current standard, and it's worth being precise about it.
Under the old RFC 7489, staged rollout was handled with the pct= tag — publishing something like p=reject; pct=10 to apply the policy to only 10% of failing mail while the rest fell back one enforcement level. In practice, receivers implemented pct= inconsistently: some sampled by total traffic, some by failing traffic only, and behavior at values other than 0 or 100 was genuinely unreliable across the receiver ecosystem. RFC 9989 removes pct= entirely (see the RFC's own Appendix A.6 for the rationale) and does not replace it with an equivalent staged-rollout mechanism. It replaces it with something simpler but blunter: the t= (testing) tag.
t= is binary:
t=y: the published policy (p=,sp=, and/ornp=) is not enforced. Receivers evaluate and report on it exactly as if it were live, but take no enforcement action — functionally similar to whatpct=0used to signal.t=n(the default): full enforcement of whatever policy is published.
There is no t= equivalent of "apply enforcement to 25% of failing mail." The staged-rollout problem that pct= was trying to solve is not solved by the new spec — it's handed back to the operator. In practice this means:
p=none: Monitor only, as before. Use this while collecting aggregate report data and fixing sending sources. Nothing here changes.p=quarantine; t=y: You've decided on quarantine as your next step, but want receivers to report on what would happen without actually affecting mail. Use this as a dry run.p=quarantine; t=n(or justp=quarantine): Full quarantine enforcement. Because there's no percentage dial anymore, plan to run this for a full reporting cycle (RFC 9989 §8 implies daily reports; two to four weeks is a reasonable minimum) and watch aggregate reports closely for legitimate senders still failing.p=reject; t=y: Dry run for full rejection — see what would be blocked without blocking it.p=reject; t=n: Full enforcement.
If you're migrating an existing record that still has pct= in it, you don't need to panic — RFC 9989 §4.7 still requires receivers to ignore unrecognized tags, so a lingering pct= tag won't break anything. But don't rely on it: receivers that have moved to the new standard will ignore it silently, which means your "gradual" rollout is actually full enforcement on those receivers already. The practical move is to strip pct= at your next DNS edit and rely on report-driven confidence plus t=y dry runs instead of a percentage dial.
Signals that indicate readiness to move to full enforcement (t=n):
- Aggregate reports show near-100% SPF or DKIM alignment pass rates across all known sending sources.
- No legitimate source is producing consistent failures.
- Forwarding paths that matter are protected by ARC, or recipients have been migrated to direct delivery.
- A
t=ydry run at your target policy level has run for at least one full reporting cycle with no unexpected sources appearing in the reports.
Rushing to p=reject; t=n without a t=y dry run first is how organizations accidentally reject their own transactional mail.
One more current-standard addition worth knowing about: if part of your forwarding or spoofing problem involves attackers using subdomains that don't actually exist in your DNS (a common phishing pattern), RFC 9989 adds an np= tag specifically for this — letting you set np=reject for non-existent subdomains while keeping p= and sp= more lenient for your real, still-onboarding infrastructure. That's a separate topic from rollout staging, but it's part of the same record and worth knowing about while you're already updating it.
This playbook covers the diagnostic chain from raw header to remediation, current as of the RFC 9989/9990/9991 standard published May 2026. The full authentication stack — SPF record mechanics, DKIM key management, ARC chain validation, BIMI readiness — has more depth than any single guide can hold. Email Decoded is a complete chapter-driven technical reference built for practitioners who need to master that stack with the same rigor applied here. It covers every authentication protocol, header field, and failure mode with the same dissect-the-specimen approach, from SMTP handshake to inbox. If this playbook answered your immediate question, the book answers the next ten.