EMAIL AUTHENTICATION
Everyone already told you who is spoofing your domain
Every mailbox provider that receives mail claiming to be from your domain will send you a daily report naming every source that tried. The reports arrive as compressed XML in a mailbox nobody opens, so domains sit at p=none for years while the one dataset that answers “is anyone impersonating us?” goes unread.
Why so many domains are stuck at p=none
In February 2024 Google, Yahoo and later Microsoft began requiring bulk senders — Google’s threshold is 5,000 messages a day to Gmail — to publish SPF, DKIM and a DMARC record. A large number of organisations published one that week and have not touched it since.
The reason is in the requirement itself. Google’s guideline states plainly that the enforcement policy can be set to none. So the compliant minimum is a record that instructs receivers to do nothing differently. It satisfies the gatekeeper and blocks no spoofing at all.
That is not a criticism of the requirement — p=none is the correct place to start, because it turns on reporting without risking real mail. It is only a problem when it becomes the permanent state, which is what happens when nobody reads what the reporting produces.
The specification moved in 2026. DMARC is now RFC 9989 (Proposed Standard, May 2026), which obsoletes the long-standing informational RFC 7489. Aggregate reporting is split into its own document, RFC 9990. Most DMARC material online still cites 7489.
What changed in RFC 9989
| Change | What it means in practice |
|---|---|
pct removed | The percentage rollout tag is gone. A new t tag expresses test mode instead. If you were planning a staged rollout with pct=25, that plan needs rewriting. |
np added | A separate policy for non-existent subdomains of the organizational domain — the ones attackers like, because nothing legitimate ever sends from them. |
psd added | Flags a record as belonging to a public suffix domain operator. |
| Tree walk replaces the PSL | Receivers now discover the organizational domain by walking DNS upward rather than consulting the Public Suffix List. One less external dependency in the evaluation path. |
| DKIM selector now required in reports | Aggregate reports must name the selector alongside the signing domain, which makes attributing a signature to a specific sending system materially easier. |
p still takes the same three values, and none remains valid. Nothing in the new specification forces you off it — that decision stays yours, and stays evidence-based.
What an aggregate report actually contains
This is worth being precise about, because both the fear and the hope around DMARC reports are usually misplaced.
Each record in the report carries: the connecting IP address, a message count, the disposition the receiver applied, the aligned SPF and DKIM results, the RFC5322.From domain, and the underlying authentication results including each DKIM signature’s domain, selector and outcome.
It does not carry: message content, subject lines, recipient addresses, or the mail addresses of individuals. RFC 9990 is explicit on this point. Aggregate reports are domain-level statistics, not copies of your mail.
So the report answers “which systems sent mail as us, in what volume, and did it authenticate” — and cannot answer “what did that message say” or “who received it.” Every practical use of DMARC monitoring follows from that boundary.
Alignment is the part people skip
An SPF pass is not a DMARC pass. SPF validates the envelope sender; DKIM validates whatever domain signed the message. DMARC additionally requires that the passing identifier align with the domain in the visible From: header.
This is precisely how a marketing platform can report healthy SPF while contributing nothing to your DMARC posture: it authenticates its own domain, not yours. In the aggregate report this appears as a source with an SPF pass and an alignment failure — a row that is easy to skim past and is usually the reason a domain cannot move off p=none.
Reading them by hand
For one domain and a handful of reporters this is entirely reasonable, and worth doing once even if you later automate it:
- Publish a record with a
rua=address you control:_dmarc.example.com. TXT "v=DMARC1; p=none; rua=mailto:dmarc@example.com" - Collect the attachments. They arrive daily, gzip-compressed, named
receiver!policy-domain!begin!end.xml.gz. - Decompress and group every record by source IP. Sum the counts.
- For each source, compute the share of messages with aligned SPF or DKIM — not raw pass rates.
- Identify every source. This is the slow part, and the point of the exercise: each one is a system someone in your organisation set up, and the list is almost always longer than anyone expects.
- Repeat weekly and diff, because the answer changes whenever someone signs up for a new SaaS.
Steps 3 to 6 are where doing it by hand stops scaling — not because any single step is difficult, but because it has to happen every week, forever, and the week it gets skipped is the week a new source appears.
The failure mode nobody watches for
Reports that stop arriving look exactly like reports with nothing to report. A typo in rua=, a mailbox that filled up, an address belonging to someone who left — any of these produces a silence that reads as “all clear.”
It is the same shape as a detector that never fires. If your monitoring cannot distinguish no findings from no data, it is not monitoring.
DmarcWatch
Self-hosted DMARC aggregate report monitoring. Upload the report files exactly as they arrive — .xml, .xml.gz or .zip — and it reports who sends as your domain, which sources are failing alignment at volume, when a new source appears, and whether the evidence supports tightening your policy. Single Go binary, SQLite, no telemetry: your mail metadata stays on your machine, which matters because a complete list of your sending systems is a useful document to an attacker too.
| Finding | Severity | Condition |
|---|---|---|
dmarc.spoofing | High | A source sent 10 or more messages failing both aligned SPF and DKIM |
dmarc.new-source | Medium | A sending source appeared for the first time in the last 7 days |
dmarc.alignment-drop | Medium | This week’s aligned rate is 10 or more points below the 30-day average |
dmarc.no-reports | Medium | No reports for 7 days or more — collection itself is broken |
dmarc.policy | Low | p=none in force, with a direct answer to whether tightening is safe yet |
The readiness call is the one that decides whether a domain ever leaves p=none: when alignment holds at 98% or better across multiple independent reporters, the evidence supports moving to p=quarantine, and from there to p=reject. That is a threshold you can argue with — but it is stated, and the data behind it is on screen.
Run it
Grab the tarball and SHA256SUMS from the releases page, then:
sha256sum -c SHA256SUMS
tar -xzf dmarcwatch-free-*-linux-amd64.tar.gz
./dmarcwatch
# dashboard on http://127.0.0.1:8429 — add your domain, upload reports
Honest limits
- Ingestion is upload-only in v0. There is no IMAP polling yet, so getting the files out of the mailbox is still a manual step.
- Aggregate reports only. No forensic (RUF) reports, and no enrichment of source IPs.
- The thresholds — 10 messages for spoofing, 98% for readiness — are fixed in v0. If they do not suit your domain, they are not yet yours to change.
- Analysis windows are 7 and 30 days.
- It reads reports; it does not change DNS. Moving to
p=quarantineis your edit, through your change process.
DMARC findings can be emitted as RFC 3164 syslog, so a spoofing finding can land in the same incident timeline as everything else: lightweight self-hosted SIEM.