DMARC Alignment Explained: How SPF and DKIM Finally Protect the From Header
SPF authenticates the envelope sender.
DKIM authenticates a signing domain. Neither ever
looks at the From: header — the only sender identity your users actually
see. A mail can pass both checks and still display ceo@example.com to the
victim. DMARC is the layer that closes this gap, and alignment is the
concept that makes it work. If you only ever internalise one thing about
email authentication, make it this article.
As with the companion pieces, the angle is analyst-side: understanding verdicts and checking records, not deploying DMARC for your own domain.
Pre-requisites
- Working knowledge of SPF and DKIM — DMARC builds directly on both
- A raw mail with full headers — for Outlook
.msgexports, see Open Outlook .msg Files on Linux digon your analysis machine
Examples use RFC 2606 reserved domains and RFC 5737 documentation IPs.
The spec moved in 2026
DMARC was originally specified in RFC 7489 (2015, Informational). Since
May 2026 the current specification is
RFC 9989 (Standards
Track), with aggregate reporting split into RFC 9990 and failure
reporting into RFC 9991 — together they obsolete RFC 7489. Notable
changes: the pct= tag was removed (a t= test-mode tag replaces it),
a np= tag covers non-existent subdomains, and the organizational
domain is now found via a DNS Tree Walk instead of the Public Suffix
List. Most blog posts and many records you will encounter still reflect
the 2015 spec.
Three Domains, One Mail
Every message carries up to three independent sender domains:
| Domain | Checked by | Visible to the user? |
|---|---|---|
MAIL FROM / Return-Path: (RFC5321.MailFrom) |
SPF | No |
d= in DKIM-Signature: |
DKIM | No |
From: header (RFC5322.From) |
DMARC | Yes — this is the one that gets spoofed |
An attacker controls the first two outright: they send from their own
envelope domain (SPF passes) and sign with their own DKIM key (DKIM passes).
DMARC's contribution is to ask the question the other two skip: do any of
those authenticated domains match the domain in the visible From:?
How DMARC Decides
For the From: domain, the receiver fetches the DMARC policy record and then
evaluates:
DMARC passes if at least one of the following holds:
- SPF gave
passand the SPF-validated domain is aligned with theFrom:domain, or- DKIM verified and the
d=domain is aligned with theFrom:domain.
Both mechanisms passing unaligned counts for nothing; one mechanism passing aligned is enough. This "either/or" is deliberate — SPF routinely breaks on forwarding while DKIM survives it, so requiring both would punish legitimate mail.
Alignment comes in two modes, set per mechanism via the aspf= and
adkim= tags (both default to relaxed):
- Relaxed (
r) — the domains must share the same organizational domain:mail.example.comaligns withexample.com. - Strict (
s) — the domains must be identical.
Under RFC 9989 the organizational domain is discovered with a DNS Tree Walk
(querying _dmarc. records up the name hierarchy); RFC 7489 used the Public
Suffix List for the same purpose.
Reading a DMARC Record
The policy lives in a TXT record at _dmarc.<domain>:
If no record exists at the From: domain itself, the receiver falls back to
the organizational domain's record — subdomains are covered by default. The
tags you will meet:
| Tag | Meaning |
|---|---|
v=DMARC1 |
Version, mandatory first tag |
p= |
Policy for the domain: none, quarantine, or reject |
sp= |
Separate policy for subdomains |
np= |
Policy for non-existent subdomains (new in RFC 9989) |
rua= |
Where aggregate reports go (mailto: URI) |
ruf= |
Where per-message failure reports go |
adkim= / aspf= |
Alignment mode per mechanism: r (default) or s |
t= |
Test mode t=y — evaluate but don't enforce (replaces pct= from RFC 7489; you will still see pct= in older records) |
What the three policies request from receivers:
| Policy | Domain owner's statement | Typical receiver behaviour |
|---|---|---|
p=none |
No preference — monitoring only | Mail delivered normally; failures only show up in reports |
p=quarantine |
Failing mail is suspicious | Junk folder / quarantine |
p=reject |
Failing mail is not valid use of the domain | SMTP-level rejection |
The analyst takeaway: p=none means a spoof of that domain lands in the
inbox even though DMARC evaluated fail — the header will read
dmarc=fail action=none. A domain's DMARC record tells you how spoofable it
is; check it for your own brand domains and for any domain being impersonated
in a campaign.
Reading the Verdict in Headers
Your gateway records the DMARC result in Authentication-Results:
(RFC 8601) — as always,
trust only the instance your own infrastructure stamped. Here is a complete
header from a spoofed mail, the case from the intro:
Authentication-Results: spf=pass (sender IP is 203.0.113.25)
smtp.mailfrom=example.org; dkim=pass (signature was verified)
header.d=example.org; dmarc=fail action=oreject
header.from=example.com; compauth=fail reason=000
Read it as a story:
spf=pass smtp.mailfrom=example.org— the attacker's server is authorised to send for the attacker's own envelope domain. Fine.dkim=pass header.d=example.org— the attacker validly signed with their own key. Also fine.dmarc=fail header.from=example.com— but neitherexample.orgresult aligns with theFrom:domain the user sees. The spoof is exposed.
header.from always names the domain DMARC judged — the visible one. In
Microsoft 365 the action= field shows what the filter did with the failure
(oreject = failed under p=reject, and the tenant's configured override
was applied instead of an SMTP reject; permerror/temperror = the record
could not be evaluated), and compauth is Microsoft's composite verdict that
folds DMARC together with its own heuristics — reason=000 means an explicit
DMARC fail against an enforcing policy.
A dmarc=none verdict means the From: domain publishes no DMARC record at
all: nothing constrains spoofing of it beyond content filtering.
Aggregate Reports (rua) in One Minute
The rua= address receives daily XML summaries from every large receiver:
which IPs sent mail claiming the domain, and how SPF/DKIM/alignment evaluated
for each source. Two things follow for you as an analyst:
- If your organisation publishes
rua=, those reports are a free detection feed for spoofing campaigns against your own domains — someone should be reading them (in practice via a parsing service, the raw XML does not scale). - The reports contain no message content —
ruf=failure reports would, but most receivers do not send them for privacy reasons.
Putting All Three Together
The mental model to keep from this series:
- SPF — "this IP may send mail for the envelope domain" — breaks on
forwarding, blind to
From: - DKIM — "the signing domain vouches for this exact content" — survives
forwarding, blind to
From: - DMARC — "does either authenticated domain align with the visible
From:, and what should you do if not"
Only the combination makes From: spoofing detectable — and only an
enforcing policy (p=quarantine/p=reject) makes it stoppable.
Check It Yourself
Query the DMARC policy of any domain in your case:
For a subdomain From:, check the subdomain first and the organizational
domain as fallback:
A v=DMARC1 answer with p=quarantine or p=reject means spoofs of the
domain should not reach inboxes at compliant receivers; p=none or no answer
means the visible From: is effectively unprotected.