How to Read DMARC Aggregate Reports (Without Losing Your Mind)

You added a rua= address to your DMARC record, and now a zipped XML file arrives from Google, Microsoft and a dozen other senders every day. Most people glance at one, see a wall of angle brackets, and stop reading. That's a shame, because these reports are the only way to find out who is sending mail as your domain before you tell the world to reject anything that fails.

Where the reports come from

Aggregate reports exist because your DMARC record asked for them. Check yours:

dig TXT _dmarc.example.com +short

You're looking for something like:

"v=DMARC1; p=none; rua=mailto:[email protected]"

The rua tag is the address that receives aggregate reports. Receivers that support reporting send one per reporting period, and RFC 7489 sets the default interval (the ri tag) at 86400 seconds, which is one day. Each report is an XML file, usually gzip-compressed, attached to an email.

If the rua address is on a different domain from the one being reported on, say you send reports for example.com to a service at reports.example.net, the receiving domain has to agree to accept them. RFC 7489 does this with a TXT record at example.com._report._dmarc.reports.example.net. Reporting services normally set that up for you, but it's the first thing to check if reports never arrive.

Our email security checker will read your DMARC record and show you whether the rua tag is there and well-formed.

The anatomy of a report

Every report follows the schema in RFC 7489 Appendix C. Stripped down, it looks like this:

<feedback>
  <report_metadata>
    <org_name>google.com</org_name>
    <report_id>1234567890</report_id>
    <date_range><begin>1790000000</begin><end>1790086399</end></date_range>
  </report_metadata>
  <policy_published>
    <domain>example.com</domain>
    <adkim>r</adkim><aspf>r</aspf>
    <p>none</p><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>fail</spf>
      </policy_evaluated>
    </row>
    <identifiers><header_from>example.com</header_from></identifiers>
    <auth_results>
      <dkim><domain>example.com</domain><selector>s1</selector><result>pass</result></dkim>
      <spf><domain>bounces.esp.example.net</domain><result>pass</result></spf>
    </auth_results>
  </record>
</feedback>

Here's what each part tells you.

report_metadata says who sent the report and which period it covers. The begin and end values are Unix timestamps.

policy_published is your DMARC record as the receiver saw it. If this doesn't match what you think you published, you've found a DNS problem before you've even looked at the data.

record is where the real information lives, and a report can hold many of them. Each one groups messages that share the same sending IP and results. source_ip is where the mail came from and count is how many messages matched.

The part everyone misreads

Look at the example again. SPF says fail under policy_evaluated but pass under auth_results. That isn't a contradiction, and understanding why is most of the battle.

auth_results holds the raw results. SPF checked bounces.esp.example.net, the envelope sender domain, and it passed. DKIM verified a signature from example.com and passed.

policy_evaluated holds the DMARC results, which add one more requirement: alignment. The authenticated domain has to match the domain in the visible From header, which is header_from. SPF passed for bounces.esp.example.net, which isn't example.com, so for DMARC purposes SPF failed. DKIM passed for example.com itself, so it aligned.

DMARC only needs one of the two to pass and align. Here DKIM did, so the message passes DMARC overall. This pattern is completely normal for mail sent through an email service provider that uses its own bounce domain. It's why DKIM signing with your own domain matters so much.

The disposition field tells you what the receiver did: none, quarantine or reject. With p=none it will nearly always say none, whatever the results.

Sorting the sources

Reading reports is really a sorting exercise. Group the records by source_ip and put each one in a bucket.

Your own servers and services. Your mail provider, your CRM, your helpdesk, your billing system. These should pass. If one fails, that service needs DKIM set up with your domain or adding to your SPF record. Keep an eye on SPF's 10-lookup limit while you do that, which is covered in fixing SPF "too many DNS lookups".

Forwarders and mailing lists. When someone forwards your mail, it arrives from their server, so SPF fails. DKIM usually survives unless the forwarder modified the message. Mailing lists that add footers or change subject lines break DKIM too. You'll see these as small counts from IPs belonging to universities, ISPs and list servers. Some receivers mark them with a reason element such as forwarded or mailing_list.

Spoofers. IPs with no connection to you, failing both checks, often sending from hosting providers or residential ranges in bulk. This is exactly what DMARC exists to stop.

To work out which bucket an IP belongs in, look it up. Our IP lookup shows the ISP and network behind an address, and a reverse DNS lookup often gives the game away. A PTR like mail-sor-f41.google.com is a different story from a generic residential hostname.

Moving from p=none to reject

Once your own services all pass consistently, you can tighten the policy. A cautious path looks like this:

  1. Stay at p=none until at least a few weeks of reports show your legitimate sources passing.
  2. Move to p=quarantine, optionally with pct= set low so only a share of failing mail is affected.
  3. Raise pct gradually and watch for new failing sources that turn out to be yours.
  4. Move to p=reject.

Keep reading the reports after you get there. A new marketing tool somebody signed up for without telling IT will show up as a failing source long before anyone complains about missing mail. If you haven't set up the underlying records yet, start with SPF, DKIM and DMARC explained.

Frequently Asked Questions

Why do different receivers send different numbers?

Each receiver only reports on mail it received. Google reports what reached Gmail, Microsoft reports what reached Outlook. No single report shows everything, which is why people use tools that merge them.

Do I need a paid service to read DMARC reports?

No. For a small domain with a handful of senders, opening the XML in a text editor once a week works. Paid or free parsing services earn their keep at higher volumes, where they merge reports and resolve IPs to names for you.

What's the difference between rua and ruf?

rua receives aggregate reports: counts and results, no message content. ruf asks for forensic or failure reports about individual messages. Many large receivers don't send failure reports at all, largely for privacy reasons, so don't be surprised if ruf stays quiet.

I set up rua but get no reports. Why?

Check the record's syntax first, including the mailto: prefix. If the address is on another domain, make sure the _report._dmarc authorisation record exists. And give it a couple of days, since reports cover a full day and arrive after it ends.

Was this useful?
Share

Related reading

0 comments

No comments yet. Be the first.

Leave a comment

Comments are reviewed before they appear. Your email is optional, is never published, and is only used if we need to reply.

« Back to Blog