Skip to content
correct.email
Email authentication

How to read DMARC aggregate reports

What a DMARC aggregate (rua) report contains, how to read its XML record by record, and how to turn the data into fixes for your senders and policy.

By Exmoor Software7 min read

DMARC aggregate reports are the feedback loop that makes DMARC usable. Once your DMARC record contains a rua= address, receivers that support reporting send you a summary — usually once a day — of the mail they received with your domain in the From header: which servers sent it, how many messages there were, and whether SPF, DKIM and DMARC passed.

Since May 2026 the report format is defined in RFC 9990, which replaced the reporting part of RFC 7489. This guide explains what a report contains, how to read it and what the patterns you will see actually mean.

How reports arrive

You request reports in your DMARC record:

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

Each participating receiver then emails that address once per reporting period — typically one UTC day — and per DMARC policy domain. The report is an XML file attached to the message, normally gzip-compressed. RFC 9990 fixes the format of the subject and the file name, so reports are easy to filter:

Subject: Report Domain: example.com Submitter: mail.receiver.example Report-ID: <1792627200-example.com@mail.receiver.example>

mail.receiver.example!example.com!1792627200!1792713599.xml.gz

The two numbers in the file name are the start and end of the reporting period in seconds since 1 January 1970 (UTC).

Not every receiver sends reports — RFC 9989 lists resource constraints, local policy and privacy concerns as the usual reasons for opting out. Treat the data as a large sample of your traffic, not a complete count.

Report addresses on another domain

If rua points outside your organizational domain — typically to a reporting service — receivers first check that the destination has agreed to receive reports for you (RFC 9990, section 4). For rua=mailto:dmarc@reports.example.net on example.com, the destination must publish:

example.com._report._dmarc.reports.example.net.  IN  TXT  "v=DMARC1"

A service that accepts reports for any domain can use a wildcard: *._report._dmarc.reports.example.net. Without the record, receivers must ignore the address and the reports never arrive — silently. The DMARC checker tests this for every address in your record.

What is inside a report

Here is a shortened report with two records:

<feedback xmlns="urn:ietf:params:xml:ns:dmarc-2.0">
  <version>1.0</version>
  <report_metadata>
    <org_name>Example Receiver</org_name>
    <email>dmarc-noreply@mail.receiver.example</email>
    <report_id>1792627200-example.com@mail.receiver.example</report_id>
    <date_range>
      <begin>1792627200</begin>
      <end>1792713599</end>
    </date_range>
  </report_metadata>
  <policy_published>
    <domain>example.com</domain>
    <discovery_method>treewalk</discovery_method>
    <p>none</p>
    <sp>none</sp>
    <np>none</np>
    <adkim>r</adkim>
    <aspf>r</aspf>
    <testing>n</testing>
  </policy_published>
  <record>
    <row>
      <source_ip>192.0.2.25</source_ip>
      <count>1840</count>
      <policy_evaluated>
        <disposition>none</disposition>
        <dkim>pass</dkim>
        <spf>pass</spf>
      </policy_evaluated>
    </row>
    <identifiers>
      <header_from>example.com</header_from>
      <envelope_from>example.com</envelope_from>
    </identifiers>
    <auth_results>
      <dkim>
        <domain>example.com</domain>
        <selector>s2026</selector>
        <result>pass</result>
      </dkim>
      <spf>
        <domain>example.com</domain>
        <scope>mfrom</scope>
        <result>pass</result>
      </spf>
    </auth_results>
  </record>
  <record>
    <row>
      <source_ip>198.51.100.40</source_ip>
      <count>312</count>
      <policy_evaluated>
        <disposition>none</disposition>
        <dkim>fail</dkim>
        <spf>fail</spf>
      </policy_evaluated>
    </row>
    <identifiers>
      <header_from>example.com</header_from>
      <envelope_from>bounces.mailer.example.net</envelope_from>
    </identifiers>
    <auth_results>
      <dkim>
        <domain>mailer.example.net</domain>
        <selector>m1</selector>
        <result>pass</result>
      </dkim>
      <spf>
        <domain>bounces.mailer.example.net</domain>
        <scope>mfrom</scope>
        <result>pass</result>
      </spf>
    </auth_results>
  </record>
</feedback>

The file has three parts.

report_metadata — who sent the report and for which period: org_name, a contact email, a unique report_id and the date_range. An optional error element lists problems the receiver found in your DMARC record, such as an invalid rua or two DMARC records at the same name.

policy_published — your DMARC record as the receiver saw it: the policy domain, p, sp, np, the alignment modes and testing mode (t), plus discovery_method: treewalk for the RFC 9989 method or psl for the older Public Suffix List method. Tags you did not set appear with their default values.

record — at least one per sending IP address and combination of results:

ElementWhat it tells you
row/source_ipIP address of the server that connected to the receiver
row/countnumber of messages with exactly this combination
row/policy_evaluatedthe DMARC outcome: disposition and the aligned dkim and spf results
identifiers/header_fromdomain in the From header — may be a subdomain of the policy domain
identifiers/envelope_fromenvelope sender (MAIL FROM) domain that SPF checked
auth_results/dkimeach DKIM signature: domain (d=), selector (s=), result
auth_results/spfSPF domain, scope (always mfrom) and result

The one distinction that matters

Every record contains SPF and DKIM results twice, and they mean different things:

  • auth_results are the raw results, uninterpreted with respect to DMARC: did SPF pass for some domain, did some signature verify.
  • policy_evaluated says whether those results counted for DMARC. It is pass only when the passing domain is aligned with the From domain.

In the second record above, SPF passed for bounces.mailer.example.net and the signature from mailer.example.net verified — yet both policy_evaluated values are fail, because neither domain is example.com. That is a service sending on your behalf without your domain configured, the most common finding in a new deployment. DMARC alignment explained covers the rules in detail.

A message passes DMARC when policy_evaluated/dkim or policy_evaluated/spf is pass.

Disposition and override reasons

disposition is what the receiver did with the messages: none, quarantine or reject, or — new in RFC 9990 — pass for messages that passed DMARC under an enforcing policy. When the receiver applied something other than your published policy, a reason element says why:

reason/typeMeaning
policy_test_modeyour record has t=y, so a policy one level lower was applied
mailing_listthe receiver concluded the message came through a mailing list
trusted_forwarderthe message came through a forwarder the receiver trusts
local_policythe receiver’s own rules took precedence
otheranything else, explained in comment

Reading the records: common patterns

Sort the records by count and work through each sending source. The reverse DNS lookup of source_ip usually tells you whose server it is.

Your own servers, everything passes. Aligned DKIM and SPF both pass. This is what every legitimate source should look like, and it is how you confirm that a change didn’t break anything.

A service you use: raw pass, aligned fail. As in the example, SPF and DKIM pass for the provider’s domains. Enable DKIM signing with your domain in the service (usually CNAME records it gives you) and, if offered, a custom return-path domain such as bounce.example.com. Adding the service’s include: to your SPF record does not help while it uses its own envelope sender.

A forgotten sender. An IP you recognize — an invoicing system, a web shop, a scanner — with SPF fail or softfail and no DKIM signature. Add it to your SPF record, enable DKIM in that system, or route its mail through a service that signs. Monthly senders only appear after a full month, so keep p=none long enough to see them.

Forwarding. source_ip belongs to another mailbox provider or a university, envelope_from is your domain and SPF fails, but DKIM is aligned and passes. DMARC passes thanks to DKIM; there is nothing to fix. If DKIM fails too — a mailing list changed the subject or added a footer — you may see the mailing_list reason. You can’t fix that on your side, which is why DKIM on all your mail matters.

Unknown sources, nothing aligned. Small counts from IPs that aren’t yours, no DKIM signature or one from an unrelated domain, SPF fail. This is usually spoofing — exactly what p=quarantine or p=reject will stop.

SPF permerror or temperror on all your sources. Your SPF record is broken, often by exceeding the 10-lookup limit. Run the SPF checker.

Reports in the older format

Receivers that still implement RFC 7489 send reports built on its schema: a different XML namespace, no np, testing or discovery_method, a pct element, and only none, quarantine and reject as dispositions. Their override reasons include forwarded and sampled_out, which RFC 9990 no longer has. Expect both formats for some time and make sure whatever processes your reports accepts both. The other differences between the specifications are summarized in DMARC RFC 9989: what changed and what to do.

Privacy and safe handling

RFC 9990 states that aggregate reports contain no personal information such as individual mail addresses or message content. They do describe your mail flows, though, and RFC 9989 notes that for a small organization a report can reveal sensitive business patterns. If you send reports to an external processor, check that your internal policies and contracts allow it.

Treat incoming reports as untrusted input:

  • A report is a compressed XML file from the internet. Use a parser that is protected against decompression and XML bombs (RFC 9990, section 8.1).
  • Reports can be forged to influence your decisions (section 8.2). Report messages are themselves required to pass DMARC, so you can discard those that don’t.
  • A receiver that resends a report must reuse the same file name and report_id; de-duplicate on them.

Tools to process reports

Reading XML by hand works for a test domain, not for daily traffic; RFC 9989 recommends machine parsing. The usual options are:

  • A one-off look in the browser. The DMARC report analyzer reads a single report (XML, ZIP or GZIP), groups the records by sending source and does not store the file. With a free account you can keep reports for a verified domain and follow the trend for 90 days.
  • A hosted DMARC reporting service. You point rua at its address (it publishes the authorization record) and get the records grouped by source. Check where it stores the data and for how long.
  • An open-source parser that reads a mailbox, unpacks the attachments and loads the records into a database or a search tool.
  • Your own script. Extract the attachments, parse each record and sum count by source_ip, header_from and the policy_evaluated results.

Whichever you choose, use a dedicated mailbox for rua so that reports don’t flood a person’s inbox.

Next steps

  1. Check that your record requests reports and that external addresses are authorized — DMARC checker.
  2. Open the first reports you receive in the DMARC report analyzer to see which sources fail.
  3. Fix every legitimate source that fails in policy_evaluated — see DMARC alignment explained.
  4. When only spoofing is left among the failures, move to p=quarantine and then p=reject, as described in How to configure DMARC correctly.

References