Email and DNS

How to read DMARC aggregate reports

By Matt Chapman/Published /Updated /7 min read

On this page
  1. What arrives and how to open it
  2. The three parts of a report
  3. Read one record: a worked example
  4. Raw results versus DMARC results
  5. Decide what to do with each sender
  6. Check your fix worked
  7. Common puzzles

A DMARC aggregate report is a daily summary, in XML, from one mail provider about all the mail it received that claimed to come from your domain. Each <record> is one sending server: its IP address, how many messages it sent, and whether those messages passed DMARC. The two lines that matter most sit inside <policy_evaluated>: if either <dkim> or <spf> says pass, that mail passed DMARC.

The rest of the report explains why. This guide goes through a real-shaped report a section at a time, shows the one comparison that catches most problems, and ends with how to prove a fix worked. If you haven’t published a DMARC record with a rua address yet, start with how to set up DMARC and come back when the reports arrive.

What arrives and how to open it

Reports come as email attachments to the address in your rua tag. The standard says the attachment is an XML file, usually gzipped, with a file name built from four parts separated by !:

reporter.example!example.co.uk!1793750400!1793836799.xml.gz

That reads: the reporting organisation’s domain (reporter.example here), your domain, then the start and end of the reporting period as Unix timestamps (seconds since 1 January 1970, in UTC). Those two numbers are 00:00:00 and 23:59:59 UTC on 4 November 2026. Some file names carry an extra unique ID before the extension, and you may also get .zip files; they open the same way.

To read one by hand, save the attachment (not the email), unzip it, and open the .xml file in a text editor or a browser. To read a few weeks of them at once, drop the attachments straight into the DMARC report analyser. It unpacks .gz and .zip files in your browser, adds up every record and groups them by sending IP address; nothing is uploaded.

Reports contain no message content and no personal email addresses. The standard is explicit about this: they hold domain-level authentication results, IP addresses of sending servers, and counts.

The three parts of a report

Every aggregate report has the same skeleton under a <feedback> root:

SectionWhat it tells you
report_metadataWho sent the report (org_name, email), a unique report_id, and the date_range as Unix timestamps. An error element here means the reporter had trouble with your DMARC record.
policy_publishedYour DMARC record as the reporter saw it: domain, p, and optionally sp, np, adkim, aspf, fo and testing. Check it matches what you think you published.
record (one or more)One per combination of sending IP, result and domains. This is where the work is.

Each record has three parts of its own:

  • row: source_ip (the server that connected), count (messages), and policy_evaluated, which holds the DMARC verdict: disposition (what the receiver did) and the aligned dkim and spf results, each simply pass or fail.
  • identifiers: header_from, the domain in the From address your recipients see. Newer reports may also show envelope_from (the bounce address domain SPF checked) and envelope_to.
  • auth_results: the raw SPF and DKIM results, with the domain each one was checked against. For DKIM you also get the selector, which often tells you which service signed the message.

Read one record: a worked example

Here is a trimmed report for example.co.uk, which has p=none and sends from Microsoft 365 plus a newsletter service. The IP addresses are documentation addresses; yours will be real ones.

<policy_published>
  <domain>example.co.uk</domain>
  <p>none</p>
</policy_published>
<record>
  <row>
    <source_ip>198.51.100.24</source_ip>
    <count>412</count>
    <policy_evaluated>
      <disposition>none</disposition>
      <dkim>pass</dkim>
      <spf>pass</spf>
    </policy_evaluated>
  </row>
  <identifiers><header_from>example.co.uk</header_from></identifiers>
  <auth_results>
    <dkim><domain>example.co.uk</domain><selector>selector1</selector><result>pass</result></dkim>
    <spf><domain>example.co.uk</domain><result>pass</result></spf>
  </auth_results>
</record>
<record>
  <row>
    <source_ip>203.0.113.50</source_ip>
    <count>37</count>
    <policy_evaluated>
      <disposition>none</disposition>
      <dkim>fail</dkim>
      <spf>fail</spf>
    </policy_evaluated>
  </row>
  <identifiers><header_from>example.co.uk</header_from></identifiers>
  <auth_results>
    <dkim><domain>mailer.example.net</domain><selector>s1</selector><result>pass</result></dkim>
    <spf><domain>bounces.mailer.example.net</domain><result>pass</result></spf>
  </auth_results>
</record>
<record>
  <row>
    <source_ip>192.0.2.77</source_ip>
    <count>3</count>
    <policy_evaluated>
      <disposition>none</disposition>
      <dkim>fail</dkim>
      <spf>fail</spf>
    </policy_evaluated>
  </row>
  <identifiers><header_from>example.co.uk</header_from></identifiers>
  <auth_results>
    <spf><domain>example.co.uk</domain><result>fail</result></spf>
  </auth_results>
</record>

Read each record in the same order: count, verdict, then the domains.

  1. 198.51.100.24, 412 messages. Both aligned results pass, and both raw results are for example.co.uk. The selector selector1 is the name Microsoft 365 uses for its first DKIM key. This is your own mail working properly. Nothing to do.
  2. 203.0.113.50, 37 messages. The verdict is fail, fail. But look at auth_results: DKIM and SPF both passed, just for mailer.example.net, the newsletter service’s own domain. The mail is genuine; it isn’t aligned with your From address. Once you move to p=quarantine, these 37 newsletters go to junk.
  3. 192.0.2.77, 3 messages. No DKIM signature at all, and SPF failed for your domain because this IP isn’t in your SPF record. Either it’s someone spoofing you, or a device of yours (a scanner, a web server) sending direct. Look up who owns the IP before deciding: a reverse (PTR) lookup often names the host or provider.

In this report 412 of 452 messages passed, a pass rate of 91%. The 37 newsletter messages are the whole gap that matters.

Raw results versus DMARC results

Record two is the most common thing you will find, and the reason so many people misread these reports. The two sets of results answer different questions:

  • auth_results says whether a signature or SPF check was valid for some domain.
  • policy_evaluated says whether a passing check was for your domain (the one in header_from). This is alignment, and it’s the only thing DMARC cares about.

So when a raw result says pass and the verdict says fail, compare the domain in auth_results with header_from. If they differ, the service is authenticating as itself. With the default relaxed alignment, a subdomain of yours counts as a match (mail.example.co.uk aligns with example.co.uk); someone else’s domain never does. If your record sets adkim=s or aspf=s, the domains must match exactly.

DMARC needs only one of the two to pass and align. Mail that passes on DKIM alone is fine. Mail that passes on SPF alone works today, but forwarding breaks SPF, so it’s worth adding aligned DKIM for those senders too.

Decide what to do with each sender

Work down your sending sources, largest count first, and put each in one of four groups.

What you seeWhat it meansWhat to do
Aligned pass on DKIM, SPF or bothYour mail, set up properlyNothing
Raw pass for another domain, verdict failA real service sending as you without alignmentSet up DKIM with your own domain in that service (often called domain authentication or a custom sending domain)
Your own DKIM domain with result failYour signature broke in transit, usually a forwarder or mailing list editing the messageUsually nothing, if it’s a small share of your mail
No DKIM, SPF fail or softfail, unknown IPSpoofing, or a sender you haven’t found yetIdentify the IP. If it’s yours, route it through your mail provider; if not, this is what enforcement blocks

Two other things are worth a glance. A reason element inside policy_evaluated means the receiver didn’t apply your policy as published, with a type such as mailing_list, trusted_forwarder or local_policy. And the disposition shows what was actually done: under p=none it is none even for failing mail, which is exactly why monitoring is safe.

Collect at least two to four weeks of reports before tightening the policy. Monthly invoices, payroll and statements only appear once a month, and a week of reports can miss them completely.

Check your fix worked

Say you’ve added the newsletter service’s DKIM records for example.co.uk. First check the records are published: the email DNS checker shows your SPF and DMARC records and tests DKIM selectors. Then send a test newsletter to a Gmail or Outlook.com address, open the message source and find the Authentication-Results header. You want to see dkim=pass next to your own domain (shown as header.d=example.co.uk or header.i=@example.co.uk, not the service’s domain) and dmarc=pass.

The reports confirm it a day or two later. In the next reports, the record for 203.0.113.50 should show:

<policy_evaluated>
  <disposition>none</disposition>
  <dkim>pass</dkim>
  <spf>fail</spf>
</policy_evaluated>
...
<dkim><domain>example.co.uk</domain><selector>s1</selector><result>pass</result></dkim>

SPF still fails alignment, because the service still uses its own bounce domain, and that’s fine: DKIM carries it. When every sender you recognise passes like this for a couple of weeks, you’re ready for p=quarantine.

Common puzzles

The numbers don’t match what I sent

Each report covers only the mail that one provider received, and mail to providers that don’t send reports never appears. Treat the reports as a large sample, not a complete count.

The same IP appears in several records

A new record starts for each different combination of result and domains, so one server can show up two or three times: once for messages that passed and once for forwarded copies that didn’t, for example.

Reports list subdomains I don’t use

Mail with a From address on a subdomain that has no DMARC record of its own is reported under your main domain, with that subdomain in header_from. If it fails and you don’t send from it, it’s spoofing. The sp and np tags in your DMARC record control how strictly those are treated.

The same report arrived twice

Reporters are meant to reuse the same report_id and file name when they resend, so duplicates are easy to spot. The analyser skips duplicate reports for you; if you are adding up by hand, check the report_id.

Microsoft 365 mail fails with a .onmicrosoft.com DKIM domain

That is the provider’s default signing domain, so it doesn’t align. Turn on DKIM for your own domain; enabling DKIM in Microsoft 365 covers the two CNAME records and the switch in Defender.