DMARC · Aggregate reports
Your first DMARC report arrived. Here's how to read it
Each <record> row compresses many messages into one line: a source IP, a count, the policy evaluated, the identifiers claimed, and the raw authentication results underneath. Five passes over those rows turn XML into a to-do list.
Anatomy — the fields that matter
| Block | Fields to read | Meaning |
|---|---|---|
report_metadata | org_name · date_range | WHO is reporting and the window (a UTC period — the period mail was processed, not necessarily sent) |
policy_published | domain · p · sp · pct* | The DMARC record they evaluated against — sanity-check it matches what you think you published (*pct exists only in the 7489 dialect; the current spec retired it, with t=y taking its test-mode role — see the consistency note in our rollout guide's series context) |
row | source_ip · count · policy_evaluated | The sender, the VOLUME (never skip — rows are weighted by count), and the verdict block |
policy_evaluated | disposition · dkim · spf · reason? | dkim/spf here are pass/fail of ALIGNMENT (the health score). disposition = action applied (none/pass/quarantine/reject). reason is required when a failing alignment got a gentler action than your policy implies (values: local_policy, mailing_list, trusted_forwarder, policy_test_mode, other) |
identifiers | header_from · envelope_from | The domain the world saw vs the envelope domain actually authenticated — daylight between them is the alignment story |
auth_results | dkim: domain/selector/result · spf: domain/result | The RAW layer, “uninterpreted with respect to DMARC” — whose signature, which selector, what the envelope's SPF really did. Your detective layer. |
The five-pass protocol (Monday morning, 10 minutes)
- Whose, when: note the reporting org and window. A week of reports from multiple receivers is a dataset; one report is an anecdote.
- Find yourself: mark rows whose source/auth domains are your known senders (your mailbox provider, your ESPs — the auth_results
domainnames them). These should show an aligned pass. - Your failures = the fix list: rows from YOUR sources with both evaluated fields fail are misconfigurations you own (a new tool sending unauthenticated, a selector that died — our DKIM guide). Fix list first, strangers later.
- Classify the unknowns (auth_results is the lens): your DKIM signature present → your forgotten tool, inventory it · everything fails + your domain only in header_from → spoofing (your future p=reject neutralizes it; see our spoofing guide) · reason says trusted_forwarder/mailing_list → transit damage, expected physics, not an enemy.
- Do the count math: total the counts; compute the share of YOUR legitimate volume that aligns. That percentage — not any single scary row — is the input to the enforcement decision (our rollout guide's gates are built from exactly this).
Action matrix — what the combinations ask of you
| Evaluated DKIM / SPF | Situation | Action |
|---|---|---|
| pass / anything | Aligned via DKIM | None. The resilient leg is doing its job |
| fail / pass | Aligned via SPF only | Works today; note it — forwarding breaks SPF alignment first, so get DKIM signing on that source when you can |
| fail / fail — your source | Your sender, unauthenticated | Fix list: authenticate it or stop it sending as you |
| fail / fail — unknown source, big count | Spoofing at volume, or a major forwarder | Classify via auth_results/reason; spoofer rows are your p=reject business case, not a fire |
| fail / fail — unknown, count of 2 | Background noise of the open internet | Log it in the census; spend your worry by volume |
Cadence and silence — calibrated expectations
- Spec: generators SHOULD report at least every 24 h (opt-outs exist); Google: “usually sent once a day”; Microsoft: typically daily, previous day's data, likely gzipped. The expected attachment is
.xml.gzor plain XML — a.zipis outside the spec's documented forms, so treat unexpected archives with ordinary sender-verification suspicion. - No report ≠ no mail, and ≠ failure: non-participating receiver, zero observed traffic, or a report whose delivery itself failed (the spec permits discarded undeliverables). Judge by the reporters you do hear from, over weeks.
- Tools honesty: parsers/visualisers exist as a category (the spec itself anticipates third-party report services, and Google recommends a dedicated rua mailbox or service as volume grows). We name categories, not champions: any tool you adopt gets read access to your sending census — choose like it matters, because it does.
- Forensic reports (
ruf): the unicorn. Far fewer receivers send them, privacy limits their content, and several major receivers don't send them at all. Design your process around aggregates; treat any forensic arrival as a bonus.
Traps
| Trap | Reality |
|---|---|
| Panicking at fail rows while p=none | Under monitor mode, disposition none on failures is the design: you asked for a census before enforcement. The fails ARE the product. |
| Reading rows as messages | Each row is a count-weighted aggregate. Sort by count or be misled by a row of 2 out-arguing a row of 20,000. |
| Every unknown IP = attacker | Forwarders, lists, and your own shadow IT fill this bucket. auth_results + reason classify; adrenaline doesn't. |
| Hand-counting XML forever | Fine for week one (and genuinely instructive), unsustainable after. Move to a parser when the census is a habit — that's the spec's own anticipated workflow. |
FAQ
Reports show a domain in policy_published I don't recognize as mine.
Subdomains inherit/report under your organisational domain's policy — a subdomain's traffic legitimately appears in your reports. Trace it via identifiers before suspecting a mix-up.
Can reports tell me WHO read my mail / message subjects?
No — aggregates are deliberately content-free: counts, sources, verdicts. That privacy shape is why receivers are willing to send them at all.
When do reports let me climb to p=reject?
When pass 5's math says your legitimate volume aligns (with the fix list empty) across a representative window — the rollout guide turns that into staged gates. Reports are the evidence; the climb is the decision.
Sources
RFC 9990 — DMARC Aggregate Reporting (format, schema incl. the two disposition enums, example report) · RFC 9989 — DMARC (§5.3.8 report cadence SHOULD, enforcement workflow) · RFC 7489 — legacy dialect differences (pct, optional selector) · Google — “About DMARC reports” (cadence, mailbox/service recommendation) · Microsoft Learn — DMARC reporting (daily cadence, carried from this site's earlier verified research).
Did this guide work for you?
Your reaction tells us whether this guide solved the problem it promised to solve.