The short answer: A DMARC aggregate report is a compressed XML file, usually sent once a day by each mailbox provider that received mail claiming to be from your domain. Each row lists a sending IP, a message count, and whether SPF and DKIM passed DMARC alignment. You receive them by adding rua=mailto:address to your DMARC record, and you read them to find unauthenticated senders before enforcing.
How to Turn On Aggregate Reports
Add a rua tag to the DMARC TXT record at _dmarc.yourdomain.com:
v=DMARC1; p=none; rua=mailto:dmarc-reports@yourdomain.com
Details worth knowing:
- Reports arrive as email attachments, typically
.xml.gzor.zip. The filename follows the patternreceiver!your-domain!begin-timestamp!end-timestamp!id.xml.gz. - Most large providers send one report per day covering a 24-hour window. Not every provider sends reports, and small ones may send nothing.
- Use a dedicated mailbox or group. Volume grows with your sending, and an inbox nobody reads defeats the purpose.
- You can list several addresses separated by commas, for example your own mailbox and a monitoring service.
- Sending reports to a different domain (a monitoring vendor, for instance) requires that domain to authorise it with a TXT record at
yourdomain.com._report._dmarc.vendor-domain.comcontainingv=DMARC1. Reputable services handle this for you. If reports never arrive, a missing authorisation record is the usual cause.
Do not have a record yet? The DMARC record generator builds one with rua included.
A Sample DMARC XML Report
This is a realistic excerpt using documentation domains and IP ranges (example.com, 192.0.2.0/24, 198.51.100.0/24):
<?xml version="1.0" encoding="UTF-8" ?>
<feedback>
<report_metadata>
<org_name>google.com</org_name>
<email>noreply-dmarc-support@google.com</email>
<report_id>13478092765543210987</report_id>
<date_range>
<begin>1759622400</begin>
<end>1759708799</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>192.0.2.25</source_ip>
<count>412</count>
<policy_evaluated>
<disposition>none</disposition>
<dkim>pass</dkim>
<spf>pass</spf>
</policy_evaluated>
</row>
<identifiers>
<header_from>example.com</header_from>
</identifiers>
<auth_results>
<dkim>
<domain>example.com</domain>
<result>pass</result>
<selector>google</selector>
</dkim>
<spf>
<domain>example.com</domain>
<result>pass</result>
</spf>
</auth_results>
</record>
<record>
<row>
<source_ip>198.51.100.77</source_ip>
<count>63</count>
<policy_evaluated>
<disposition>none</disposition>
<dkim>fail</dkim>
<spf>fail</spf>
</policy_evaluated>
</row>
<identifiers>
<header_from>example.com</header_from>
</identifiers>
<auth_results>
<dkim>
<domain>mailer.vendor-example.net</domain>
<result>pass</result>
<selector>s1</selector>
</dkim>
<spf>
<domain>bounces.vendor-example.net</domain>
<result>pass</result>
</spf>
</auth_results>
</record>
</feedback>
Reports can contain hundreds of record blocks. Aggregators group identical results into a single row with a count, which is why you see numbers rather than individual messages. No message content or subject lines are included.
Reading the Report, Element by Element
report_metadata
Who sent the report and for what period. org_name is the receiver (google.com, Yahoo, Microsoft and so on), and date_range holds Unix timestamps for the start and end of the window. Use it to confirm which providers are reporting and to spot gaps. Action: none, but if a major provider never appears, check your rua address and external authorisation.
policy_published
Your DMARC record as the receiver saw it: p, sp, pct, and the alignment modes adkim and aspf (r relaxed, s strict). Action: compare it with what you meant to publish. If it shows p=none after you changed it to quarantine, the receiver cached the old record or you have a typo or duplicate record.
record / row
Each row is one sending source: source_ip and count. This is where sender discovery happens. In the sample, 192.0.2.25 sent 412 messages and 198.51.100.77 sent 63. Action: look up each IP (WHOIS, reverse DNS) and match it to a service you know. Unknown IPs with significant volume need investigating before you enforce.
policy_evaluated
The result of DMARC evaluation for that row.
disposition: what the receiver actually did (none,quarantine,reject).dkimandspf: whether each mechanism passed with alignment.
A reason element (type values include forwarded, mailing_list, local_policy, sampled_out) may appear when the receiver overrode your policy. Action: treat the first row as healthy. For the second row, both are fail, so DMARC fails, which is the important finding.
identifiers
header_from is the domain in the visible From address that DMARC evaluated. Subdomains show up here. Action: check whether failing mail is from your main domain or a subdomain you forgot about.
auth_results
The raw SPF and DKIM outcomes before the alignment check. This block, compared with policy_evaluated, explains the failure. In the second record, DKIM passed for mailer.vendor-example.net and SPF passed for bounces.vendor-example.net, but neither domain matches example.com, so alignment fails. That is the classic third-party sender problem. Action: configure the vendor to sign with your domain (DKIM) or use a custom return-path on your domain (SPF).
Quick reference for policy_evaluated and auth_results together:
| policy_evaluated | auth_results | Meaning | Fix |
|---|---|---|---|
| spf pass, dkim pass | pass, pass | Fully aligned | Nothing |
| spf fail, dkim pass | spf pass for another domain | SPF unaligned, DKIM carries DMARC | Optional: align return-path |
| spf pass, dkim fail | dkim pass for vendor's domain | DKIM unaligned | Publish vendor DKIM records for your domain |
| both fail | both pass for vendor's domain | Third-party sender, nothing aligned | Set up custom domain authentication at vendor |
| both fail | both fail | Unauthorised sender or broken config | Identify the IP; fix or block |
What to Do About Each Finding
- Known sender, fully passing: record it in a sender inventory. Done.
- Known sender, failing: authenticate it (SPF include, DKIM CNAME, custom return-path) and recheck in 24–48 hours. See adding third-party senders to SPF.
- Unknown IP, high volume: treat as a shadow IT tool or compromised account until proven otherwise. Find the owner.
- Unknown IP, low volume, both fail: probably spoofing. It will be blocked at
p=reject; no fix needed. - Forwarded or list mail failing: forwarding breaks SPF, and lists can break DKIM. Expect a small residual failure rate. ARC lets intermediaries preserve results, but you cannot control it from your side.
- SPF
permerror: you exceed ten DNS lookups; see SPF too many DNS lookups.
When reports are consistently clean, use them to time your move through enforcement, as described in DMARC policy: none vs quarantine vs reject.
A Weekly Review Routine
Reading every report is not necessary. A repeatable routine is:
- Collect a week of reports into one folder or dashboard.
- Total by source. Group rows by
source_ip(or by sender name if your tool resolves it) and add upcount. - Mark each source as known and passing, known and failing, or unknown.
- Fix known failures first. These are real mail from real systems, and they are the ones that bounce after enforcement.
- Investigate unknowns by volume. Start with the biggest.
- Note the pass rate (DMARC-passing messages divided by total) and record it week over week. A rising rate with no new unknown sources is your signal to move up a policy level.
Expect to see small numbers of failures that never go away: forwarded mail, mailing lists, and random spoofing attempts. They are not a reason to delay enforcement forever, but each should be understood.
Quick-and-Dirty XML Summary
If you want a count without installing anything, this short Python script prints failing rows from an uncompressed report:
import sys, xml.etree.ElementTree as ET
root = ET.parse(sys.argv[1]).getroot()
for rec in root.iter("record"):
row = rec.find("row")
ev = row.find("policy_evaluated")
dkim, spf = ev.findtext("dkim"), ev.findtext("spf")
if dkim != "pass" and spf != "pass":
print(row.findtext("source_ip"), row.findtext("count"),
rec.findtext("identifiers/header_from"))
Run it with python summary.py report.xml after unzipping. It lists every source that failed DMARC with its message count and From domain. For continuous, multi-provider ingestion use a proper parser rather than a script like this.
Why Reports From Different Providers Look Different
Aggregate reports follow a shared schema, but receivers fill it in differently. Some points to expect:
- Counts are not totals. Providers may only report mail they evaluated, and some sample very high volumes.
- Large providers dominate. Gmail, Microsoft and Yahoo account for most of the data, so a small provider's mail to your customers can fail without ever appearing.
- Dispositions can differ from your policy. A
reasonelement such aslocal_policyormailing_listmeans the receiver made its own decision. - Timing differs. Reports for the same day can arrive hours apart, so judge trends over days, not hours.
- Internal mail appears too. Mail your own staff send to colleagues is evaluated and reported like any other.
Do You Need a DMARC Report Analyzer?
You can read raw XML for a low-volume domain. Past a handful of reports a day it is impractical, so most people use a DMARC report analyzer: a service or tool that receives the XML, parses it, and groups senders by name. Options range from free email digests to hosted dashboards and self-hosted parsers such as parsedmarc. The best DMARC monitoring tools comparison covers free and paid choices, and PowerDMARC alternatives covers one platform in detail.
For a one-off look, unzip the file and open it in a browser or text editor with an XML viewer. Note that Google Postmaster Tools is not a replacement: it reports on Gmail traffic from your domain, not aggregate DMARC XML from multiple providers.
Frequently Asked Questions
What is a DMARC aggregate report?
It is a daily XML summary from a receiving mail provider, listing the IPs that sent mail as your domain, message counts, and SPF, DKIM and DMARC results. It contains no message content.
How often do DMARC aggregate reports arrive?
Most providers send one per day for the previous 24 hours, though timing varies and some send less often. The ri tag can request another interval, but providers are not obliged to honour it.
Why am I not receiving DMARC reports?
Common causes are a missing or misspelled rua tag, a mailto: typo, reports landing in spam, or an external reporting domain that has not authorised your domain. Low sending volume also means some providers send nothing.
What is the difference between aggregate and forensic reports?
Aggregate reports (rua) are statistical summaries. Forensic or failure reports (ruf) are per-message samples, and many providers do not send them for privacy reasons.
Can I read DMARC XML reports without a tool?
Yes. Decompress the .gz or .zip, open the XML, and read each record. It works for small domains but does not scale.
Why does a failing row show disposition none?
Because your policy is p=none, or the receiver chose not to act. Disposition records what the receiver actually did, so failing rows with none at p=none are expected. They show you what would be affected once you enforce.
Is DMARC data safe to share with a third-party analyzer?
Reports contain IPs, domains and counts, not message bodies. Still, review a vendor's privacy terms before pointing rua at them.
Next step: Check that your reporting address is published with the DMARC checker, or request a free email audit. Setting up on Google? Follow the Google Workspace DMARC guide.