How to Read DMARC Reports โ€” Aggregate & Forensic Report Analysis Guide (2026)

Published April 26, 2026 ยท 15 min read

๐Ÿ“Š Analyze Your DMARC Reports

Set up DMARC reporting and start receiving data about who's sending email from your domain. Check your DMARC configuration first.

Free DMARC Check โ†’

DMARC reports are the most powerful โ€” and most underused โ€” tool in email deliverability. They tell you exactly which IP addresses are sending email from your domain, whether those emails pass SPF and DKIM authentication, and whether DMARC policy is being applied correctly. If you're not reading your DMARC reports, you're flying blind on email security.

This guide covers everything you need to know about DMARC reports: what they are, how to set them up, how to read the XML format, and what actions to take based on what you find.

What Are DMARC Reports?

DMARC (Domain-based Message Authentication, Reporting, and Conformance) generates two types of reports:

Report TypeTagFrequencyContentValue
AggregateruaDaily (typically)Summary statistics: IP, volume, SPF/DKIM pass/fail๐Ÿ”ด Essential
ForensicrufPer failureIndividual message details: headers, delivery result๐ŸŸก Nice-to-have

Aggregate reports (rua) are the primary tool. They provide daily summaries of all email received from your domain by each reporting provider. If you only set up one thing, make it rua reporting.

Forensic reports (ruf) contain individual message samples. They're useful for debugging specific failures but are increasingly rare โ€” Google stopped sending ruf reports in 2022, and most major receivers no longer support them.

Setting Up DMARC Reporting

Step 1: Create the rua DMARC Record

Add the rua tag to your DMARC TXT record. Start with p=none so you can collect data without affecting delivery:

# DMARC record with aggregate reporting
_dmarc.yourdomain.com  TXT  "v=DMARC1;p=none;rua=mailto:dmarc@yourdomain.com"

The rua tag tells receivers where to send aggregate reports. You'll typically receive reports within 24-48 hours.

Step 2: Verify the Reporting Address

Before receivers will send reports to your rua address, you must verify you own the reporting domain. If rua points to an address on the same domain (e.g., rua=mailto:dmarc@yourdomain.com on yourdomain.com), verification is automatic.

If rua points to a different domain, you need to add a verification record on that domain:

# On the reporting domain (reports.example.com)
yourdomain.com._report._dmarc.reports.example.com  TXT  "v=DMARC1"

Step 3: Receive and Process Reports

Reports arrive as compressed XML attachments (usually .gz or .zip) to your rua email address. You can:

Pro tip: Most domains receive reports from 5-15 different receivers. Google/Gmail and Microsoft send the most reports and are the most important to monitor. Set up a dedicated email address (e.g., dmarc-reports@yourdomain.com) with enough storage โ€” reports can be 100KB-10MB each.

DMARC Aggregate Report XML Structure

Let's walk through a real DMARC aggregate report. Here's what a typical report looks like:

<?xml version="1.0" encoding="UTF-8"?>
<feedback>
  <report_metadata>
    <org_name>google.com</org_name>
    <email>noreply-dmarc-report@google.com</email>
    <report_id>12345678901234567890</report_id>
    <date_range>
      <begin>1742918400</begin>
      <end>1743004799</end>
    </date_range>
  </report_metadata>

  <policy_published>
    <domain>yourdomain.com</domain>
    <adkim>r</adkim>
    <aspf>r</aspf>
    <p>none</p>
    <sp>none</sp>
    <pct>100</pct>
  </policy_published>

  <record>
    <row>
      <source_ip>209.85.128.45</source_ip>
      <count>847</count>
      <policy_evaluated>
        <disposition>none</disposition>
        <dkim>pass</dkim>
        <spf>pass</spf>
      </policy_evaluated>
    </row>
    <identifiers>
      <header_from>yourdomain.com</header_from>
    </identifiers>
    <auth_results>
      <dkim>
        <domain>yourdomain.com</domain>
        <result>pass</result>
        <selector>google</selector>
      </dkim>
      <spf>
        <domain>yourdomain.com</domain>
        <result>pass</result>
      </spf>
    </auth_results>
  </record>

  <record>
    <row>
      <source_ip>45.33.32.156</source_ip>
      <count>23</count>
      <policy_evaluated>
        <disposition>none</disposition>
        <dkim>fail</dkim>
        <spf>fail</spf>
      </policy_evaluated>
    </row>
    <identifiers>
      <header_from>yourdomain.com</header_from>
    </identifiers>
    <auth_results>
      <dkim>
        <domain>yourdomain.com</domain>
        <result>fail</result>
      </dkim>
      <spf>
        <domain>yourdomain.com</domain>
        <result>fail</result>
      </spf>
    </auth_results>
  </record>
</feedback>

Breaking Down the DMARC Report

report_metadata โ€” Who Sent the Report

<report_metadata>
  <org_name>google.com</org_name>
  <email>noreply-dmarc-report@google.com</email>
  <report_id>12345678901234567890</report_id>
  <date_range>
    <begin>1742918400</begin>
    <end>1743004799</end>
  </date_range>
</report_metadata>
FieldMeaningWhat to Look For
org_nameWhich receiver sent this reportMost reports come from Google, Microsoft, Yahoo
report_idUnique ID for this reportUseful for deduplication
date_rangeTime period (Unix timestamps)Reports typically cover 24 hours

policy_published โ€” Your DMARC Policy

<policy_published>
  <domain>yourdomain.com</domain>
  <adkim>r</adkim>
  <aspf>r</aspf>
  <p>none</p>
  <sp>none</sp>
  <pct>100</pct>
</policy_published>
FieldMeaningRecommended Value
domainThe domain this policy applies toYour sending domain
adkimDKIM alignment mode (r=relaxed, s=strict)r (relaxed)
aspfSPF alignment mode (r=relaxed, s=strict)r (relaxed)
pDomain policy (none/quarantine/reject)Start none, move to quarantine, then reject
spSubdomain policyMatch p or set reject for subdomains
pctPercentage of messages policy applies to100

record โ€” The Most Important Part

Each <record> represents a group of messages from the same source IP with the same authentication results. This is where you find actionable data.

Key fields:

How to Analyze DMARC Reports: 5 Key Questions

1. Are Unknown IPs Sending From Your Domain?

The source_ip field reveals who's sending email from your domain. Compare against your known sending IPs:

# Look up who owns an unknown IP
whois 45.33.32.156

# Or use our API to check
curl https://korpo.pro/api/v1/check/yourdomain.com

If you find IPs you don't recognize, it could be:

2. Are Your Legitimate Emails Passing Authentication?

Check that your known sending services (Google Workspace, SendGrid, Mailchimp, etc.) appear in reports with dkim=pass and spf=pass. If legitimate senders are failing SPF or DKIM, your SPF or DKIM configuration needs fixing.

3. What Is Your DMARC Alignment Rate?

DMARC alignment means the domain in the From header matches the domain in the SPF or DKIM signature. Look at the policy_evaluated section:

4. What Disposition Are Failed Messages Receiving?

If your policy is p=quarantine or p=reject, check the disposition field for failed messages:

Important: Before switching from p=none to p=quarantine or p=reject, ensure that 100% of your legitimate email passes DMARC. Check your rua reports for at least 2 weeks. Any legitimate email failing DMARC will be spammed or bounced under a stricter policy.

5. Are There Volume Anomalies?

Sudden spikes in volume from unknown IPs can indicate a phishing campaign using your domain. Conversely, sudden drops in volume from known IPs may indicate delivery problems.

Common DMARC Report Findings and What They Mean

Finding: Unknown IP Failing SPF and DKIM

<row>
  <source_ip>198.51.100.22</source_ip>
  <count>156</count>
  <policy_evaluated>
    <dkim>fail</dkim>
    <spf>fail</spf>
  </policy_evaluated>
</row>

Meaning: Someone is sending email from your domain from an IP address that's not authorized. This is likely email spoofing.

Action: If p=none, switch to p=reject to block these. Also check your SPF record to ensure it's not too permissive.

Finding: Known IP Failing SPF but Passing DKIM

<row>
  <source_ip>169.55.62.74</source_ip>
  <count>34</count>
  <policy_evaluated>
    <dkim>pass</dkim>
    <spf>fail</spf>
  </policy_evaluated>
</row>

Meaning: A legitimate sending service (DKIM passes) but SPF fails โ€” you may not have included it in your SPF record, or there's an alignment issue.

Action: Add the sending service to your SPF record, or flatten your SPF record if you're hitting the DNS lookup limit.

Finding: Known IP Passing SPF but Failing DKIM

<row>
  <source_ip>209.85.128.45</source_ip>
  <count>847</count>
  <policy_evaluated>
    <dkim>fail</dkim>
    <spf>pass</spf>
  </policy_evaluated>
</row>

Meaning: SPF authenticates the email, but DKIM signature is broken or using the wrong selector. This still passes DMARC (SPF aligns), but DKIM should be fixed for maximum protection.

Action: Check your DKIM configuration โ€” the selector may be wrong, the key may have rotated, or the service may not be signing correctly.

Finding: Local Part of From Address Differs

<identifiers>
  <header_from>yourdomain.com</header_from>
</identifiers>
<auth_results>
  <spf>
    <domain>news.yourdomain.com</domain>
    <result>pass</result>
  </spf>
</auth_results>

Meaning: The From address is yourdomain.com but the SPF domain is news.yourdomain.com. Under relaxed alignment (aspf=r), this passes DMARC because the organizational domain matches. Under strict alignment (aspf=s), this would fail.

Action: Verify that relaxed alignment is appropriate for your setup. Most organizations use aspf=r.

Automating DMARC Report Analysis

Manually opening and reading XML reports is impractical beyond a few domains. Here are approaches for automation:

Approach 1: Simple Python Parser

#!/usr/bin/env python3
"""Parse DMARC aggregate reports from zip/gz attachments."""
import xml.etree.ElementTree as ET
import zipfile, gzip, sys

def parse_dmarc_report(filepath):
    """Parse a DMARC aggregate report file."""
    if filepath.endswith('.gz'):
        with gzip.open(filepath) as f:
            tree = ET.parse(f)
    elif filepath.endswith('.zip'):
        with zipfile.ZipFile(filepath) as z:
            xml_files = [n for n in z.namelist() if n.endswith('.xml')]
            tree = ET.parse(z.open(xml_files[0]))
    else:
        tree = ET.parse(filepath)

    root = tree.getroot()
    ns = {'d': 'urn:ietf:params:dmarc:dmarc-1.0'}

    metadata = root.find('d:report_metadata', ns)
    org = metadata.find('d:org_name', ns).text

    results = []
    for record in root.findall('d:record', ns):
        row = record.find('d:row', ns)
        source_ip = row.find('d:source_ip', ns).text
        count = int(row.find('d:count', ns).text)
        eval = row.find('d:policy_evaluated', ns)
        dkim = eval.find('d:dkim', ns).text
        spf = eval.find('d:spf', ns).text
        disposition = eval.find('d:disposition', ns).text

        auth = record.find('d:auth_results', ns)
        spf_result = auth.find('d:spf/d:result', ns).text

        results.append({
            'source': org,
            'ip': source_ip,
            'count': count,
            'dkim': dkim,
            'spf': spf,
            'spf_detail': spf_result,
            'disposition': disposition
        })

    return results

# Print failing records only
for r in parse_dmarc_report(sys.argv[1]):
    if r['dkim'] == 'fail' or r['spf'] == 'fail':
        print(f"โš ๏ธ  {r['ip']}: SPF={r['spf']} DKIM={r['dkim']} "
              f"count={r['count']} from={r['source']}")

Approach 2: API-Based Monitoring

# Check your domain's DMARC status with our API
curl https://korpo.pro/api/v1/check/yourdomain.com

# Monitor multiple sending domains
curl -X POST https://korpo.pro/api/v1/batch \
  -H "Content-Type: application/json" \
  -d '{"domains":["yourdomain.com","news.yourdomain.com","mail.yourdomain.com"]}'

Approach 3: DMARC Reporting Services

Several services parse DMARC reports and provide dashboards with trend analysis, alerts, and automated policy recommendations:

DMARC Report Timeline: From p=none to p=reject

PhasePolicyDurationWhat You're Looking For
1. Monitorp=none2-4 weeksAll legitimate senders identified, all passing auth
2. Quarantinep=quarantine2-4 weeksFailed messages go to spam, no legitimate mail affected
3. Rejectp=rejectOngoingFailed messages are rejected, spoofing is blocked

At each phase, monitor your DMARC reports to confirm:

Pro tip: Don't skip the p=none phase. It typically reveals sending services you forgot about โ€” automated emails from CRMs, internal tools, or third-party services. Moving to p=reject too early blocks these legitimate emails.

DMARC Forensic Reports (ruf)

Forensic reports (also called failure reports) contain individual message samples when authentication fails. They include:

Note: Forensic reports raise privacy concerns because they contain email content. Google stopped sending ruf reports in 2022, and most major senders no longer support them. Aggregate (rua) reports are sufficient for nearly all monitoring needs.

If you do want forensic reports, add ruf to your DMARC record:

_dmarc.yourdomain.com  TXT  "v=DMARC1;p=none;rua=mailto:dmarc@yourdomain.com;ruf=mailto:dmarc-fail@yourdomain.com"

Detecting Email Spoofing with DMARC Reports

DMARC reports are your best early warning system for email spoofing. Here's how to identify spoofing attempts:

Red Flags in DMARC Reports

Responding to Spoofing

  1. If p=none: Switch to p=reject immediately to block spoofed messages
  2. If p=quarantine: Switch to p=reject for stronger enforcement
  3. If p=reject:Spoofed messages are already being blocked โ€” verify in reports
  4. Document the IP โ€” Check whois to identify the attacker's hosting provider
  5. Report to the IP's abuse contact โ€” Find via whois or the hosting provider's abuse desk

Frequently Asked Questions

What is a DMARC aggregate report?

A DMARC aggregate report (rua) is an XML file sent daily by email receivers, summarizing all email they received from your domain. It includes counts of messages that passed or failed SPF and DKIM authentication, the source IP addresses, and the DMARC policy results.

What is the difference between DMARC rua and ruf?

rua (reporting URI for aggregate reports) are daily summary statistics. ruf (reporting URI for forensic reports) are individual failure reports sent per-message. rua is essential for monitoring; ruf provides detailed samples but is rarely supported by major receivers in 2026.

How do I set up DMARC reporting?

Add rua=mailto:dmarc@yourdomain.com to your DMARC DNS record. For forensic reports add ruf=mailto:dmarc@yourdomain.com. Most receivers send reports within 24 hours. Use our DMARC setup guide for step-by-step instructions.

What should I look for in DMARC reports?

Focus on: 1) IP addresses sending email that fail DMARC (potential spoofers), 2) SPF/DKIM failures from your legitimate sending services (configuration issues), 3) Volume trends over time, 4) New unauthorized senders appearing in reports.

How long should I monitor with p=none before moving to p=quarantine?

Monitor for at least 2-4 weeks with p=none. You need enough data to identify all legitimate senders (including infrequent ones like quarterly email services). Once all legitimate email passes DMARC and only unauthorized email fails, move to p=quarantine for 1-2 weeks, then p=reject.

๐Ÿ“Š Start Monitoring Your Domain Today

Check your DMARC configuration and set up aggregate reporting. Our free tool validates your DMARC record and shows you exactly what needs fixing.

Free DMARC Check โ†’

Related Guides