DMARC Aggregate Report Explained: What the XML Actually Tells You

A field-by-field walkthrough of the DMARC aggregate XML. Worked example, what each element tells you, what the report does not tell you, and how to turn it into action.

11 min readLast reviewed 2026-04-23guides
Thomas Johnson

Founder, mxio · Email infrastructure since 2016

What a DMARC Aggregate Report Is

A DMARC aggregate report is an XML summary of authentication results for email sent using your domain, produced by a mailbox provider and delivered to the address in your DMARC record.

DMARC defines two report types. RUA (Reporting URI for Aggregate) is the daily summary covered by this guide: message counts, source IPs, policy observations, and pass/fail results. RUF (Reporting URI for Forensic) is a per-message failure report with redacted headers. Forensic reports are rarely supported anymore — most major providers stopped sending them years ago because of privacy concerns. In practice, "DMARC reports" means aggregate reports.

Aggregate reports arrive roughly once per day per reporting organization. The major senders are Google (Gmail and Google Workspace), Microsoft (Outlook.com and Microsoft 365), Yahoo, Fastmail, and a long tail of regional providers. Low-volume senders may skip a day or not report at all. You do not control the schedule, the format variations, or whether a given provider sends anything.

This guide walks through a real aggregate report element by element, explains what each field tells you, points out what the report deliberately omits, and covers the common pitfalls when you start ingesting them yourself.

Anatomy of the XML — a Worked Example

Here is a sanitized aggregate report representing a single day of traffic for example.com, with three sending sources: a Google Workspace server, a SendGrid relay, and an unknown high-volume source.

<?xml version="1.0" encoding="UTF-8" ?>
<feedback>

  <report_metadata>
    <org_name>google.com</org_name>
    <email>noreply-dmarc-support@google.com</email>
    <report_id>14567891011121314151</report_id>
    <date_range>
      <begin>1745366400</begin>
      <end>1745452800</end>
    </date_range>
  </report_metadata>

  <policy_published>
    <domain>example.com</domain>
    <adkim>r</adkim>
    <aspf>r</aspf>
    <p>quarantine</p>
    <sp>quarantine</sp>
    <pct>100</pct>
  </policy_published>

  <record>
    <row>
      <source_ip>209.85.220.41</source_ip>
      <count>1247</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>google</selector>
        <result>pass</result>
      </dkim>
      <spf>
        <domain>example.com</domain>
        <result>pass</result>
      </spf>
    </auth_results>
  </record>

  <record>
    <row>
      <source_ip>167.89.101.45</source_ip>
      <count>412</count>
      <policy_evaluated>
        <disposition>none</disposition>
        <dkim>fail</dkim>
        <spf>pass</spf>
      </policy_evaluated>
    </row>
    <identifiers>
      <header_from>example.com</header_from>
    </identifiers>
    <auth_results>
      <dkim>
        <domain>sendgrid.net</domain>
        <selector>s1</selector>
        <result>pass</result>
      </dkim>
      <spf>
        <domain>example.com</domain>
        <result>pass</result>
      </spf>
    </auth_results>
  </record>

  <record>
    <row>
      <source_ip>185.234.219.12</source_ip>
      <count>3891</count>
      <policy_evaluated>
        <disposition>quarantine</disposition>
        <dkim>fail</dkim>
        <spf>fail</spf>
      </policy_evaluated>
    </row>
    <identifiers>
      <header_from>example.com</header_from>
    </identifiers>
    <auth_results>
      <dkim>
        <domain>spammer.net</domain>
        <result>fail</result>
      </dkim>
      <spf>
        <domain>spammer.net</domain>
        <result>fail</result>
      </spf>
    </auth_results>
  </record>

</feedback>

<report_metadata>

The top-level envelope identifying who is reporting and for what period.

  • org_name — the reporting organization. Usually google.com, microsoft.com, yahoo.com, or a similar domain.
  • email — the no-reply address the report was sent from. Useful for debugging ingestion, not for correspondence.
  • report_id — unique per reporting org. Used to deduplicate if the same report is delivered twice.
  • date_range — Unix timestamps (seconds since epoch) for the start and end of the reporting window. Most reports cover 24 hours; some providers use slightly longer or shorter windows.

<policy_published>

Your DMARC record as the receiver saw it while processing the messages in this report. If this does not match what is currently in your DNS, you probably made a policy change during the reporting window.

  • domain — the organizational domain the record applies to.
  • adkim / aspf — alignment mode. r is relaxed (subdomains align to the organizational domain). s is strict (exact match required).
  • p — the policy applied to the organizational domain: none, quarantine, or reject.
  • sp — the subdomain policy, if your record specified one. If absent, subdomains inherit p.
  • pct — the percentage of failing messages the policy applies to. 100 is the common case during enforcement; lower values are used during rollout.

<record>

One record per distinct combination of source IP, authentication results, and disposition. A busy domain may produce dozens of records per report.

Inside each <record>:

<row> — the observed traffic for this combination.

  • source_ip — the IP that sent the messages.
  • count — how many messages from this IP matched this exact result combination during the window.
  • policy_evaluated — the receiver's DMARC decision:
    • disposition — the action taken: none, quarantine, or reject. This may differ from your published p if the receiver applied local policy or the pct tag.
    • dkim — DMARC DKIM alignment result: pass or fail.
    • spf — DMARC SPF alignment result: pass or fail.

<identifiers> — the visible From: header domain the receiver used for the DMARC lookup. Usually matches policy_published.domain; if you see a different value, the message set a different From: header than you expected.

<auth_results> — the raw SPF and DKIM results, before alignment is considered.

  • <dkim>domain is the d= tag from the signature, selector is the key selector, result is whether the signature verified.
  • <spf>domain is the MAIL FROM (envelope sender) domain and result is whether SPF passed for that domain.

The important distinction: auth_results shows whether SPF or DKIM passed at all. policy_evaluated shows whether the passing mechanism aligned with the From: header. The second record in the worked example illustrates this — DKIM passes for sendgrid.net in auth_results but fails in policy_evaluated because sendgrid.net does not align with example.com. For a deeper walkthrough of alignment, see Understanding DMARC Aggregate Reports.

What the Report Tells You

Once you can parse the XML, four operationally useful signals come out of it.

Volume by source. The count values inside each <row> tell you how much traffic each IP is producing. Aggregated across a reporting window, you get a volume distribution by source. This is the fastest way to spot a new high-volume sender that should not exist, or a legitimate sender whose traffic has ramped unexpectedly.

Disposition. The disposition field tells you what the receiver actually did with messages at this source/result combination. At p=none, disposition will be none regardless of whether SPF and DKIM aligned — you are in monitoring mode. At p=quarantine or p=reject, disposition reflects real delivery decisions, and the first messages you quarantine or reject show up here.

Raw authentication. The auth_results block tells you whether SPF and DKIM passed for their own domains. If DKIM signs with d=sendgrid.net and that signature verifies, auth_results says pass. This is the mechanical pass/fail, independent of alignment.

Alignment. The policy_evaluated block tells you whether the passing mechanism aligned with the visible From: header. This is the DMARC verdict. A source where SPF passes in auth_results but fails in policy_evaluated is a third-party sender authenticating for its own domain instead of yours — the classic "configured SPF for the service, forgot to align" problem.

Combine those four and you can answer the questions that matter for enforcement: which services are sending as my domain, which of those are aligning, how much volume is unaligned, and what is genuinely unknown traffic that needs investigation before I tighten policy. The Moving from p=none to p=reject guide covers the thresholds that should drive the decision.

What the Report Does NOT Tell You

Aggregate reports are deliberately narrow. They omit a lot of data that people sometimes expect to find there.

  • No message content. No subject lines, no bodies, no headers beyond the From: domain, no attachments.
  • No recipient information. You cannot see who received the mail. The data is aggregated by source IP and result, not by destination.
  • No sender identity. You see the IP that relayed the message, not the user who sent it. An employee sending through Google Workspace appears as a Google IP; the report gives you no way to attribute the message to a specific mailbox.
  • No real-time signal. Reports cover a closed 24-hour window and arrive afterward. Typical latency is 24–72 hours between a message and its appearance in a report.
  • No guarantee of completeness. A provider may omit records below a volume threshold, defer a report during an outage, or not send reports at all for your domain if their internal policy suppresses them.

Aggregate reports are a diagnostic tool for your sending infrastructure. They are not a mail archive, not a forensic log, and not a real-time monitoring feed.

Reading One Manually vs. Automating

You have two practical paths for turning aggregate reports into action.

Manual — upload a single report. If you have a handful of XML files sitting in a mailbox and you want to understand what they contain, use the free DMARC Report Viewer. Drop the XML (or the .xml.gz / .zip wrapper) into the page and you get a parsed breakdown: source IPs resolved to reverse DNS and ESP names where possible, alignment results per source, raw counts, and the policy observed. No account required. This is the right tool for a one-time diagnosis, a spot check, or confirming that a provider is actually sending reports.

Automated — ingest continuously. Once you are past the one-off phase, manually parsing XML every morning becomes untenable. Large providers can produce reports with hundreds of records; a domain with many senders produces dozens of reports per day across Google, Microsoft, Yahoo, and others. The mxio DMARC Reporting service ingests reports as they arrive, normalizes them into a single data model, resolves source IPs to ESP identities using reverse DNS and ASN data, classifies each source into authorized/failing/forwarded/unknown/misconfigured buckets, tracks alignment rates over time, and surfaces anomalies when the pattern changes.

The rua= address you publish is unique to your domain in mxio, so there is no shared inbox and no possibility of one domain's reports landing in another customer's view. The setup flow and what the dashboard actually shows are covered in DMARC Reporting in mxio: Setup, First Data, and What To Do Next.

The decision between the two is about volume and frequency. If you are going to read reports more than once or need to make enforcement decisions based on trends, automate. If you are doing a single diagnostic pass, the free viewer is enough.

Publishing the RUA Address

Before any reports arrive, you need to publish a DMARC record with an rua= tag. A minimal monitoring record looks like this:

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

If you want reports delivered to multiple addresses — your own mailbox plus an automated service — comma-separate them inside the rua= value:

_dmarc.example.com  TXT  "v=DMARC1; p=none; rua=mailto:dmarc@example.com,mailto:rua+abc123@rua.mxdns.io"

When the rua= destination lives on a different domain than the one being reported on, mailbox providers require external report verification. That is a TXT record under _report._dmarc on the receiving domain that authorizes your domain to send reports there. Without it, many providers will skip the report.

The record looks like this:

_report._dmarc.example.com._report._dmarc.rua.mxdns.io  TXT  "v=DMARC1"

mxio publishes that authorization record automatically for customers using DMARC Reporting, so the only thing you publish yourself is the rua= value. If you are using a self-hosted ingestion pipeline or a third-party service that does not handle this for you, you will need to publish the authorization record manually on the receiving domain.

Common Ingestion Pitfalls

Five gotchas show up repeatedly when teams try to build their own report ingestion.

Report size. Aggregate reports from high-volume senders can be large. A report covering a domain sending millions of messages per day may have thousands of records and measure in megabytes of XML. Your parser needs to stream, not buffer the whole file.

Compression format varies. Some providers send .xml.gz (gzip). Others send .zip archives containing one or more XML files. A few send raw .xml inline. Your ingestion pipeline has to handle all three.

Schema variants. RFC 7489 defines the core schema, but Appendix C leaves room for optional extensions. Some providers include a policy_override_reason block inside policy_evaluated. Some include multiple <auth_results><dkim> elements when a message has multiple signatures. Some omit the selector field on DKIM. Your parser should tolerate missing and extra elements without blowing up.

Duplicate reports. Providers occasionally re-send the same report. Deduplicate on report_id + org_name, not just report_id. Different organizations can and do use overlapping identifiers.

DMARC loops. If you publish your own mailbox in rua=, the emails carrying aggregate reports are themselves subject to DMARC on the reporting domain. A misconfigured receiving domain can cause reports to bounce or disappear. Make sure the domain hosting your rua= mailbox is not quarantining mail from the reporting providers.

Next Steps

If you have a handful of reports in a mailbox and want to understand what they contain right now, upload one to the free DMARC Report Viewer and work through the parsed output. No account required.

If you are past that stage and need continuous ingestion, provider identification, alignment tracking, and enforcement readiness guidance, the mxio DMARC Reporting service handles the XML so you do not have to. Setup is publishing one mailto: address in your DMARC record; the rest — external verification, parsing, classification, anomaly detection — is done for you.

Was this article helpful?

Related Articles