Reading DMARC reports
Module 4: Deliverability: SPF, DKIM and DMARC in practice · Reading time: 12 minutes
Learning objective: After this lesson, you'll be able to open and read a DMARC aggregate report, work out per row which source it is and why it passes or fails, and decide what to fix, what you can ignore and when you can tighten your policy.
Key concepts: aggregate report (RFC 9990), rua, XML, gzip or zip, report_metadata, policy_published, record, row, source_ip, count, policy_evaluated (disposition, dkim, spf), identifiers (header_from, envelope_from), auth_results, selector, failure report (ruf, RFC 9991), reporting service, privacy
What you'll receive
If you've set an rua address in your DMARC record (lesson 4.4), large recipients such as Google, Microsoft and Yahoo will send you roughly one aggregate report a day. It's an email with an attachment, usually a gzipped XML file with a name like:
receiver.example!example.com!1793923200!1794009599.xml.gzThat means: from recipient receiver.example, about your domain example.com, for the period between the two timestamps (seconds since 1970; here, 6 November 2026, the whole day in UTC).
A report contains no mail content, no subject lines and usually no recipient addresses. For each sending server it contains the number of messages and the outcome of SPF, DKIM and DMARC. That makes it a reliable overview of who is sending mail on behalf of your domain, including sources you'd forgotten about yourself, and fraudsters.
The structure of a report
A report has three parts. Below are the key pieces from the example file for this lesson (dmarc-rapport-1.xml).
1. Who is reporting, and for which period
<report_metadata>
<org_name>receiver.example</org_name>
<email>noreply-dmarc@receiver.example</email>
<report_id>receiver.example-20261106-7f3c2a91</report_id>
<date_range>
<begin>1793923200</begin>
<end>1794009599</end>
</date_range>
</report_metadata>2. Which policy the recipient found for your domain
<policy_published>
<domain>example.com</domain>
<p>none</p>
<sp>none</sp>
<np>none</np>
<adkim>r</adkim>
<aspf>r</aspf>
<testing>n</testing>
</policy_published>Always check this: if it doesn't match what you think is in your DNS, the recipient read a different (or old) record.
3. Per source: a record
Each record covers one sending IP address with one combination of outcomes:
<record>
<row>
<source_ip>198.51.100.40</source_ip>
<count>1250</count>
<policy_evaluated>
<disposition>none</disposition>
<dkim>fail</dkim>
<spf>fail</spf>
</policy_evaluated>
</row>
<identifiers>
<envelope_from>bounce.mailer.example</envelope_from>
<header_from>example.com</header_from>
</identifiers>
<auth_results>
<dkim>
<domain>mailer.example</domain>
<selector>k1</selector>
<result>pass</result>
</dkim>
<spf>
<domain>bounce.mailer.example</domain>
<scope>mfrom</scope>
<result>pass</result>
</spf>
</auth_results>
</record>| Part | Meaning |
|---|---|
source_ip | The IP address of the server that delivered the mail |
count | How many messages from this IP had this outcome |
policy_evaluated | The DMARC outcome: did DKIM pass in alignment (dkim), did SPF pass in alignment (spf), and what did the recipient do (disposition: pass, none, quarantine or reject) |
header_from | The domain in the From address |
envelope_from | The domain of the Return-Path |
auth_results | The raw outcomes of SPF and DKIM, with the domain they applied to, and for DKIM the selector |
The key distinction: auth_results versus policy_evaluated
This is the heart of reading a report. In the example above, auth_results shows pass twice: DKIM passed for mailer.example and SPF passed for bounce.mailer.example. Yet policy_evaluated shows fail twice. That's not a contradiction: policy_evaluated only counts a passed check that's aligned with header_from. Neither domain belongs to example.com, so DMARC fails.
Here's how to read each row:
- Look at
policy_evaluated. Doesdkimorspfshow pass? Then DMARC passes and this source is fine. - Does everything fail? Check the domains in
auth_results. Is it one of your services, passing but for its own domain? Then you need to turn on DKIM with your own domain at that service (lesson 4.3). - Does it also fail in
auth_results, or is DKIM missing altogether? Find out whose IP address it is (reverse DNS, lesson 5.1, or a whois lookup). Is it one of your own systems: fix it. Is it unknown and do the numbers look suspicious: it's probably forgery.
Patterns you'll see again and again
| Pattern | What it usually is | What to do |
|---|---|---|
| DKIM pass and SPF pass, both for your own domain | Your own mail server | Nothing |
| DKIM pass for your domain, SPF fail or softfail, unknown IP | Forwarded mail: someone is forwarding your mail, DKIM survived | Nothing; this is exactly why DKIM matters |
| DKIM pass for your domain, SPF pass for a different domain | A service with its own Return-Path and your DKIM | Nothing, DMARC passes on DKIM |
| Both pass, but for a service's own domain | A service that signs with its own domain | Turn on DKIM with your domain at that service |
| SPF softfail for your domain, no DKIM, own server | A server or address of yours that's not in your SPF and doesn't sign | Add it to SPF and turn on DKIM |
| SPF fail or softfail, no DKIM, unknown IP, fluctuating counts | Forgery (spoofing) | Ignore; p=quarantine or reject will stop this |
Also watch out for IPv6: a server of your own that sometimes sends over IPv6, while your SPF only lists the IPv4 address, will show a softfail on those rows. With a DKIM signature, DMARC still passes, but it's a sign that your SPF is incomplete.
From report to decision
You're ready to tighten your policy (from none to quarantine, then to reject) when:
- every source that's yours has at least one pass in
policy_evaluated; - that's been the case for a few weeks, across all major recipients;
- whatever still fails is only forwarded mail with a valid DKIM signature (which passes anyway) or clear forgery.
If a major source of your own fails, such as the newsletter service with 1250 messages in the example, it's too early. With p=reject, the recipient would reject those 1250 newsletters.
Making reports readable
One report in a text editor is manageable. Twenty a day is not. There are three approaches:
- A reporting service (there are many, including free options). You put their address in
ruaand they turn the data into charts and lists. Note: this gives that service insight into which servers send mail on your behalf, and the report address is on their domain (see the authorisation via_report._dmarcin lesson 4.4). Choose a service you trust and that complies with the GDPR. - A tool of your own: there are open-source programs that pull reports from a mailbox and turn them into an overview.
- Reading them yourself with an XML viewer or a browser, for the occasional check.
Failure reports (ruf, RFC 9991) are different: one per failed message, sometimes including parts of the mail. Almost no large recipient sends them, precisely because of privacy. You don't need them to roll out DMARC properly.
In practice: what it looks like
At WebOké, you can enter the report address in the DMARC helper in DirectAdmin or My WebOké (see lesson 4.4), and you choose which mailbox the reports arrive in yourself; a separate address such as dmarc@ with its own folder stops your regular mail from being buried. You read the reports with a reporting service or a tool of your choice. This works the same way with every provider: the report is a standard format.
Summary
- An aggregate report is a daily XML file (often gzipped) per recipient, with the number of messages and outcomes for each sending IP.
auth_resultsgives the raw SPF and DKIM outcome;policy_evaluatedonly what passed in alignment. A pass inauth_resultscan still be a fail in DMARC.- You recognise forwarded mail by DKIM pass with SPF fail; forgery by everything failing from an unknown IP.
- Only tighten your policy once all your own sources have been passing for a few weeks.
- Failure reports (
ruf) are rare and not needed.
Quiz
Exercise
Exercise Z4.5: Reading a DMARC report. Open the example file dmarc-rapport-1.xml (an aggregate report about example.com, with fictional data). Answer the questions: which policy did the recipient find, how many messages are in the report in total, how many of those failed DMARC, which row is the newsletter service and what needs to happen there, which row is forwarded mail, which row is most likely forgery, what needs to change in the SPF record according to row 6, which row passes only thanks to DKIM at a service with its own Return-Path, and can the domain now move to p=quarantine? The program checks your answers against a fixed key and explains, for each question, which line of the report gives the answer.
Further reading
The steps in these articles apply to WebOké's control panels; with other providers it works in a similar way.
- What is DMARC? Explanation in plain language
- DirectAdmin guide 5.9: Setting up DMARC
- Free DNS course 4.5: DMARC: what receivers should do with fake mail
- RFC 9990 (DMARC Aggregate Reporting), with the example report in appendix B; RFC 9991 (Failure Reporting)
