Read DMARC Reports: Aggregate XML Explained Field by Field

Updated: 4 min read Editorial team haveibeenhacked.de
Short answer

To read DMARC reports, unzip the XML attachment and look at each record block: source_ip shows who sent, count shows how many messages, and policy_evaluated shows whether DKIM and SPF passed with alignment to your domain. Known IPs that fail point to misconfiguration; unknown IPs that fail are usually spoofing.

The first DMARC reports usually land the day after you publish the record. An email from [email protected], subject “Report domain: example.com”, with a ZIP file named like a hash. Most people open it, see XML and close it again. That’s a shame, because that file lists everyone who sent mail in your name yesterday.

Where the reports come from

The rua=mailto:… tag in your DMARC record asks every receiver to send you aggregate reports. Google, Microsoft, Yahoo and many others do so roughly once a day. Each report summarises which IP addresses delivered mail with your domain in the From header, how many messages, and how authentication turned out. There are no message bodies, subjects or recipient addresses in them.

The format is now defined in RFC 9990, published in May 2026; before that it lived in RFC 7489. The core structure hasn’t changed. If you haven’t published a record yet, start with DMARC setup.

How to read DMARC reports: the structure

Here is a trimmed report after unzipping:

<feedback>
  <report_metadata>
    <org_name>google.com</org_name>
    <email>[email protected]</email>
    <report_id>8822104593315601234</report_id>
    <date_range><begin>1792195200</begin><end>1792281599</end></date_range>
  </report_metadata>
  <policy_published>
    <domain>example.com</domain>
    <adkim>r</adkim><aspf>r</aspf>
    <p>none</p><sp>none</sp>
  </policy_published>
  <record>
    <row>
      <source_ip>198.51.100.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><selector>s2026</selector><result>pass</result></dkim>
      <spf><domain>example.com</domain><result>pass</result></spf>
    </auth_results>
  </record>
</feedback>

The dates are Unix timestamps. On Linux date -u -d @1792195200 turns that into a readable date; on macOS use date -u -r 1792195200. Both print 17 October 2026.

FieldWhat it tells you
org_nameWhich receiver produced the report
policy_publishedThe policy the receiver saw in your DNS
source_ipWhere the messages came from
countHow many messages in the period
policy_evaluated/dkim, /spfDMARC result, including alignment
dispositionWhat the receiver did: none, quarantine, reject
auth_resultsRaw SPF and DKIM results, with the domain each one checked

The distinction people most often miss: auth_results says whether SPF and DKIM passed for any domain at all. policy_evaluated says whether they passed for your domain. A pass in the first and a fail in the second almost always means a service is signing or bouncing with its own domain instead of yours.

Four patterns you will see again and again

Your own server, all green

A known IP, a high count, DKIM and SPF both pass. Exactly what you want.

Known vendor, DKIM not aligned

The IP belongs to your email marketing platform. auth_results shows dkim domain=vendor-mail.net result=pass, but policy_evaluated says fail. The fix is to authenticate your own sending domain in that platform, as described in DKIM setup.

Forwarding

An IP from another mailbox provider, small counts, SPF fail, DKIM pass. A recipient forwards your mail to another inbox. Because DKIM survives, DMARC still passes. Nothing to do.

Unknown IP, everything fails

An address in a network you don’t recognise, often a cheap hosting provider or a residential range in a country where you have no business. SPF fails or refers to some unrelated domain, no DKIM. This is spoofing. Under p=reject the disposition reads reject, which is what you want to see.

Who owns that IP address?

To tell a forgotten vendor from a forger, look up the address:

$ dig +short -x 209.85.220.41
mail-sor-f41.google.com.
$ whois 209.85.220.41 | grep -iE '^(OrgName|netname)'
NetName:        GOOGLE
OrgName:        Google LLC

Reverse DNS often names the service outright. Large senders such as Mailchimp, Amazon SES, SendGrid and Microsoft use recognisable hostnames. No reverse DNS and an IP owned by a budget VPS host rarely means anything legitimate.

Turning a pile of XML into a table

Once reports arrive daily from several receivers, reading them one by one gets old. With the unzipped files in one folder and xmllint installed, this prints a quick overview:

for f in *.xml; do
  xmllint --xpath 'concat(//org_name, ";", //record[1]/row/source_ip, ";", //record[1]/row/count, ";", //record[1]/row/policy_evaluated/dkim, ";", //record[1]/row/policy_evaluated/spf)' "$f"
  echo
done

It only reads the first record in each file, but it is enough to spot the obvious. For anything more, open-source parsers such as parsedmarc can pull reports straight from an IMAP mailbox into a database, and there are commercial reporting services. If you send rua to a vendor’s domain, that domain needs to publish an authorisation record, or receivers will discard the reports.

Failure reports (ruf)

The ruf= tag requests per-message failure reports, which include headers and sometimes content. You will get few of them: Gmail sends none, and many others hold back for privacy reasons. If they do arrive, handle that mailbox as one containing personal data.

When you’re ready to enforce

Read at least two weeks of reports and build a list of every IP with a meaningful count. When you can name the service behind each one, and every legitimate service passes DKIM or SPF with alignment, the domain is ready for quarantine and then reject. What that buys you is covered in prevent email spoofing. An enforced policy is also the entry ticket for a logo in the inbox with BIMI.

It helps to send reports to an address that stays out of your normal inbox. Some people who run several projects register a separate domain just for that. The price list for every extension shows what that costs at betadomain; a .email domain is €29.99 a year. You can look up a name for your reporting domain in a few seconds.

Found your domain? You can check and register it directly at compare domain prices – yearly price from €2.99.

FAQ

How often do DMARC reports arrive?

Most receivers send one aggregate report per day, but only if they saw mail claiming to be from your domain. A low-volume domain may only hear from Google and Microsoft; a busy one gets reports from dozens of providers.

Why am I not getting forensic (ruf) reports?

Many large receivers, Gmail included, do not send them because they contain parts of the original message. In practice aggregate reports are enough to reach an enforced policy.

Do I need a tool to analyse DMARC reports?

For a small domain you can read the XML by hand; the structure is simple. Once you have several senders or many reports a day, a parser or reporting service saves hours. What matters most is that somebody looks regularly.

What does disposition none mean on a failing row?

The receiver delivered the message despite the DMARC failure. That always happens under p=none, and can happen under stricter policies if the receiver applied a local override, for example for known forwarders. The reason element often explains why.