Your DMARC record has a rua= tag, and every day, mailbox providers send you an XML file. Here's what's actually inside it and how to read it without a headache.
What's in a DMARC aggregate report at a glance
What's in a DMARC aggregate report at a glance
Section of the file | XML element | What it tells you |
Who sent the report | report_metadata | Which mailbox provider generated it, the reporting window, and a unique report ID |
Your policy, as the receiver saw it | policy_published | The p, sp, adkim, and aspf values the receiver read from your DNS |
Each sending source | record (one or more) | The IP address, message count, and whether that traffic passed or failed DMARC |
The verdict for that source | policy_evaluated | The disposition applied (none, pass, quarantine, or reject) and whether DKIM/SPF were DMARC-aligned |
The raw check, unfiltered | auth_results | What DKIM and SPF actually returned before DMARC applied any alignment logic |
Keep this table nearby. Everything below walks through why each row matters and how to read it in a real file.
DMARC aggregate reports are the only part of the DMARC system built specifically for humans to read, and they're still written in XML. That's the whole tension. Red Sift OnDMARC turns this into dashboards so you never touch raw XML by hand, but understanding what those dashboards are summarizing makes every decision you make about your DMARC policy faster and more defensible. This guide walks through an actual report, field by field, and shows you how to tell a rotation in your email service provider apart from a real spoofing attempt.
This isn't a rehash of what an aggregate report is or why your rua= mailbox might be empty. If you need that groundwork first, our guide to DMARC aggregate reporting basics covers it. This guide picks up from the moment the file lands in your inbox and shows you what to do with it.
By the end, you'll be able to:
- Open a report and identify every section without a reference sheet
- Tell the difference between DMARC's pass/fail result and the raw SPF/DKIM result underneath it
- Read the disposition column and know exactly what happened to that mail
- Spot the four most common patterns that show up in real reports, including forwarding, ESP rotation, and outright spoofing
- Understand what changed in the report format under the 2026 DMARC update
What's actually inside the file you received
Every DMARC aggregate report is a single XML document with a root element called feedback. Inside it, three blocks carry all the information you need [1].
- report_meta identifies who sent you the report and when. It includes the reporting organization's name, a contact email, a unique report ID, and a
date_rangeshowing the reporting window in seconds since the Unix epoch. Most receivers report on a rolling 24-hour window. - policy_published is the receiver's read of your DMARC record at the time they evaluated your mail. This matters more than it sounds. If you changed your policy from
p=nonetop=quarantinemid-day, different reports covering that day might show different values here, because DNS propagation and caching mean not every receiver saw the change at the same moment [1]. - record is where the actual data lives. There's one record block per combination of sending IP, evaluated result, and authentication identifiers, so the same IP can legitimately appear in multiple records even when the disposition is the same. A single report can contain anywhere from one to several thousand of these depending on your mail volume. Each
recordbreaks down into three parts:row(the IP, the count, and the verdict),identifiers(the domains DMARC actually checked), andauth_results(the raw SPF and DKIM outcomes before DMARC's alignment logic touched them).
Once you can name these three blocks on sight, reading any report becomes a matter of finding the right one, not decoding the whole file from scratch.
A real report, annotated
Here's what a single record looks like once it's unzipped, based on the structure defined in RFC 9990 [1]. Most receivers compress the file (Gmail sends .zip, others send .xml.gz), so the first step is always decompression before you can read anything.
<feedback>
<report_metadata>
<org_name>google.com</org_name>
<email>noreply-dmarc-support@google.com</email>
<report_id>16215678901234567890</report_id>
<date_range>
<begin>1754784000</begin>
<end>1754870399</end>
</date_range>
</report_metadata>
<policy_published>
<domain>redsift.com</domain>
<adkim>r</adkim>
<aspf>r</aspf>
<p>quarantine</p>
<sp>none</sp>
</policy_published>
<record>
<row>
<source_ip>203.0.113.42</source_ip>
<count>842</count>
<policy_evaluated>
<disposition>pass</disposition>
<dkim>pass</dkim>
<spf>pass</spf>
</policy_evaluated>
</row>
<identifiers>
<header_from>redsift.com</header_from>
</identifiers>
<auth_results>
<dkim>
<domain>redsift.com</domain>
<result>pass</result>
<selector>google</selector>
</dkim>
<spf>
<domain>redsift.com</domain>
<result>pass</result>
</spf>
</auth_results>
</record>
</feedback>Here's what each line is actually telling you:
- org_name and date_range: Google generated this report, covering one 24-hour window. Everything below reflects mail sent in that window, not mail sent today.
- policy_published: Google read
redsift.com's DMARC record as relaxed alignment (r) on both DKIM and SPF, with a quarantine policy. This is the policy that was actually applied, not necessarily the one currently live in DNS if it's changed since. - source_ip: 842 messages came from this one IP during the window. This is an aggregate count, not 842 individual log lines.
- disposition:
passmeans the message passed DMARC evaluation under this enforcing policy. Disposition reflects the reported DMARC result/action here, not a guarantee of how the mailbox provider ultimately handled the message. - dkim and spf inside policy_evaluated: Both show
pass, meaning the message aligned under DMARC's rules, not just the raw protocol check. - auth_results: This is the raw, pre-DMARC verdict. DKIM passed using the
googleselector, and SPF passed too. In this case, the raw result and the DMARC-aligned result agree. They won't always.
That last point is the one most people miss on their first pass through a report, and it's worth its own section. But first, a word on the identifiers block, since it's the piece that decides which domain gets credit or blame for everything else in the record.
Reading identifiers: which domain actually matters
The identifiers element only carries up to three fields, and it's easy to skim past because none of them show a pass or fail result. That's exactly why it matters. This block tells you which domains DMARC actually checked, and every other verdict in the record is checked against the values here.
- header_form is the domain in the visible From address, the one your recipient actually sees in their inbox. This is required in every record, and it's the anchor for DMARC's entire alignment logic. Every alignment check in
policy_evaluatedis really asking: does the domain that passed SPF or DKIM match this one? - envelope_form is the domain from the SMTP
MAIL FROMcommand, sometimes called the bounce or return-path domain. Recipients never see this directly, but it's what SPF actually validates against. When a marketing platform sends on your behalf, this often shows their infrastructure domain rather than yours, which is exactly why SPF alignment fails unless you've configured a custom return-path. - envelope_to is optional and shows the recipient domain, useful mainly when you're trying to figure out whether failing traffic is headed to one specific mailbox provider or spread across many.
Here's the part worth internalizing: if header_from shows redsift.com but envelope_from shows bounce.some-esp.com, SPF is being checked against the ESP's domain, not yours. The raw SPF check can pass cleanly against that ESP domain and still fail DMARC alignment, because alignment compares envelope_from back to header_from, and those two don't match unless the ESP is sending through a subdomain you've delegated to them. This single relationship explains more "why did this fail" tickets than almost anything else in the report.
The result that matter
Every record contains separate verdicts on DKIM and SPF, and they answer different questions.
- auth_results answers: "Did the raw SPF or DKIM check pass?" This is the protocol-level result, checked against whatever domain the signature or SPF record actually named.
- policy_evaluated answers: "Did that result count for DMARC?" This is the alignment-checked result, and it's the one that decides your disposition.
The difference is alignment. DMARC doesn't just ask whether SPF or DKIM passed. It asks whether the domain that passed SPF or DKIM matches the domain in your visible From address, either exactly (strict alignment, adkim=s or aspf=s) or at the organizational level (relaxed alignment, the default for most senders) [2]. A message can pass SPF against bounce.thirdpartyesp.com and still fail DMARC, because bounce.thirdpartyesp.com doesn't align with redsift.com in the From header.
This is why you'll sometimes see auth_results show pass while policy_evaluated shows fail for the same record. That gap is almost always your answer when a legitimate sender's mail is landing in quarantine despite "passing" on the vendor's own dashboard. The vendor is showing you the raw SPF or DKIM check. DMARC cares about alignment on top of that.
Reading the disposition column correctly
The disposition field tells you what actually happened to the mail, and it takes four values [1]:
DMARC disposition values and what they mean
Disposition | What happened | When you'll see it |
none | No enforcement action applied | Your policy is p=none, so DMARC results are reported without affecting delivery due to DMARC |
pass | Passed evaluation under an enforcing policy | Reported when a message passes DMARC evaluation under an enforcing policy, meaning no quarantine or reject action was taken |
quarantine | Reported as spam or quarantine | Your policy is p=quarantine and the message failed DMARC |
reject | Reported as blocked before delivery | Your policy is p=reject and the message failed DMARC |
One detail catches almost everyone at some point: if your policy is p=none, most records will show disposition: none, even the ones where both DKIM and SPF failed. That's usually expected, though a receiver can still apply local policy or another override, which is exactly what the reason element (below) covers. At p=none, you're asking receivers to report on what would have happened, not to actually enforce anything. The report is doing its job. Nothing was blocked because you didn't ask for anything to be blocked yet.
If you're seeing a disposition that doesn't match what you'd expect from your published policy, check for a reason element inside policy_evaluated. RFC 9990 defines five override types a receiver can report: local_policy, mailing_list, trusted_forwarder, policy_test_mode, and other [1]. A mailing_list reason on a failing record indicates the receiver attributed the override to mailing-list handling. A common pattern behind that is a list server forwarding the message in a way that breaks SPF alignment and strips or alters the DKIM signature, but the reason value itself doesn't guarantee that exact failure sequence. Either way, it's expected behavior for forwarded mail and not something to chase as a threat.
Four patterns worth recognizing before you escalate anything
Reading one record in isolation only gets you so far. The value of aggregate reports comes from reading many records from the same sender over several days. Four patterns show up constantly, and telling them apart saves you from either ignoring a real problem or chasing a fake one.
1. A legitimate sender you haven't authorized yet. A new source_ip shows up, auth_results shows SPF and DKIM both failing, and the count is consistent day over day. This is usually a marketing platform, CRM, or helpdesk tool someone in the business signed up for without looping in IT. The fix is adding them to SPF and configuring DKIM signing, not blocking them.
2. ESP or cloud provider IP rotation. You've had a sender authorized for months, and suddenly a batch of new IPs appear failing SPF while DKIM still passes. Large senders rotate sending infrastructure regularly. Because DKIM signatures travel with the message regardless of which IP sent it, DKIM alignment staying intact while SPF alignment breaks is a strong signal this is rotation, not spoofing. Check with your ESP to see if anything changed that would impact you.
3. Mailing lists and forwarders. Both SPF and DKIM fail, the reason element shows mailing_list or the count is small and scattered across many source IPs rather than concentrated, and the header_from still shows your domain. This is a colleague forwarding a message or a mailing list redistributing it. It's the expected cost of forwarding under DMARC, and RFC 9989 explicitly accounts for it through indirect mail flow handling [2].
4. Actual spoofing. SPF and DKIM both fail, the source IP has no relationship to any vendor or infrastructure you recognize, there's no reason override, and the volume is either a single burst or a steady trickle you can't attribute to anything internal. This is the pattern worth acting on: confirming your policy is strong enough to quarantine or reject it, and if the volume is high, treating it as a phishing campaign impersonating your domain.
Running these checks by hand across thousands of daily records doesn't scale past a handful of sending sources, which is the entire reason DMARC reporting platforms like Red Sift OnDMARC exist: to surface exactly these four patterns automatically instead of asking you to eyeball XML every morning.
What changed under the 2026 DMARC update
In May 2026, the IETF replaced the original DMARC specification, RFC 7489, with three separate Standards Track documents: RFC 9989 covers the core protocol, RFC 9990 covers aggregate reporting specifically, and RFC 9991 covers failure reporting [1, 2]. If you've been reading aggregate reports for a while, the format you already know still works. Report consumers built against the old schema can generally keep working, since RFC 9990 requires processors to tolerate unsupported extension data [1], but that's not a guarantee: the RFC 9990 namespace and schema differ from RFC 7489, and semantic changes like the pass disposition mean a strict RFC 7489 parser may not accept an RFC 9990 report outright.
A few additions are worth knowing about the next time you're in a report:
- discover_method can now appear inside
policy_published, showing eitherpsl(the legacy Public Suffix List method) ortreewalk(a new DNS-based method for finding your Organizational Domain) [1]. - Selectors are now required, not optional, whenever a DKIM signature appears in
auth_results. Previously some receivers omitted theselectorfield. - A generator field can appear in
report_metadata, naming the specific software that produced the report, useful if you're debugging a receiver that's sending malformed files.
None of this changes how you read a report day to day. Existing parsers and dashboards generally keep working, because RFC 9990's extensibility rules call for unsupported fields to be tolerated rather than rejected [1], this isn't a guarantee of standard XML parsing behavior, but a deliberate rule the RFC sets for handling its own extensions. It's context worth having if you're the one auditing your own parser or evaluating a vendor's, and it's a good reminder that DMARC reporting is still an actively maintained standard, not something frozen in 2015. The bigger structural change is that aggregate reporting now has its own dedicated specification separate from the core protocol, which means the reporting format can now evolve on its own schedule instead of waiting for a full DMARC document revision every time. We've covered the full set of changes, including what it means for your DNS record itself, in our breakdown of the RFC 9989 update.
Why almost nobody reads these by hand at scale
A single mid-size domain can generate reports from 15 to 30 different mailbox providers every day, each with its own file, its own compression format, and its own quirks in how thoroughly it fills out optional fields. None of that is designed for a person to open, unzip, and scroll through daily. It's designed for machines to parse and for the resulting data to be summarized somewhere a person can actually act on it.
Google's bulk sender requirements, in effect since February 2024, require any domain sending 5,000 or more messages a day to Gmail addresses to authenticate with SPF and DKIM and publish a DMARC policy of at least p=none [3]. That single requirement pushed a huge number of domains into DMARC for the first time, and most of them immediately hit the same wall: the reports are correct, complete, and unreadable at any real volume without tooling.
That's the gap Red Sift OnDMARC is built to close. It ingests every report automatically, groups records by sending source instead of by file, flags new or unauthorized senders the day they first appear, and gives you a plain view of pass rate by source so you can decide when it's safe to move from p=none to p=quarantine to p=reject. If you're earlier in that process, our guide to DMARC implementation walks through the full rollout from first record to full enforcement.
Common mistakes people make reading these reports
Reading one report instead of a trend. A single day's report tells you what happened in a 24-hour window. Sending infrastructure changes, mail volume varies, and one-off failures happen. Look at the same source across at least a week before drawing conclusions.
Confusing auth_results with the DMARC verdict. As covered above, these can disagree. If you're troubleshooting why "everything passes on our end" but DMARC still shows failures, alignment is almost always the answer.
Assuming disposition:none means nothing failed. At p=none, it means nothing was blocked, not that everything passed. Check dkim and spf inside policy_evaluated directly.
Ignoring the count field. A record showing one failed message from an unrecognized IP is noise. The same failure pattern showing hundreds of messages a day for two weeks straight is a sender you need to either authorize or block.
Treating every failure as an attack. Most failures in a typical inbox are forwarding, mailing lists, or a vendor that needs an SPF update, not spoofing. Save the urgency for patterns with no internal explanation and no reason override.
Skipping the identifiers block. It has no pass or fail value, so it's easy to jump straight to policy_evaluated. But without checking header_from against envelope_from, you're guessing at why alignment failed instead of knowing.
Where to go from here
A DMARC aggregate report is three XML blocks and a handful of fields repeated for every sending source you have. Once you can name report_metadata, policy_published, and record on sight, and once you know to check policy_evaluated rather than auth_results for your actual DMARC result, the file stops being intimidating. The harder part is doing that same read across dozens of receivers and thousands of records every day without losing the thread.
That's the part worth automating. Red Sift OnDMARC parses every report the moment it arrives, surfaces new and unauthorized senders automatically, and shows you pass rate by source so the decision to tighten your policy is a one-screen check instead of a spreadsheet project.
References
[2] RFC 9989 - Domain-Based Message Authentication, Reporting, and Conformance (DMARC)
Streamline DMARC aggregate reporting with Red Sift OnDMARC




