The mailbox behind your rua address has a few hundred unread messages in it. Each one carries a small compressed attachment with a name like mail.receiver.example!example.com!1013662812!1013749130.xml.gz. Nobody has opened one since the week the DMARC record went live.
That filename is not arbitrary. RFC 9990 defines it precisely: the reporting receiver, then the policy domain, then the beginning and ending timestamps of the reporting window in seconds since January 1, 1970 UTC, then an optional unique ID, then either xml or xml.gz. The two numbers in the middle are the window. Convert them and you know exactly which day you are looking at.
Worth knowing before you start: DMARC was re-published in May 2026 as three Standards Track documents, RFC 9989, RFC 9990, and RFC 9991, which together obsolete the original RFC 7489. Aggregate reporting now has its own RFC. The XML changed in small ways that matter when you read it.
The two blocks at the top of every report
Every report opens with report_metadata. It holds org_name (who is telling you this), email, an optional extra_contact_info, a report_id, a date_range with begin and end as Unix timestamps, and an optional generator string naming the software that built the file. There is also an optional error element. If a receiver could not do something you asked, it says so there, and almost nobody reads it.
Next comes policy_published. This is the part people skip and should not. It is not your intended policy. It is the policy that particular receiver actually resolved from DNS during that window. It carries domain, p, sp, np, adkim, aspf, testing, and discovery_method. If you pushed a record change and one reporter still shows the old p value three days later, you have a DNS problem, not a mail problem.
The alignment tags are where quiet failures live. adkim and aspf default to r, relaxed, meaning the two domains need only share an Organizational Domain. Set to s, strict, they must be identical. Flipping either to strict without checking your reports first is how a working mail stream breaks on a Tuesday.
Two newer details: np sets a separate policy for non-existent subdomains, and discovery_method tells you whether the receiver found your record by the old public suffix list method or by the DNS tree walk introduced in RFC 9989. Meanwhile the pct tag has been removed from the standard entirely. You will still see pct in reports and in vendor documentation, including Microsoft's own published example, because older generators and older parsers have not caught up.
How to read one record row
Everything after that is a repeating record element. Each record is one group of messages that shared the same source and the same authentication outcome during the window. It has three children.
row holds source_ip, a count of messages, and policy_evaluated. Inside policy_evaluated sit disposition, dkim, spf, and zero or more reason elements.
identifiers holds header_from (the RFC5322.From domain, the one a human sees), and optionally envelope_from (the RFC5321.MailFrom domain, used for the bounce path) and envelope_to.
auth_results holds the raw underlying checks: one or more dkim entries with domain, selector, and result, plus an spf entry with domain, scope, and result.
Here is the single most important thing in the whole format, and it is not obvious from looking at the file. The dkim and spf elements appear twice, in two different places, and they mean two different things.
Under policy_evaluated, they can only ever be pass or fail. That is the schema. Those are alignment verdicts: did an authenticated identifier line up with the domain in header_from?
Under auth_results, the vocabulary is much wider. DKIM can return none, pass, fail, policy, neutral, temperror, or permerror. SPF can return any of those plus softfail. That is authentication: did the check itself succeed, against whatever domain it happened to be checking?
A message can authenticate perfectly and still fail DMARC, because it authenticated as somebody else. Microsoft states the pattern directly in its own documentation: an auth_results SPF result of pass combined with a policy_evaluated SPF of fail means SPF passed but the domains do not align. Read those two fields side by side and most of the mystery drains out of the report.
One more field to know: disposition can be none, pass, quarantine, or reject. The value pass is a newer addition meaning no action was taken and the message passed DMARC under an enforcing policy. And reason, when present, carries a type from a fixed list: local_policy, mailing_list, other, policy_test_mode, or trusted_forwarder. A receiver that overrode your policy will often tell you why, right there.
Three sources that look identical at a glance
A real spoof
An unfamiliar source_ip with no relationship to anything you run. In auth_results, SPF returns fail or none, and either there is no dkim entry at all or its domain is not yours. Both policy_evaluated values are fail. The giveaway is the absence of any authenticated identifier you control. Do not weight this by count alone. Targeted fraud against a finance team may be a dozen messages, sitting below a mailing list generating thousands.
Your own misconfigured SaaS sender
This is the one that gets misread as an attack, and it is by far the most common finding. A payroll platform, ticketing system, or campaign tool sends on your behalf. In auth_results, SPF returns pass — but look at the domain it passed on. It is the vendor's bounce domain, not yours. Microsoft's published example makes this concrete with an envelope_from of bounces.adatum.com under a header_from of contoso.com. Because those do not share an Organizational Domain, policy_evaluated SPF is fail. DKIM often fails alignment the same way, signed with the vendor's own d= value rather than a selector delegated into your domain.
The signature to memorize: authentication passes, on a domain that is not yours, from an IP range that belongs to a company you actually pay. The fix is alignment work at the vendor, usually a delegated DKIM selector and a custom bounce domain.
A legitimate forwarder that broke SPF but kept DKIM
RFC 9989 describes this case explicitly. Mail sent to an alumni forwarder, a role address, or a personal alias gets relayed onward. At the final destination the connecting IP belongs to the forwarder, not to any host in your SPF record, so SPF fails unless the relay rewrote the MailFrom address. The RFC's own words: DKIM signatures will generally remain valid in these relay situations.
So the row reads policy_evaluated SPF fail, policy_evaluated DKIM pass, and a disposition of none or pass. Critically, the auth_results DKIM domain is your domain, with a selector you recognize. That is your signature surviving the trip. Sometimes a reason element with type trusted_forwarder or mailing_list confirms it.
This is exactly why RFC 9989 states that domains publishing p=reject must not rely on SPF alone and must apply valid DKIM signatures. Discussion lists complicate it further: list software now rewrites the From header to stay aligned, turning an address at your domain into forms like bob=example.com@user.somelist.example. Once that happens, header_from is no longer your domain and those messages stop appearing in your reports at all.
The trap: zero failures only covers the slice that reports
An aggregate report is something a receiver chooses to send. The standard is written around the domain owner requesting reports through the rua tag. Nothing obliges a receiver to generate them, and plenty do not. Your dashboard is a sample, and it is not a random one.
What can be verified from the receivers themselves is narrower than most people assume. Google sends aggregate reports, usually once per day, and documents that Gmail does not support the ruf tag at all, so no failure reports come from there. Microsoft states that Microsoft 365 sends aggregate reports to domains with a valid rua address, and separately that it does not send forensic reports even when a valid ruf address exists.
But read Microsoft's condition twice, because it is the part that quietly removes a large share of business email from your visibility. Aggregate reports are sent as long as the MX record for the Microsoft 365 domain points directly to Microsoft 365. Where internet mail is routed through a non-Microsoft service or device before delivery to Microsoft 365 — a third-party security gateway in front of the tenant, or a hybrid setup delivering on-premises first and relaying in over a connector — Microsoft states that aggregate reports are not sent.
That condition describes the recipient's inbound routing, not yours. Every customer, supplier, and prospect running Exchange Online behind a mail gateway is a blind spot in your data, and you cannot see which ones they are. Someone spoofing your domain at exactly those organizations generates no row anywhere in your reports.
There is a second blind spot written into RFC 9989. If you publish a hard fail SPF record ending in -all, a receiver may evaluate SPF early and reject during the SMTP transaction. The RFC is blunt about the consequence: such messages will never appear in aggregate DMARC reports, because the transaction never reaches the DATA phase, so the RFC5322.From domain is never revealed and your DMARC policy is never discovered. The mail you would most want to see is the mail most likely to be missing.
The honest version of the sentence, then, is this: no DMARC failures were reported, by the receivers that send reports, for messages that reached the DATA phase, at recipient organizations whose inbound mail was not routed through an intermediary first. That is still useful. It is not the same claim as nobody is spoofing your domain.
One last thing to check before trusting any of it. If your rua address lives on a different domain than the one being monitored, the destination domain must publish an authorization record or reports are simply never delivered. Microsoft documents the format: a TXT record at contoso.com._report._dmarc containing v=DMARC1. Miss it and the reporting looks flawless, because nothing arrives at all — and an empty inbox and a clean inbox render identically in every dashboard ever built.
