How to Read a DMARC Aggregate Report and Spot Rogue Senders
· 8 min read
Checks run by DNS only. Nothing is sent from or to your domain.
To read a DMARC aggregate report, unzip the XML file, check the reporting organisation and date range, then go through each record row: the source IP, the message count, and whether SPF and DKIM passed and aligned with your From domain. Rows that fail both from IPs you do not recognise are unauthorised senders.
Aggregate reports are the most useful and least read files in email. Every major mailbox provider sends them, usually once a day, to the address in the rua tag of your DMARC record. They tell you about every server in the world that sent mail claiming to be from your domain, and whether that mail authenticated. If you are moving from p=none to enforcement, or trying to understand why some messages fail, these reports are the source of truth. This guide shows what is inside one and how to turn it into decisions.
Where reports come from and what they look like
Once your DMARC record has a reporting address, for example v=DMARC1; p=none; rua=mailto:[email protected], receivers such as Gmail, Yahoo, Outlook.com and many corporate gateways start sending reports. Each report arrives as an email with a compressed attachment, usually .zip or .gz. The file name holds the reporting organisation, your domain, and the start and end of the period as Unix timestamps, so you can sort a mailbox full of reports without opening them.
If the reporting address is on a different domain from the one being reported, for example you publish rua=mailto:[email protected] for example.com, the receiving domain must authorise it by publishing a TXT record at example.com._report._dmarc.monitoring-provider.example containing v=DMARC1. Without that record, many receivers silently skip sending reports, which is a common reason people think they get "no data".
Inside the archive is a single XML file. A trimmed example:
<feedback>
<report_metadata>
<org_name>google.com</org_name>
<email>[email protected]</email>
<report_id>1234567890123456789</report_id>
<date_range><begin>1760140800</begin><end>1760227199</end></date_range>
</report_metadata>
<policy_published>
<domain>example.com</domain>
<adkim>r</adkim><aspf>r</aspf>
<p>none</p><sp>none</sp><pct>100</pct>
</policy_published>
<record>
<row>
<source_ip>198.51.100.42</source_ip>
<count>1840</count>
<policy_evaluated>
<disposition>none</disposition>
<dkim>pass</dkim><spf>fail</spf>
</policy_evaluated>
</row>
<identifiers><header_from>example.com</header_from></identifiers>
<auth_results>
<dkim><domain>example.com</domain><selector>s1</selector><result>pass</result></dkim>
<spf><domain>bounces.esp-mail.net</domain><result>pass</result></spf>
</auth_results>
</record>
</feedback>
The metadata and the published policy
The report_metadata block tells you who sent the report and which 24 hour window it covers. Convert the timestamps to dates when you compare reports from different providers, because their windows do not line up exactly.
The policy_published block shows the DMARC record the reporter saw when it evaluated your mail. This is worth checking on its own. If you changed your record yesterday and a report still shows the old policy, the reporter cached the old record. If the block shows p=reject but you meant p=quarantine, you have found a typo in production. adkim and aspf show the alignment mode (r relaxed, s strict), sp the policy for subdomains and pct the percentage of failing mail the policy applies to.
Reading a record row by row
Each record is one combination of source IP, authentication results and From domain, with a count of how many messages matched. A busy domain can have hundreds of records per report.
Read every record in this order:
- source_ip and count. Who sent it and how much. A single IP with thousands of messages is a real sending platform. Scattered IPs with one or two messages each are often forwarding or spoofing.
- policy_evaluated. The DMARC verdict.
dkimandspfhere mean pass or fail after alignment.dispositionis what the receiver did:none,quarantineorreject. If eitherdkimorspfsays pass, the message passed DMARC. - header_from. The domain in the visible From address. This is the domain DMARC protects.
- auth_results. The raw SPF and DKIM results before alignment, including which domain was checked. This is where you find out why something failed.
The example above is the most common pattern you will see. DKIM passed with d=example.com, so it aligns and the message passes DMARC. SPF also passed, but for bounces.esp-mail.net, the ESP's bounce domain, so it does not align and policy_evaluated shows spf as fail. That is fine. One aligned pass is enough. It does tell you that this source depends entirely on DKIM, so if the ESP ever stops signing with your domain, the stream fails.
The four patterns you need to recognise
Aligned DKIM and aligned SPF
The ideal. Your main mail platform and properly configured ESPs end up here. Nothing to do.
Raw pass but not aligned
SPF passes for the provider's domain, or DKIM passes with d=provider.com, but neither matches your From domain, so DMARC fails. This is the classic sign of a legitimate tool that was connected without custom authentication: a CRM, a help desk, an invoicing tool, a survey platform. The fix is to set up custom DKIM for your domain in that tool, or a custom return path that puts your domain in the envelope sender. Before you enforce DMARC, every row in this category must be fixed, otherwise p=reject will block your own invoices.
Fails both from IPs you recognise
Your own server or a known provider failing everything usually means a missing DKIM key, an expired selector, an SPF record over the 10 lookup limit, or mail going out through a relay nobody added to SPF. Check the selector named in auth_results with a DKIM checker and the SPF record with a SPF checker.
Fails both from IPs you do not recognise
These are the unauthorised senders. Look up the PTR record of each IP and check who owns the network. You will typically find three kinds:
- Forwarding. A recipient's mailbox automatically forwards your message elsewhere. SPF breaks because the forwarder's IP is not in your record. DKIM usually survives if the message body was not modified. Mailing lists that rewrite subjects or add footers break DKIM too. Small counts from university or ISP mail servers are almost always this.
- Shadow IT. A department or agency sending from a platform you did not know about. The PTR points to a known marketing or ticketing service, the volume is real, and the fix is to bring it under proper authentication.
- Spoofing. Random IPs, often on residential or hosting ranges in unexpected countries, sending small batches. That is phishing or spam using your domain, and it is exactly what
p=rejectstops.
From reports to enforcement
The reason to read these reports is to reach p=reject without breaking legitimate mail. A safe path looks like this:
- Publish
p=nonewith aruaaddress and collect at least two to four weeks of reports, so monthly senders such as billing runs show up. - Build a list of every legitimate source, with its volume and its aligned pass rate.
- Fix each source until its aligned pass rate is close to 100 percent.
- Move to
p=quarantine, optionally withpct=25and then higher, while watching the reports for anything legitimate that starts failing. - Move to
p=reject. Keep reading reports, because new tools get connected all the time.
Remember subdomains. Without an sp tag, subdomains inherit the main policy. If you send from news.example.com, make sure it is covered by your reports and passes before enforcement applies to it.
Aggregate reports versus failure reports
DMARC defines a second report type, failure reports, requested with the ruf tag. They are sent per message rather than per day and can contain headers or even parts of the failing message. In practice, few large mailbox providers send them, mostly for privacy reasons, so you should not rely on them. Aggregate reports are the dependable source: they contain no message content, only IPs, counts and results, and nearly every major receiver sends them.
Common mistakes when reading reports
- Judging by raw results. An
auth_resultsSPF pass means nothing for DMARC unless the domain aligns. Always readpolicy_evaluatedfor the verdict. - Panicking about forwarding. A steady trickle of failures from mail servers at universities and ISPs is usually forwarding. Do not add those IPs to SPF; that would authorise strangers to send as you.
- Ignoring low volume sources. A tool that sends 40 messages a month, such as quarterly invoices or contract signatures, can be the most important mail you send. Wait for a full month of reports before enforcing.
- Looking at one receiver only. Gmail reports cover Gmail recipients. A B2B sender may see most of its traffic in reports from corporate gateways and Outlook.com instead.
- Forgetting the record itself. If
policy_publishedsuddenly disappears from reports, or reports stop, check that the DMARC record still exists. DNS changes during a migration remove it more often than you would expect.
Why spreadsheets stop working
One report from one receiver is readable. A domain sending real volume receives dozens of reports a day from different providers, each with hundreds of rows, and the same source appears under different IPs in each. Merging them by hand, resolving IPs to owners and tracking pass rates over time becomes a weekly chore that nobody does for long.
A DMARC report analyzer does that merge for you and shows the sources, volumes and alignment results in a readable table. With continuous DMARC monitoring, every report sent to your unique reporting address is parsed automatically, sources are grouped by platform, and you get an alert in plain language when something changes, for example "DMARC alignment fails for 14 percent of messages, all from one source (DKIM signed with their domain)". The same panel tracks blacklist listings and SPF and DKIM changes for every domain, which helps with the Google and Yahoo bulk sender requirements as well.
Quick reference
| Field | What to look for |
|---|---|
org_name | Which receiver reported |
date_range | Unix timestamps of the 24 hour window |
p, sp, pct | The policy the receiver saw |
source_ip, count | Who sent, and how many messages |
policy_evaluated | DMARC result after alignment and the action taken |
header_from | The From domain being protected |
auth_results | Raw SPF and DKIM results with the domain and selector checked |