How to read DMARC aggregate reports
What a DMARC aggregate (rua) report contains, how to read its XML record by record, and how to turn the data into fixes for your senders and policy.
By Exmoor Software7 min read
DMARC aggregate reports are the feedback loop that makes DMARC usable. Once your DMARC record contains a rua= address, receivers that support reporting send you a summary — usually once a day — of the mail they received with your domain in the From header: which servers sent it, how many messages there were, and whether SPF, DKIM and DMARC passed.
Since May 2026 the report format is defined in RFC 9990, which replaced the reporting part of RFC 7489. This guide explains what a report contains, how to read it and what the patterns you will see actually mean.
How reports arrive
You request reports in your DMARC record:
_dmarc.example.com. 3600 IN TXT "v=DMARC1; p=none; rua=mailto:dmarc-reports@example.com"
Each participating receiver then emails that address once per reporting period — typically one UTC day — and per DMARC policy domain. The report is an XML file attached to the message, normally gzip-compressed. RFC 9990 fixes the format of the subject and the file name, so reports are easy to filter:
Subject: Report Domain: example.com Submitter: mail.receiver.example Report-ID: <1792627200-example.com@mail.receiver.example>
mail.receiver.example!example.com!1792627200!1792713599.xml.gz
The two numbers in the file name are the start and end of the reporting period in seconds since 1 January 1970 (UTC).
Not every receiver sends reports — RFC 9989 lists resource constraints, local policy and privacy concerns as the usual reasons for opting out. Treat the data as a large sample of your traffic, not a complete count.
Report addresses on another domain
If rua points outside your organizational domain — typically to a reporting service — receivers first check that the destination has agreed to receive reports for you (RFC 9990, section 4). For rua=mailto:dmarc@reports.example.net on example.com, the destination must publish:
example.com._report._dmarc.reports.example.net. IN TXT "v=DMARC1"
A service that accepts reports for any domain can use a wildcard: *._report._dmarc.reports.example.net. Without the record, receivers must ignore the address and the reports never arrive — silently. The DMARC checker tests this for every address in your record.
What is inside a report
Here is a shortened report with two records:
<feedback xmlns="urn:ietf:params:xml:ns:dmarc-2.0">
<version>1.0</version>
<report_metadata>
<org_name>Example Receiver</org_name>
<email>dmarc-noreply@mail.receiver.example</email>
<report_id>1792627200-example.com@mail.receiver.example</report_id>
<date_range>
<begin>1792627200</begin>
<end>1792713599</end>
</date_range>
</report_metadata>
<policy_published>
<domain>example.com</domain>
<discovery_method>treewalk</discovery_method>
<p>none</p>
<sp>none</sp>
<np>none</np>
<adkim>r</adkim>
<aspf>r</aspf>
<testing>n</testing>
</policy_published>
<record>
<row>
<source_ip>192.0.2.25</source_ip>
<count>1840</count>
<policy_evaluated>
<disposition>none</disposition>
<dkim>pass</dkim>
<spf>pass</spf>
</policy_evaluated>
</row>
<identifiers>
<header_from>example.com</header_from>
<envelope_from>example.com</envelope_from>
</identifiers>
<auth_results>
<dkim>
<domain>example.com</domain>
<selector>s2026</selector>
<result>pass</result>
</dkim>
<spf>
<domain>example.com</domain>
<scope>mfrom</scope>
<result>pass</result>
</spf>
</auth_results>
</record>
<record>
<row>
<source_ip>198.51.100.40</source_ip>
<count>312</count>
<policy_evaluated>
<disposition>none</disposition>
<dkim>fail</dkim>
<spf>fail</spf>
</policy_evaluated>
</row>
<identifiers>
<header_from>example.com</header_from>
<envelope_from>bounces.mailer.example.net</envelope_from>
</identifiers>
<auth_results>
<dkim>
<domain>mailer.example.net</domain>
<selector>m1</selector>
<result>pass</result>
</dkim>
<spf>
<domain>bounces.mailer.example.net</domain>
<scope>mfrom</scope>
<result>pass</result>
</spf>
</auth_results>
</record>
</feedback>
The file has three parts.
report_metadata — who sent the report and for which period: org_name, a contact email, a unique report_id and the date_range. An optional error element lists problems the receiver found in your DMARC record, such as an invalid rua or two DMARC records at the same name.
policy_published — your DMARC record as the receiver saw it: the policy domain, p, sp, np, the alignment modes and testing mode (t), plus discovery_method: treewalk for the RFC 9989 method or psl for the older Public Suffix List method. Tags you did not set appear with their default values.
record — at least one per sending IP address and combination of results:
| Element | What it tells you |
|---|---|
row/source_ip | IP address of the server that connected to the receiver |
row/count | number of messages with exactly this combination |
row/policy_evaluated | the DMARC outcome: disposition and the aligned dkim and spf results |
identifiers/header_from | domain in the From header — may be a subdomain of the policy domain |
identifiers/envelope_from | envelope sender (MAIL FROM) domain that SPF checked |
auth_results/dkim | each DKIM signature: domain (d=), selector (s=), result |
auth_results/spf | SPF domain, scope (always mfrom) and result |
The one distinction that matters
Every record contains SPF and DKIM results twice, and they mean different things:
auth_resultsare the raw results, uninterpreted with respect to DMARC: did SPF pass for some domain, did some signature verify.policy_evaluatedsays whether those results counted for DMARC. It ispassonly when the passing domain is aligned with the From domain.
In the second record above, SPF passed for bounces.mailer.example.net and the signature from mailer.example.net verified — yet both policy_evaluated values are fail, because neither domain is example.com. That is a service sending on your behalf without your domain configured, the most common finding in a new deployment. DMARC alignment explained covers the rules in detail.
A message passes DMARC when policy_evaluated/dkim or policy_evaluated/spf is pass.
Disposition and override reasons
disposition is what the receiver did with the messages: none, quarantine or reject, or — new in RFC 9990 — pass for messages that passed DMARC under an enforcing policy. When the receiver applied something other than your published policy, a reason element says why:
reason/type | Meaning |
|---|---|
policy_test_mode | your record has t=y, so a policy one level lower was applied |
mailing_list | the receiver concluded the message came through a mailing list |
trusted_forwarder | the message came through a forwarder the receiver trusts |
local_policy | the receiver’s own rules took precedence |
other | anything else, explained in comment |
Reading the records: common patterns
Sort the records by count and work through each sending source. The reverse DNS lookup of source_ip usually tells you whose server it is.
Your own servers, everything passes. Aligned DKIM and SPF both pass. This is what every legitimate source should look like, and it is how you confirm that a change didn’t break anything.
A service you use: raw pass, aligned fail. As in the example, SPF and DKIM pass for the provider’s domains. Enable DKIM signing with your domain in the service (usually CNAME records it gives you) and, if offered, a custom return-path domain such as bounce.example.com. Adding the service’s include: to your SPF record does not help while it uses its own envelope sender.
A forgotten sender. An IP you recognize — an invoicing system, a web shop, a scanner — with SPF fail or softfail and no DKIM signature. Add it to your SPF record, enable DKIM in that system, or route its mail through a service that signs. Monthly senders only appear after a full month, so keep p=none long enough to see them.
Forwarding. source_ip belongs to another mailbox provider or a university, envelope_from is your domain and SPF fails, but DKIM is aligned and passes. DMARC passes thanks to DKIM; there is nothing to fix. If DKIM fails too — a mailing list changed the subject or added a footer — you may see the mailing_list reason. You can’t fix that on your side, which is why DKIM on all your mail matters.
Unknown sources, nothing aligned. Small counts from IPs that aren’t yours, no DKIM signature or one from an unrelated domain, SPF fail. This is usually spoofing — exactly what p=quarantine or p=reject will stop.
SPF permerror or temperror on all your sources. Your SPF record is broken, often by exceeding the 10-lookup limit. Run the SPF checker.
Reports in the older format
Receivers that still implement RFC 7489 send reports built on its schema: a different XML namespace, no np, testing or discovery_method, a pct element, and only none, quarantine and reject as dispositions. Their override reasons include forwarded and sampled_out, which RFC 9990 no longer has. Expect both formats for some time and make sure whatever processes your reports accepts both. The other differences between the specifications are summarized in DMARC RFC 9989: what changed and what to do.
Privacy and safe handling
RFC 9990 states that aggregate reports contain no personal information such as individual mail addresses or message content. They do describe your mail flows, though, and RFC 9989 notes that for a small organization a report can reveal sensitive business patterns. If you send reports to an external processor, check that your internal policies and contracts allow it.
Treat incoming reports as untrusted input:
- A report is a compressed XML file from the internet. Use a parser that is protected against decompression and XML bombs (RFC 9990, section 8.1).
- Reports can be forged to influence your decisions (section 8.2). Report messages are themselves required to pass DMARC, so you can discard those that don’t.
- A receiver that resends a report must reuse the same file name and
report_id; de-duplicate on them.
Tools to process reports
Reading XML by hand works for a test domain, not for daily traffic; RFC 9989 recommends machine parsing. The usual options are:
- A one-off look in the browser. The DMARC report analyzer reads a single report (XML, ZIP or GZIP), groups the records by sending source and does not store the file. With a free account you can keep reports for a verified domain and follow the trend for 90 days.
- A hosted DMARC reporting service. You point
ruaat its address (it publishes the authorization record) and get the records grouped by source. Check where it stores the data and for how long. - An open-source parser that reads a mailbox, unpacks the attachments and loads the records into a database or a search tool.
- Your own script. Extract the attachments, parse each
recordand sumcountbysource_ip,header_fromand thepolicy_evaluatedresults.
Whichever you choose, use a dedicated mailbox for rua so that reports don’t flood a person’s inbox.
Next steps
- Check that your record requests reports and that external addresses are authorized — DMARC checker.
- Open the first reports you receive in the DMARC report analyzer to see which sources fail.
- Fix every legitimate source that fails in
policy_evaluated— see DMARC alignment explained. - When only spoofing is left among the failures, move to
p=quarantineand thenp=reject, as described in How to configure DMARC correctly.