RUA reports arrive as gzipped XML that nobody reads by hand. The report anatomy, a working parsing pipeline, and how to interpret the sources you will find.
Publishing a DMARC record with a rua= address starts a flood: every major mailbox provider begins sending you daily XML documents describing the mail they received claiming to be your domain. Those reports are the only view you get of your authentication posture as receivers see it, and they are unreadable at any volume without tooling. This article covers the report format, a pipeline to process it, and the interpretation work the tools cannot do for you.
Anatomy of a report
Each report covers one receiver's observations for one domain over a window, usually 24 hours. The envelope metadata identifies the reporter and window. policy_published echoes the DMARC record the receiver evaluated against. The heart is the record list: one entry per source IP and result combination, with a message count and the outcome of SPF and DKIM evaluation both raw and aligned.
1<record>
2 <row>
3 <source_ip>203.0.113.42</source_ip>
4 <count>1523</count>
5 <policy_evaluated>
6 <disposition>none</disposition>
7 <dkim>pass</dkim>
8 <spf>fail</spf>
9 </policy_evaluated>
10 </row>
11 <identifiers>
12 <header_from>example.com</header_from>
13 </identifiers>
14 <auth_results>
15 <dkim>
16 <domain>example.com</domain>
17 <selector>s2025a</selector>
18 <result>pass</result>
19 </dkim>
20 <spf>
21 <domain>bounce.esp-partner.com</domain>
22 <result>pass</result>
23 </spf>
24 </auth_results>
25</record>Read that example carefully, because it shows the single most misread distinction in DMARC. auth_results says SPF passed, yet policy_evaluated says SPF failed. Both are true: the message passed SPF for the ESP's bounce domain, but that domain does not align with example.com, so SPF fails for DMARC purposes. The message still passes DMARC overall because the DKIM signature is aligned. Tooling that shows only one of these layers produces confident misdiagnoses.
The pipeline
Processing is mechanical: collect the report mail, extract and decompress attachments, parse XML into rows, enrich with reverse DNS and known-sender matching, store, and aggregate. The open source parsedmarc does all of this, ships dashboards for OpenSearch or Elasticsearch, and is the default answer for teams that want ownership without building a parser. Commercial platforms (Dmarcian, Valimail, EasyDMARC and peers) trade money for the enrichment and workflow layers.
1import gzip, zipfile, io
2import xml.etree.ElementTree as ET
3
4def extract_report(attachment: bytes, filename: str) -> ET.Element:
5 if filename.endswith('.gz'):
6 xml_bytes = gzip.decompress(attachment)
7 elif filename.endswith('.zip'):
8 with zipfile.ZipFile(io.BytesIO(attachment)) as z:
9 xml_bytes = z.read(z.namelist()[0])
10 else:
11 xml_bytes = attachment
12 return ET.fromstring(xml_bytes)
13
14def records(root: ET.Element):
15 for rec in root.iter('record'):
16 yield {
17 'source_ip': rec.findtext('row/source_ip'),
18 'count': int(rec.findtext('row/count')),
19 'disposition': rec.findtext('row/policy_evaluated/disposition'),
20 'dkim_aligned': rec.findtext('row/policy_evaluated/dkim'),
21 'spf_aligned': rec.findtext('row/policy_evaluated/spf'),
22 'header_from': rec.findtext('identifiers/header_from'),
23 }Interpreting what you find
Aggregated by source, every domain's data resolves into three populations. The first is your own infrastructure: corporate mail, ESPs, transactional providers, the CRM, the billing system, and the SaaS tools someone connected years ago. Expect surprises here; discovering forgotten senders is half the value of monitoring and the reason p=none comes first.
The second population is forwarding. University addresses, mailing lists, and inbox-to-inbox forwards re-transmit your mail from IPs you do not control. SPF breaks in transit by design; DKIM usually survives unless a list rewrites content. Forwarded traffic that passes DKIM is fine, and the residue that fails everything is the unavoidable cost of enforcement, typically a fraction of a percent of volume.
The third population is abuse: sources sending mail with your domain in the From header that fail everything and match nothing you operate. At p=none this traffic reaches inboxes with your name on it. Watching its volume is watching the size of the problem enforcement will solve, which turns the p=quarantine conversation from theoretical risk into a number.
From data to decisions
The weekly triage loop
- 1
Rank sources by volume
Sort unmatched source IPs by message count and identify everything above a threshold, say 0.5% of total volume, using reverse DNS and your vendor inventory.
- 2
Fix alignment on legitimate sources
Each real sender either gets DKIM signing with your domain, an aligned SPF path, or a documented decision that its stream will fail under enforcement.
- 3
Classify the failing residue
Label forwarder patterns as accepted loss and spoofing as the enforcement motivation. Neither needs fixing; both need counting.
- 4
Track readiness as one number
Percentage of legitimate volume passing aligned authentication. When it holds above roughly 99% for several weeks, you are ready to step policy.
- 5
Keep monitoring after enforcement
Reports continue at p=reject and become your early warning for broken keys, new unauthorized SaaS senders, and replay abuse.
Frequently Asked Questions
What is the difference between rua and ruf reports?
Why do report totals not match my send logs?
Do I need to accept reports for domains that send no mail?
How long should reports be retained?
Aggregate reports are the feedback half of DMARC, and a policy managed without them is guesswork with a DNS record. Stand up parsedmarc or a commercial equivalent, run the weekly triage loop, and let the passing-volume number tell you when enforcement is safe.
Key Takeaways
- Aggregate reports arrive daily as gzip or zip compressed XML attachments from each reporting receiver
- Each record maps a source IP to message counts and SPF/DKIM results, including alignment
- The three populations in every report: your senders, legitimate forwarders, and spoofing attempts
- parsedmarc plus a dashboard handles ingestion for most teams; write your own parser only with a reason
- The goal is a decision per source: authorize it, fix its authentication, ignore it, or move to enforcement despite it
Related articles
2025 in Email: The Year Authentication Became Mandatory Everywhere
Microsoft joined the mandate, Gmail moved to hard rejection, and DMARCbis reached the finish line. What 2025 changed for senders and what it sets up for 2026.
Greylisting, Tarpitting, and Other Receiver Defenses Senders Should Understand
Receiving servers defend themselves with deliberate delays, temporary rejections, and traps for impatient senders. How each defense works and how a good MTA passes them.
Building a Deliverability Monitoring Stack
Postmaster Tools, SNDS, FBLs, DMARC reports, TLS-RPT, bounce logs, engagement data: the full observability stack, what each layer catches, and the alerts worth paging on.