DMARC Engine
Home/Knowledge base/How do I read a DMARC aggregate report?
Knowledge base

How do I read a DMARC aggregate report?

A DMARC aggregate report is a small XML file that tells you, source by source, exactly how mail claiming to come from your domain authenticated over a single day.

21 June 2026 · 14 min read

A DMARC aggregate report is a small XML file that tells you, source by source, exactly how mail claiming to come from your domain authenticated over a single day. It is the only honest, comprehensive view you will ever get of who sends email as you: your own mail servers, your marketing platform, your helpdesk, your payroll provider, and anyone forging your domain. The catch is that nobody designed these reports to be read by a human. They arrive gzip-compressed, full of nested tags and raw IP addresses, and the one fact you actually want, "is my real mail passing and is anyone spoofing me", is buried under a lot of mechanical detail. This article walks through a real aggregate report from top to bottom: the metadata header, the published policy, each record, the source IP, the SPF and DKIM results, alignment, and the disposition the receiver applied. By the end you will be able to open any aggregate report and know precisely what it is telling you, and what to do next.

If you only ever take one thing from a report, take this: an aggregate report is not a copy of your email. There are no subject lines, no message bodies and no recipient addresses inside it. It is a statistical summary of authentication outcomes, grouped by sending source over a reporting window. That distinction matters because people sometimes refuse to publish a rua= address fearing it leaks content. It does not. It leaks counts.

How a report arrives and what it is wrapped in

You receive aggregate reports because your DMARC record contains a rua= tag, for example rua=mailto:reports@yourdomain.com. Every mailbox provider that receives mail claiming to be from your domain, and that honours DMARC reporting, will send you one file per day summarising what it saw. Google, Microsoft, Yahoo, Comcast, Mail.ru, La Poste and a long tail of regional ISPs all do this on their own schedule.

The file almost always arrives compressed. The most common form is gzip, producing a .xml.gz attachment; some providers send a .zip. Decompress it and you have a single XML document. The filename itself follows a loose convention, usually receiverdomain!yourdomain!starttimestamp!endtimestamp.xml.gz, so you can often read the reporting provider, your domain and the time window straight from the name before you open anything.

If you want to see this decoded without building anything, paste a single file into the DMARC report analyzer; it decompresses the XML and renders it as readable rows. To confirm your own record even has a working reporting address in the first place, run the DMARC checker against your domain and look for a valid rua= tag. A record with no rua= produces no reports at all, which is the single most common reason a domain that "has DMARC" still cannot see anything.

The shape of the XML

Every aggregate report follows the same structure defined by the DMARC specification. There are three things to find at the top, then a repeating body. Here is a trimmed but representative example so you have something concrete to refer to as we go:

<feedback>
  <report_metadata>
    <org_name>google.com</org_name>
    <email>noreply-dmarc-support@google.com</email>
    <report_id>14955129635591931999</report_id>
    <date_range>
      <begin>1718668800</begin>
      <end>1718755200</end>
    </date_range>
  </report_metadata>
  <policy_published>
    <domain>yourdomain.com</domain>
    <adkim>r</adkim>
    <aspf>r</aspf>
    <p>none</p>
    <sp>none</sp>
    <pct>100</pct>
  </policy_published>
  <record>
    <row>
      <source_ip>209.85.220.41</source_ip>
      <count>312</count>
      <policy_evaluated>
        <disposition>none</disposition>
        <dkim>pass</dkim>
        <spf>pass</spf>
      </policy_evaluated>
    </row>
    <identifiers>
      <header_from>yourdomain.com</header_from>
    </identifiers>
    <auth_results>
      <dkim>
        <domain>yourdomain.com</domain>
        <selector>google</selector>
        <result>pass</result>
      </dkim>
      <spf>
        <domain>yourdomain.com</domain>
        <result>pass</result>
      </spf>
    </auth_results>
  </record>
  <!-- more <record> blocks follow -->
</feedback>

Three sections matter: report_metadata tells you who sent the report and when, policy_published tells you what policy that receiver saw on your domain, and one or more record blocks describe the actual mail. We will take them in turn.

Reading the metadata header

The <report_metadata> block answers "where did this come from and what period does it cover".

  • org_name is the reporting organisation. In the example it is google.com, so this report is Google's view of mail you sent that Google received. You will accumulate one of these per provider per day, and the same sending source will appear in several of them, each counting only the mail that particular provider saw.
  • email is the address the provider sends from. You do not reply to it; it is informational.
  • report_id is a unique identifier for this specific report. It matters when you are de-duplicating, because providers occasionally send the same report twice and you do not want to count the mail twice. Two files with the same report_id from the same org_name are the same report.
  • date_range gives begin and end as Unix epoch timestamps in UTC. The example covers 1718668800 to 1718755200, which is a 24-hour window on 18 June 2024. Almost every provider uses a 24-hour window, but they do not align it to the same hour, so do not assume "midnight to midnight" in your timezone. Convert the epoch values before you reason about timing.

The header on its own tells you nothing about your mail's health. Its job is to let you attribute and reconcile: which provider, which day. Hold on to it.

Reading the published policy

The <policy_published> block is quietly one of the most useful parts of the whole report, and most people skip it. It records the DMARC policy that this receiver actually observed on your domain at the time it processed the mail. That is not necessarily the policy you think you published; it is the policy DNS served to that receiver.

  • domain is the domain the policy applies to, your organisational domain.
  • adkim and aspf are the alignment modes for DKIM and SPF respectively. r means relaxed, s means strict. In relaxed mode, a subdomain match is enough (mail from news.yourdomain.com aligns with yourdomain.com). In strict mode, the domains must match exactly. Most domains run relaxed, and you can read the full definitions of strict and relaxed alignment in the glossary.
  • p is your domain policy: none, quarantine or reject. In the example it is none, which means the receiver was instructed to take no action on failures, only report them. This is monitoring mode.
  • sp is the subdomain policy. If absent, subdomains inherit p. If present, as here with none, it governs mail from subdomains explicitly.
  • pct is the percentage of failing mail the policy asked the receiver to act on. 100 is the safe default. A lower value during rollout means only a sample of failing mail is quarantined or rejected.

Why does this matter so much? Because it is your early-warning system for record drift. If you believe you published p=reject but a fresh report shows p=none in policy_published, then either a DNS change has not propagated, or someone overwrote your record, or a registrar flattened it. The report is telling you what the world actually sees, which is the only version that protects you. If the published policy looks wrong, re-check the live record with the DMARC checker immediately.

Reading a record: the heart of the report

Each <record> block describes one distinct combination of sending source and authentication result, with a count attached. A busy domain produces many records per report, because every different combination of IP and outcome gets its own block. Inside a record there are three parts: row, identifiers and auth_results.

The row: source, count and disposition

<row>
  <source_ip>209.85.220.41</source_ip>
  <count>312</count>
  <policy_evaluated>
    <disposition>none</disposition>
    <dkim>pass</dkim>
    <spf>pass</spf>
  </policy_evaluated>
</row>
  • source_ip is the server that actually sent the mail. This is the single most important field for tracing a source. 209.85.220.41 is a Google sending IP, so this is mail sent through Google's infrastructure on your behalf. A raw IP means little at a glance, which is exactly why turning IPs into named services is the bulk of the work later.
  • count is how many messages this exact source-and-result combination accounted for in the window. Here, 312 messages. The count is everything: a source failing authentication on two messages is noise, the same source failing on twelve thousand messages is a problem or an attack.
  • policy_evaluated is what the receiver decided to do, and the DMARC-level results it derived. disposition is the action actually taken: none, quarantine or reject. The dkim and spf values here are the DMARC-aligned results, not the raw authentication results. This is a subtle and frequently misread point, which we will pull apart below.

The identifiers: what the message claimed to be

<identifiers>
  <header_from>yourdomain.com</header_from>
</identifiers>

The header_from is the domain in the visible From address, the one a human reads in their mail client. DMARC is fundamentally about protecting this domain. Everything else, SPF and DKIM, exists to prove that the visible From domain is legitimate. Some reports also include envelope_from and envelope_to, but header_from is the identifier DMARC aligns against.

The auth_results: the raw authentication outcomes

<auth_results>
  <dkim>
    <domain>yourdomain.com</domain>
    <selector>google</selector>
    <result>pass</result>
  </dkim>
  <spf>
    <domain>yourdomain.com</domain>
    <result>pass</result>
  </spf>
</auth_results>

This is the underlying detail that the policy_evaluated results were derived from.

  • DKIM reports the domain that signed (the d= value in the signature), the selector that points to the public key, and the result: pass or fail. The signing domain here is yourdomain.com with selector google, so your Google-sent mail is DKIM-signed under your own domain. You can look up any selector's published key with the DKIM checker.
  • SPF reports the domain that SPF checked and the result: pass, fail, softfail, neutral, none, temperror or permerror. Note that the domain SPF checks is the envelope (Return-Path / Mail From) domain, which is not always the same as the visible From domain. That gap is where alignment comes in. You can inspect a domain's SPF record and its lookup count with the SPF checker.

SPF pass, DKIM pass, and the alignment that decides everything

Here is the trap that catches almost everyone reading their first reports. A message can show SPF: pass in auth_results and still fail DMARC. This is not a bug. It is the whole reason DMARC exists.

SPF authenticates the envelope domain, the address in the SMTP MAIL FROM. That domain is invisible to the recipient. The recipient only ever sees the header_from. A spammer can send a message where the envelope domain is bounces.spammer.example (which has its own valid SPF record, so SPF passes) while the visible From reads ceo@yourdomain.com. SPF passed, but it passed for the wrong domain.

DMARC closes that hole with alignment. For a message to pass DMARC it needs at least one of:

  • SPF alignment: SPF passed and the envelope domain matches the header_from domain (exactly, in strict mode; allowing a subdomain, in relaxed mode).
  • DKIM alignment: DKIM passed and the signing d= domain matches the header_from domain under the same matching rules.

The dkim and spf values inside policy_evaluated are the aligned results. So when you read a record, compare the two layers:

  • If auth_results shows spf: pass for domain yourdomain.com and header_from is yourdomain.com, then SPF is both authenticated and aligned, and policy_evaluated/spf will read pass.
  • If auth_results shows spf: pass for domain mailer.vendor.example but header_from is yourdomain.com, then SPF authenticated but did not align, and policy_evaluated/spf will read fail, even though the raw SPF passed.

A message survives DMARC if either SPF or DKIM is aligned and passing. This is why DKIM is the more durable of the two: DKIM signatures survive forwarding, whereas SPF breaks the moment a message is relayed and the envelope domain changes. When you see legitimate mail failing in reports, forwarding is one of the usual culprits, and the fix is almost always to ensure DKIM is aligned so the message still passes even when SPF cannot.

For a deeper treatment of why a message can fail DMARC while SPF and DKIM individually pass, the alignment vocabulary is laid out term by term in the glossary, and the practical implications for forwarded mail are covered in the product detail for DMARC.

Reading disposition: what the receiver actually did

The disposition inside policy_evaluated is the outcome that affected real recipients.

  • none means the message was delivered normally despite any DMARC failure. This is what you see under p=none. Failures are reported but not acted on.
  • quarantine means the receiver was instructed to treat the message with suspicion, typically routing it to spam or junk.
  • reject means the receiver was told to refuse the message outright, so it never reached the recipient.

Read disposition together with the aligned results. A record showing disposition: none with policy_evaluated/dkim: fail and policy_evaluated/spf: fail, under a published policy of p=none, is a message that would have been blocked had you been at p=reject. That is the single most important pattern to spot during rollout. Every such record is a question: is this a legitimate source I have not yet authenticated, or is it spoofed mail I want to block? You cannot move to enforcement until you can answer that for every failing source.

Turning records into action

Reading one record is easy. The value is in what you do across all of them. Here is the practical workflow, in order.

1. Group every record by source IP and resolve it to a service

A bare IP is meaningless to most people, so the first real task is identification. For each source_ip, work out what it is: your own mail server, Google Workspace or Microsoft 365, a marketing platform like Mailchimp or SendGrid, your CRM, your helpdesk, your invoicing tool. Reverse DNS, the IP ranges published in vendors' SPF records, and known sending infrastructure all help. Anything you cannot map to a service you recognise gets flagged as unknown, and unknown is exactly where your attention belongs. It is either shadow IT you forgot about or someone forging your domain.

2. Separate legitimate sources from impersonators

Now split the unknowns. A source sending a steady, modest volume that resolves to a recognisable cloud service is almost certainly a legitimate sender you forgot to authenticate, for instance an old monitoring box emailing alerts from an unsigned server, or a department that signed up to a SaaS tool without telling anyone. A source sending from residential IP ranges, botnet-style spread across many countries, with a 100 percent fail rate and no DKIM signature at all, is spoofing you. The aggregate report is where both become visible for the first time.

3. Authenticate every legitimate source

For each legitimate source that is failing alignment, fix the authentication. That usually means one of:

  • Adding the sender's include or IP to your SPF record, while watching the ten-lookup limit so you do not break SPF with a permerror.
  • Enabling and aligning DKIM for that sender, so the signing d= domain matches your From domain. This is the more robust fix because it survives forwarding.
  • Moving the sender to a subdomain you control, so its mail aligns cleanly under relaxed alignment.

After each change, watch the next few days of reports. The failing record for that source should turn into a passing one. That feedback loop, change then verify in the next report, is the entire enforcement journey.

4. Only then tighten the policy

When the reports show that every legitimate source is authenticated and aligned, and the only remaining failures are sources you have positively identified as spoofing, you can safely raise the policy. Move from p=none to p=quarantine, watch the reports for a fortnight to confirm nothing legitimate broke, then move to p=reject. Because you read the reports first, the only mail your p=reject policy blocks is the mail you wanted it to block. This staged approach, anchored in report evidence, is what takes a domain to enforcement without an email outage. The full requirements for getting there are summarised on the requirements page.

A worked reading of the example

Return to the example report and read it as a whole. The metadata says this is Google's report for a 24-hour window on 18 June 2024. The published policy says Google observed p=none with relaxed alignment on both SPF and DKIM at pct=100, so this domain is still in monitoring mode. The single record shows 312 messages from 209.85.220.41, a Google IP, with header_from of yourdomain.com. DKIM passed and signed under yourdomain.com with selector google; SPF passed for yourdomain.com. Both aligned. The disposition was none, which under p=none simply means delivered.

In one sentence: this is 312 legitimate, fully authenticated and aligned messages sent through Google Workspace, and there is nothing to fix here. Multiply that reading across every record in every provider's report for a few weeks, and you have a complete map of your sending estate, which is precisely the map you need before you enforce.

Why this is worth automating

Reading one report by hand, as we just did, is genuinely instructive, and you should do it at least once so the format stops being a mystery. Reading thirty a day, every day, reconciling overlapping counts across providers, converting IPs to services and tracking pass rates over time, is not something anyone sustains by hand. That is why most domains that publish DMARC never escape p=none: the record monitors, the reports pile up unread, and the policy never blocks a single spoofed message. The reports are the entire point of DMARC, and they are the part almost everyone skips.

DMARC Engine does this reading continuously. Point your rua= tag at a managed reporting address and every inbound report is decompressed, validated, parsed into rows, de-duplicated against the report_id, reconciled across providers and enriched so each source IP shows up as a named service rather than a number. What you see is the answer, not the XML: a list of every source sending as you, its volume, its pass rate, and whether it is aligned. Unknown and failing sources are surfaced for you to act on, and the platform guides the staged move from p=none to p=reject off that evidence.

If you want to start by understanding your own record and confirming reports will actually flow, run the DMARC checker for the rua= tag and policy, the SPF checker and DKIM checker to see what each source needs to pass, and, for forwarding-heavy mail, the MTA-STS checker and BIMI checker once you reach enforcement. Then paste one real report into the DMARC report analyzer and read it through, line by line, the way we just did. Once the format clicks, the reports become the most useful thing your domain produces, and the path to a spoof-proof domain is simply a matter of acting on what they show you.

Share

See where your domain stands today

Run a free DMARC scan, then let us take you to enforced p=reject with no email outage.