info8 min readLast updated May 27, 2026

DMARC Aggregate Reports: How to Read Them and What They Reveal

DMARC aggregate reports show who sends email as your domain and whether it passes authentication. Learn how to read RUA reports and act on the data inside them.

What are DMARC aggregate reports?

When you publish a DMARC record, you can include a rua (Reporting URI for Aggregate reports) tag that tells receiving mail servers to send you reports about email they receive claiming to be from your domain:

_dmarc.yourcompany.com.  IN  TXT  "v=DMARC1; p=none; rua=mailto:dmarc-reports@yourcompany.com"

Every major email provider -- Gmail, Microsoft 365, Yahoo, Apple -- sends these reports. They arrive as XML files (usually compressed as .gz or .zip) once per day and contain data about every email that claimed your domain in the From: header.

These reports are the foundation of a safe DMARC deployment. Without them, you are flying blind when tightening your policy from p=none to p=quarantine to p=reject.

What is inside a DMARC aggregate report?

Each report covers a time period (usually 24 hours) and contains rows of data. Each row represents a group of emails from a specific source IP with identical authentication results. The key fields:

Source IP and volume

Which IP address sent the email and how many messages:

<row>
  <source_ip>203.0.113.25</source_ip>
  <count>1524</count>
</row>

Policy evaluated

What the receiving server did with the email based on your DMARC policy:

<policy_evaluated>
  <disposition>none</disposition>
  <dkim>pass</dkim>
  <spf>fail</spf>
</policy_evaluated>

The disposition field shows the action taken: none (delivered normally), quarantine (sent to spam), or reject (bounced). The dkim and spf fields show the DMARC alignment result (not just the raw SPF/DKIM check).

Authentication results

Detailed SPF and DKIM results:

<auth_results>
  <dkim>
    <domain>yourcompany.com</domain>
    <result>pass</result>
    <selector>selector1</selector>
  </dkim>
  <spf>
    <domain>yourcompany.com</domain>
    <result>pass</result>
  </spf>
</auth_results>

Reading raw DMARC XML

The raw XML can be daunting. Here is a minimal example:

<?xml version="1.0" encoding="UTF-8"?>
<feedback>
  <report_metadata>
    <org_name>google.com</org_name>
    <date_range>
      <begin>1716768000</begin>
      <end>1716854400</end>
    </date_range>
  </report_metadata>
  <policy_published>
    <domain>yourcompany.com</domain>
    <p>none</p>
  </policy_published>
  <record>
    <row>
      <source_ip>203.0.113.25</source_ip>
      <count>450</count>
      <policy_evaluated>
        <disposition>none</disposition>
        <dkim>pass</dkim>
        <spf>pass</spf>
      </policy_evaluated>
    </row>
    <identifiers>
      <header_from>yourcompany.com</header_from>
    </identifiers>
    <auth_results>
      <dkim>
        <domain>yourcompany.com</domain>
        <result>pass</result>
      </dkim>
      <spf>
        <domain>yourcompany.com</domain>
        <result>pass</result>
      </spf>
    </auth_results>
  </record>
</feedback>

This tells you: Google received 450 emails from IP 203.0.113.25 claiming to be from yourcompany.com. Both SPF and DKIM passed and were aligned. The disposition was none (delivered normally), consistent with the p=none policy.

Parsing reports from the command line

Extract and read compressed reports

# Reports typically arrive as .gz or .zip
gunzip report.xml.gz
# or
unzip report.zip

# Pretty-print the XML
xmllint --format report.xml | less

Quick summary with command-line tools

# Extract source IPs and counts
xmllint --xpath '//row' report.xml | \
  grep -oP '<source_ip>\K[^<]+|<count>\K[^<]+'

Using a simple Python script

import xml.etree.ElementTree as ET
import sys

tree = ET.parse(sys.argv[1])
root = tree.getroot()

for record in root.findall('.//record'):
    ip = record.find('.//source_ip').text
    count = record.find('.//count').text
    dkim = record.find('.//policy_evaluated/dkim').text
    spf = record.find('.//policy_evaluated/spf').text
    disposition = record.find('.//policy_evaluated/disposition').text
    print(f"IP: {ip:20s} Count: {count:6s} DKIM: {dkim:5s} SPF: {spf:5s} Action: {disposition}")

Free DMARC report analysis tools

Parsing XML manually works for occasional checks, but for ongoing monitoring you need a tool that aggregates reports over time:

  • DMARC Analyzer -- freemium, web-based dashboard
  • Postmark DMARC -- free weekly digests, very beginner-friendly
  • URIports -- free tier, real-time report processing
  • parsedmarc -- open-source, self-hosted, outputs to Elasticsearch/Splunk

To use any of these, point your rua tag at their processing address instead of (or in addition to) your own mailbox:

_dmarc.yourcompany.com.  IN  TXT  "v=DMARC1; p=none; rua=mailto:dmarc@yourcompany.com,mailto:your-id@ag.dmarcanalyzer.com"

What to look for in DMARC reports

Legitimate senders failing authentication

This is the most important thing to identify before tightening your DMARC policy. Look for:

  • Your own mail servers failing SPF because the sending IP is not in your SPF record
  • Third-party services (marketing tools, CRMs, ticketing systems) sending as your domain without proper DKIM signing
  • Forwarded email failing SPF (this is normal -- email forwarding breaks SPF because the forwarding server's IP is not in your SPF record)

Fix each of these before moving from p=none to p=quarantine.

Unknown senders passing authentication

If an IP address you do not recognise is sending email as your domain and passing SPF or DKIM, investigate:

  • Is it a third-party service a team member set up without telling IT?
  • Is it a partner or vendor sending on your behalf?
  • Is it an attacker who has compromised your SPF record or DKIM key?

High volumes of failing email

Large numbers of emails failing both SPF and DKIM from IPs you do not recognise are likely spoofing attempts. This is exactly what DMARC is designed to catch. When you move to p=reject, these will be blocked.

Forwarding services

Email forwarding (mailing lists, alumni addresses, personal forwarding rules) causes SPF failures because the forwarding server's IP is not in your SPF record. DKIM usually still passes because the signature travels with the message. This is why DKIM is critical -- it survives forwarding while SPF does not.

The path from p=none to p=reject

DMARC aggregate reports enable a safe, gradual policy rollout:

Phase 1: p=none (monitoring only)

_dmarc.yourcompany.com.  IN  TXT  "v=DMARC1; p=none; rua=mailto:dmarc@yourcompany.com"

Collect reports for at least 2--4 weeks. Identify all legitimate senders and fix authentication issues.

Phase 2: p=quarantine with pct

_dmarc.yourcompany.com.  IN  TXT  "v=DMARC1; p=quarantine; pct=10; rua=mailto:dmarc@yourcompany.com"

Start by quarantining only 10% of failing email. Monitor reports for problems. Gradually increase pct to 25, 50, 100.

Phase 3: p=reject

_dmarc.yourcompany.com.  IN  TXT  "v=DMARC1; p=reject; rua=mailto:dmarc@yourcompany.com"

Full protection. Unauthorised email claiming your domain is rejected. Continue monitoring reports to catch any newly added services that need authentication.

For the full policy explanation, see our DMARC policy guide. For the broader email authentication picture, see email spoofing prevention.

How SurfaceScan helps

SurfaceScan monitors your DMARC configuration and processes your aggregate reports to surface actionable insights. The Email Security dashboard shows which IPs are sending as your domain, whether they pass authentication, and which legitimate senders still need to be configured. SurfaceScan flags domains stuck on p=none for too long and guides you through the rollout to p=reject, with clear visibility into what would break at each stage.

Related articles