When DMARC passes for most of your list but fails for a stubborn slice of recipients, the cause is almost never your SPF or DKIM keys. It is identifier alignment: the receiving provider authenticated a domain that does not match the domain in the visible From header, and different providers evaluate that match differently. The one-line answer: read your aggregate (RUA) reports record by record, compare the raw result in auth_results against the aligned verdict in policy_evaluated, and the split will point at a strict alignment mode, a forwarder, or a stale DNS cache.
Key takeaways
- A per-recipient DMARC split is an alignment problem, not an authentication problem: your keys are valid, but the authenticated domain does not tie back to the From domain for every receiver.
- DMARC passes when either SPF or DKIM both authenticates and aligns, so you only need one aligned identifier, per the Google Workspace DMARC requirements.
- The single most useful move is comparing
auth_results(raw pass or fail) withpolicy_evaluated(the aligned verdict) in the same RUA record. - Relaxed alignment is the default and matches at the organizational-domain level, so
aspf=soradkim=squietly breaks subdomain senders. - Forwarding and mailing lists rewrite the envelope, which fails SPF alignment while a body-signed DKIM survives, so the failing recipients cluster around forwarders.
- DNS TTL and caching mean a record change propagates unevenly, producing a genuine pass-for-some, fail-for-others window that resolves on its own.
What you need
- Edit or at least read access to the DNS zone for the From domain, so you can inspect the
_dmarcTXT record. - A DMARC record already publishing a
rua=address to an inbox or aggregator whose reports you can open. Without RUA you are blind to the recipient split. - A way to parse XML: a text editor,
xmllint, a short script, or a report analyzer. - The exact envelope (Return-Path) domain and the DKIM
d=domain your ESP signs with. In Klaviyo or a similar platform these are set when you configure a dedicated sending domain.
How to isolate which recipients fail DMARC alignment
- Confirm reporting is on and read your published policy. Alignment modes live in the same record:
Note thedig +short TXT _dmarc.example.com # v=DMARC1; p=none; rua=mailto:[email protected]; adkim=r; aspf=radkimandaspfvalues. If they are absent, both default to relaxed (r). - Collect at least seven days of aggregate reports. Each report is one XML file per receiving provider per day. Do not judge a split off a single report; providers batch on different schedules.
- For every
<record>, extract five fields:source_ip,identifiers/header_from(the domain the recipient saw),policy_evaluated/spfandpolicy_evaluated/dkim(the aligned verdicts), andauth_results(the raw checks). A short parser does this cleanly:
The element names above are the ones defined in the aggregate report schema of RFC 7489.import glob, xml.etree.ElementTree as ET for f in glob.glob("reports/*.xml"): root = ET.parse(f).getroot() org = root.findtext("report_metadata/org_name") for r in root.findall("record"): yield { "provider": org, "source_ip": r.findtext("row/source_ip"), "count": r.findtext("row/count"), "header_from": r.findtext("identifiers/header_from"), "eval_spf": r.findtext("row/policy_evaluated/spf"), "eval_dkim": r.findtext("row/policy_evaluated/dkim"), "auth_spf": r.findtext("auth_results/spf/result"), "auth_dkim": r.findtext("auth_results/dkim/result"), "dkim_d": r.findtext("auth_results/dkim/domain"), } - Group the parsed rows by
providerandheader_from. A per-recipient alignment split looks like this: the sameheader_from, witheval_spforeval_dkimreadingpassfor some providers or source IPs andfailfor others in the same window. - Separate authentication from alignment for the failing rows. If
auth_spfispassbuteval_spfisfail, SPF authenticated but did not align. The distinction between the raw check and the aligned verdict is exactly what these two blocks encode, as EasyDMARC's guide to reading aggregate reports lays out. - Check your alignment mode against your envelope domain. Under relaxed SPF (
aspf=r), an envelope ofbounce.example.comaligns to a From ofexample.combecause they share an organizational domain, defined via the public suffix list in RFC 7489 section 3.1. Underaspf=sthat same send fails SPF alignment, and only DKIM keeps DMARC alive. - Map the failing
source_ipvalues. If they resolve to a forwarder or a mailing list rather than your ESP, you have found the split: forwarding rewrites the envelope so SPF alignment breaks, which is the exact problem RFC 8617 (ARC) was written to work around.
Read the split as a table of causes
Once you have the parsed rows, the pattern of which recipients fail maps to a small set of causes. Match your symptom to the row below before changing anything.
| What the RUA shows | Likely cause | Which recipients fail | Fix |
|---|---|---|---|
auth_spf pass, eval_spf fail, DKIM aligned | Strict SPF alignment (aspf=s) with a subdomain envelope | All, unless DKIM carries them | Move to aspf=r, or align the envelope domain to the From domain |
| Pass from your ESP IPs, fail from unrelated IPs | Forwarding or mailing list rewriting the envelope | Recipients on forwarders and lists only | Rely on aligned DKIM; ask intermediaries to ARC-seal |
| Fail only in a narrow time window, then recovers | DNS TTL and caching after a record change | Providers still holding the old record | Wait out the TTL; lower TTL before future edits |
dkim_d is your ESP domain, not your From domain | DKIM signed with the platform domain, not aligned | Everyone, whenever SPF also does not align | Configure a branded DKIM d= on your sending domain |
| Fail only from one provider, pass elsewhere | Per-provider enforcement or evaluation difference | One mailbox provider's recipients | Confirm the provider honors policy; see the provider table |
Why providers disagree on the same message
Two receivers can evaluate one identical message and reach opposite DMARC verdicts, because enforcement and forwarding handling are not uniform. Gmail requires at least one of SPF or DKIM to both authenticate and align, and treats relaxed alignment as sufficient in most cases. Microsoft 365 honors a published p=quarantine or p=reject policy by default when the recipient domain's MX points to Exchange Online, per Microsoft's DMARC configuration reference. A forwarder in front of either one can break SPF alignment entirely.
| Receiver behavior | Gmail | Microsoft 365 (EOP) | A forwarder or list |
|---|---|---|---|
| Passes on one aligned identifier | Yes | Yes | Depends on what survives the hop |
| Honors published enforcement policy | Yes | Yes, when MX points to it | Not the enforcer; passes the message on |
| Effect on SPF alignment | Preserved for direct mail | Preserved for direct mail | Broken, envelope is rewritten |
| Effect on DKIM alignment | Preserved | Preserved | Survives if the body is unchanged |
| Reads prior-hop results via ARC | Yes | Yes, for trusted ARC sealers | Adds the ARC seal |
Troubleshooting
- Every report says pass, but a user swears it failed. Aggregate reports summarize streams, not individual messages, and a forwarded copy may never appear in your RUA. Ask the user for the message headers and read the DMARC line directly.
- A major provider is missing from your reports. Not every receiver sends RUA, and some batch slowly. Absence is not a pass; widen the window to two weeks before concluding.
- You just changed the record and results are split. That is TTL, not a bug. Confirm by checking whether the failing providers are reporting the old policy string, then wait for the TTL to expire.
- DKIM shows pass but DMARC still fails. Read
dkim_d. If it is your platform's shared domain rather than a subdomain of your From domain, the signature is valid but not aligned. Set up a branded signing domain. If you are also standing up a new subdomain to send from, sequence it against the reputation work in warming a new sending domain.
How to verify it worked
Do not trust a single test send. Confirm the fix two ways. First, in the next RUA cycle, the policy_evaluated pass rate for the affected header_from should approach 100 percent of your legitimate volume across every source_ip that belongs to you, with the earlier fail rows gone. Second, send one message to a Gmail account, open Show original, and confirm the header reads DMARC: PASS alongside SPF: PASS and DKIM: PASS. If you run multiple brands or sending domains, hold the aligned envelope and branded DKIM as a standing rule; the tradeoffs are covered in sending domain models for multi-brand senders.
Sources
- RFC 7489, DMARC, for alignment modes, the
adkimandaspftags, organizational-domain determination, and the aggregate report schema. - RFC 8617, Authenticated Received Chain (ARC), for how forwarding breaks SPF and how prior-hop results are preserved.
- Google Workspace, set up DMARC, for Gmail's alignment requirements.
- Microsoft, set up DMARC to validate email, for Microsoft 365 enforcement behavior.
- EasyDMARC, analyzing aggregate reports, for reading
auth_resultsagainstpolicy_evaluated.