How to read a DMARC aggregate report, line by line
DMARC aggregate reports are compressed XML that few people read. Walk through each element, what the counts mean, and how to spot spoofing quickly.

On this page(11 sections)
- Getting a report to read
- Section 1: report metadata
- Section 2: published policy
- Section 3: the records
- Reading row
- Reading auth_results
- Interpreting common patterns
- Identifying unknown source IPs
- A quick way to summarize a report
- A routine for reviewing reports
- When reports disagree
- What reports cannot tell you
- Key takeaways
A DMARC aggregate report is a daily, machine-generated summary of every message a receiver saw claiming to be from your domain. Most people pipe them into a dashboard and never open one.
Reading a single report end to end once makes every dashboard afterward far easier to trust and to question.
Getting a report to read
Reports arrive at the address in your DMARC record's rua= tag, usually as a .zip or .gz attachment containing one XML file. The file name follows a convention:
receiver.example!example.com!1790553600!1790639999.xml.gz
That is the reporting organization, your domain, and the start and end of the reporting period as Unix timestamps. Decompress it and open the XML in any editor.
The format was originally defined in RFC 7489 and is now specified in RFC 9990, which was published alongside the updated DMARC core specification in 2026. Receivers are converging on the newer schema, so expect small variations between reports.
Section 1: report metadata
<report_metadata>
<org_name>receiver.example</org_name>
<email>[email protected]</email>
<report_id>8216493758</report_id>
<date_range>
<begin>1790553600</begin>
<end>1790639999</end>
</date_range>
</report_metadata>
This tells you who sent the report and which 24-hour window it covers. When numbers look odd, the first question is always "which receiver, which day?"
Section 2: published policy
<policy_published>
<domain>example.com</domain>
<adkim>r</adkim>
<aspf>r</aspf>
<p>none</p>
<sp>none</sp>
</policy_published>
This is the receiver's record of your DMARC policy as it saw it. If it does not match what you think you published, you have a DNS or caching problem. It is also a handy way to confirm when a policy change took effect at a given receiver. Older reports may include a pct element, which the current specification no longer uses.
Section 3: the records
Everything useful is in the record elements. Each one aggregates messages that shared the same source IP and the same authentication results.
<record>
<row>
<source_ip>198.51.100.25</source_ip>
<count>1432</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>k1</selector>
<result>pass</result>
</dkim>
<spf>
<domain>bounce.vendor.example</domain>
<result>pass</result>
</spf>
</auth_results>
</record>
Reading row
- source_ip: the IP that connected to the receiver. This is the most important field for identifying who sent the mail.
- count: how many messages matched this combination.
- policy_evaluated: the DMARC verdict. Here
dkimandspfmean "did this mechanism produce an aligned pass," not just "did it pass." - disposition: what the receiver did (
none,quarantine,reject). Withp=noneit will benoneeven for failures.
Reading auth_results
This is the raw result before alignment. In the example, SPF passed, but for bounce.vendor.example, which does not align with example.com. That is why policy_evaluated/spf says fail even though auth_results/spf says pass. DKIM passed for example.com, which aligns, so the message passes DMARC overall.
That distinction, raw pass versus aligned pass, is the single most common source of confusion when people read reports.
Interpreting common patterns
| Pattern | Likely meaning |
|---|---|
| DKIM aligned pass, SPF unaligned pass | A vendor using its own bounce domain but signing as you. Healthy. |
| DKIM pass for vendor domain only, SPF fail | Vendor not set up for custom-domain DKIM. Fix before enforcing. |
| SPF fail, DKIM pass, source IP from a mail provider | Probably forwarded mail. DKIM survived forwarding. |
| Both fail, source IP from a consumer ISP or cloud host | Likely spoofing, or a misconfigured server you own. |
| Both fail, source IP is a known mailing list host | List modified the message. Expected. |
| DKIM pass for your domain, unknown IP, high count | Possible DKIM replay. Investigate. |
Identifying unknown source IPs
For each IP you do not recognize:
dig +short -x 198.51.100.25
whois 198.51.100.25 | grep -iE 'orgname|netname|descr' | head
The PTR record and the IP's registered owner usually reveal the sender: a known email platform, a cloud provider (often a misconfigured app server), or a residential network (often spoofing via compromised machines).
A quick way to summarize a report
A few lines of Python turn the XML into something readable. Reports come from outside your organization, so parse them with defusedxml rather than the standard library parser, which is not hardened against malicious XML:
import gzip, sys
from defusedxml import ElementTree as ET
root = ET.parse(gzip.open(sys.argv[1])).getroot()
print(root.findtext("report_metadata/org_name"))
for r in root.iter("record"):
row = r.find("row")
pe = row.find("policy_evaluated")
dk = r.find("auth_results/dkim")
print(f'{row.findtext("source_ip"):16} {row.findtext("count"):>6} '
f'dkim={pe.findtext("dkim"):4} spf={pe.findtext("spf"):4} '
f'd={dk.findtext("domain") if dk is not None else "-"}')
Once you have done this a few times, a commercial or open source report processor becomes an efficiency tool rather than a black box.
A routine for reviewing reports
Reading reports is most useful as a habit rather than an emergency activity. A light weekly routine works well for most organizations:
- Check total volume per source. Compare the week's message counts for each known sender with what you expect. A transactional provider whose count drops to zero may have broken authentication or lost its configuration.
- List new source IPs. Any IP that appears for the first time deserves a lookup. Most turn out to be forwarders or new infrastructure at a known vendor; a few are new tools someone adopted without telling you.
- Review failing rows by size. Sort failures by count. Large failing counts from a single legitimate-looking source usually mean a vendor needs custom DKIM. Large failing counts from residential or cloud ranges usually mean spoofing.
- Confirm the published policy. Make sure the
policy_publishedsection matches your current record at every receiver. - Note changes. Keep a short log of what you found and fixed, so the next person reviewing has context.
During a DMARC rollout, do this more often, ideally every few days, because each fix you make should be visible in the next reports.
When reports disagree
Different receivers sometimes report different results for the same source. One may show DKIM passing while another shows it failing. That usually reflects a real difference in the path, such as one receiver getting mail through a forwarder, or a difference in how each receiver handles an edge case like a long DKIM record. Treat a disagreement as a clue about where to look, not as an error in the report.
What reports cannot tell you
- Content. Aggregate reports contain no subjects, bodies or recipient addresses.
- Everything. Not every receiver sends reports, and volumes are what each receiver chose to count.
- Real-time events. Reports cover a 24-hour period and arrive after it ends.
- Delivery outcome. A DMARC pass does not mean inbox placement; filtering happens separately.
Key takeaways
- An aggregate report has three parts: metadata, the policy the receiver saw, and records grouped by source IP and result.
policy_evaluatedshows aligned results;auth_resultsshows raw results. The difference explains most confusion.- The source IP is your key to identifying each sender.
- Common patterns map to vendors needing DKIM setup, forwarding, lists, spoofing and possible replay.
- Read a few reports by hand so you can sanity-check any dashboard you use.
Start with Koltrix
Your domain, one inbox, and an API that sends.
A team inbox where AI sorts and drafts (nothing is sent without your click), plus the transactional API and SMTP relay your product sends with. 7 days free, no card.


