DMARC reporting

How to Read Your First DMARC Aggregate Report

By Adam W., founder of DMARCit · Updated

A DMARC aggregate report is a receiver telling you, about once a day, which IP addresses sent mail with your domain in the From address and how that mail fared. Each row answers one question: this IP sent this many messages, and here is what SPF, DKIM, and DMARC alignment said about them. The part worth slowing down for is that every row carries two sets of results. auth_results holds the raw SPF and DKIM checks. policy_evaluated holds the DMARC verdict, which only counts a pass when the domain that passed also aligns with your From domain. The two disagree on purpose, and the disagreement is usually where the work is.

The short version

  • Read the dkim and spf values in policy_evaluated first. If either says pass, the row passed DMARC.
  • Then read auth_results to see which domain each check ran against. A pass there and a fail in policy_evaluated is an alignment problem, not an authentication problem.
  • disposition is what the receiver did, not whether the mail passed. At p=none it reads none on every row.
  • Sort rows into four piles: your senders that pass, your senders that fail, forwarding, and sources you can't name.
  • Two formats are in circulation, RFC 7489's and RFC 9990's. Most fields are the same.

What actually arrives

Reports go to the address in your record's rua= tag. RFC 9990 has receivers send each one as an email attachment, plain XML or gzip-compressed XML, under a subject that starts with Report Domain: and your domain, then Submitter: and the receiver. The attachment's filename spells out the same facts (RFC 9990, Section 3.5.2):

mail.receiver.example!example.com!1792195200!1792281599.xml.gz

That is the receiver, your policy domain, and the start and end of the reporting period in seconds since 1970, UTC. RFC 9989 says receivers SHOULD send reports at least once every 24 hours (Section 5.3.8), and RFC 9990 says a period typically covers a single UTC day. Each report covers one policy domain, so a receiver that saw mail for example.com and for a subdomain with its own DMARC record sends two reports (Section 3.1.2). The report email itself has to pass DMARC, which is one guard against forged reports.

Not every receiver reports. Microsoft's DMARC guide puts typical coverage at 70 to 90 percent of mail volume (best practices for DMARC reports). It also says Microsoft 365 sends aggregate reports only when the receiving domain's MX record points directly to Microsoft 365, and that it doesn't send failure reports at all (DMARC for inbound mail). Silence from a receiver isn't proof that none of your mail went there.

A sample report

Here is a small report in the RFC 9990 format for example.com, a domain publishing p=none while it finds its senders. The domains are reserved for examples (RFC 2606) and the IP addresses come from the documentation ranges in RFC 5737, so none of it points at a real system. Each of the four rows matches one of the piles this page sorts rows into later.

<?xml version="1.0" encoding="UTF-8"?>
<feedback xmlns="urn:ietf:params:xml:ns:dmarc-2.0">
  <version>1.0</version>
  <report_metadata>
    <org_name>mail.receiver.example</org_name>
    <email>dmarc-reports@mail.receiver.example</email>
    <report_id>1792195200.example.com@mail.receiver.example</report_id>
    <date_range>
      <begin>1792195200</begin>
      <end>1792281599</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>
  <!-- Row 1: your own mail platform, aligned on both -->
  <record>
    <row>
      <source_ip>192.0.2.10</source_ip>
      <count>1180</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>selector1</selector>
        <result>pass</result>
      </dkim>
      <spf>
        <domain>example.com</domain>
        <scope>mfrom</scope>
        <result>pass</result>
      </spf>
    </auth_results>
  </record>
  <!-- Row 2: a newsletter service signing as itself -->
  <record>
    <row>
      <source_ip>198.51.100.25</source_ip>
      <count>340</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</envelope_from>
    </identifiers>
    <auth_results>
      <dkim>
        <domain>mailer.example</domain>
        <selector>s1</selector>
        <result>pass</result>
      </dkim>
      <spf>
        <domain>bounces.mailer.example</domain>
        <scope>mfrom</scope>
        <result>pass</result>
      </spf>
    </auth_results>
  </record>
  <!-- Row 3: your mail, forwarded by a recipient -->
  <record>
    <row>
      <source_ip>203.0.113.7</source_ip>
      <count>12</count>
      <policy_evaluated>
        <disposition>none</disposition>
        <dkim>pass</dkim>
        <spf>fail</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>selector1</selector>
        <result>pass</result>
      </dkim>
      <spf>
        <domain>example.com</domain>
        <scope>mfrom</scope>
        <result>fail</result>
      </spf>
    </auth_results>
  </record>
  <!-- Row 4: a source you can't name -->
  <record>
    <row>
      <source_ip>203.0.113.200</source_ip>
      <count>57</count>
      <policy_evaluated>
        <disposition>none</disposition>
        <dkim>fail</dkim>
        <spf>fail</spf>
      </policy_evaluated>
    </row>
    <identifiers>
      <header_from>example.com</header_from>
      <envelope_from>example.com</envelope_from>
    </identifiers>
    <auth_results>
      <spf>
        <domain>example.com</domain>
        <scope>mfrom</scope>
        <result>fail</result>
      </spf>
    </auth_results>
  </record>
</feedback>

The sections below take it from the top. If you'd rather see your own report laid out this way, the free DMARC report parser reads it in your browser.

report_metadata: who sent it, and for when

  • org_name and email identify the reporting organization and a contact address. extra_contact_info is optional.
  • report_id has to be unique among reports to your domain (Section 3.5.1), and a receiver that re-sends a report has to reuse the original filename (Section 3.5.2). If you tally by hand, count each report_id once.
  • date_range holds begin and end in seconds since the epoch, UTC. In the sample, 1792195200 is 2026-10-17 00:00:00 UTC and 1792281599 is one second before the next midnight. RFC 9990 says these mark the reporting period, not the first and last message seen.
  • generator (RFC 9990 only, optional) names the software that built the report. error (optional) carries problems the receiver hit while reading your DMARC record, such as an invalid rua or more than one record at the same name. Read it whenever it appears.

policy_published: the record the receiver saw

This block echoes your DMARC record as the receiver read it. That is evidence of what the receiver used, which is not always what is in your DNS today. If you change your record mid-period, RFC 9990 says a receiver may report the old policy, a mix of the two, or send a report for each, and it puts the burden on you to expect that during the changeover (Section 3.3). Tags you didn't publish show their default values.

ElementWhat it isFormat
domainThe policy domain: where the receiver found your DMARC recordBoth
pYour policy for the domainBoth
spYour policy for subdomainsBoth (required in RFC 7489, optional in RFC 9990)
npYour policy for subdomains that do not existRFC 9990
adkim, aspfDKIM and SPF alignment mode: r (relaxed, the default) or s (strict)Both
foFailure reporting optionsBoth
pctThe percentage of failing mail your policy applied toRFC 7489 only
testingYour t= value; y asks receivers to apply one level below your policyRFC 9990
discovery_methodHow the receiver found your record: psl (the RFC 7489 method) or treewalk (RFC 9989)RFC 9990

When testing reads y, look at the reason elements in each row next: a receiver honoring your test flag reports policy_test_mode. The RFC 9989 guide explains how t=y and the retired pct= tag interact.

record: one row per source

Each record is one combination of sending IP, identifiers, and results. RFC 9990 promises at least one record per reported IP, so the same IP shows up in several records when its messages came out differently. Each record has three blocks.

row

  • source_ip is the IP address that connected to the receiver, and count is how many messages it delivered with these results.
  • policy_evaluated is the DMARC part. disposition is what the receiver did: none, quarantine, or reject, plus pass in RFC 9990, which the schema defines as no action on mail passing DMARC under an enforcing policy. Its dkim and spf values are the results of the alignment test, so pass means that check passed with a domain aligned to your From domain.
  • reason appears when the receiver applied something other than your published policy. RFC 9990 requires one when alignment fails and the applied policy differs from yours. Its types are local_policy, mailing_list, trusted_forwarder, policy_test_mode, and other (Section 3.1.6).

identifiers

  • header_from is the domain in the message's From header: the one DMARC protects and the one alignment is measured against.
  • envelope_from is the domain SPF was checked against (the RFC 5321 MAIL FROM, often called the Return-Path). It can be empty for bounces, which are sent with a null reverse-path.
  • envelope_to, when present, is the recipient's domain.

auth_results

RFC 9990 describes this block as DKIM and SPF results “uninterpreted with respect to DMARC” (Section 3.1.1.11). Each dkim entry gives the signing domain (the signature's d=), the selector (s=), and the verification result. The spf entry gives the domain checked, the scope (always mfrom in RFC 9990), and the result. A message with several DKIM signatures can produce several dkim entries; RFC 9990 asks receivers to list aligned passes first and to stop at 100. Row 4 in the sample has no dkim entry at all, because that mail carried no signature.

auth_results versus policy_evaluated

Take row 2 of the sample. auth_results says DKIM passed and SPF passed. policy_evaluated says both failed. Nothing is broken. The two blocks answer different questions.

  • auth_results asks whether the signature verified and whether the sending IP matched the SPF record. For row 2, yes and yes, for mailer.example and bounces.mailer.example.
  • policy_evaluated asks whether either check passed with a domain that aligns with header_from. Relaxed alignment needs a shared Organizational Domain (RFC 9989, Section 3.2.10.1), and mailer.example doesn't share one with example.com. Neither aligns, so DMARC fails.

That is a newsletter service sending as you while authenticating as itself. Microsoft's report guide calls an authentication pass with an alignment fail the classic alignment problem.

Row 3 is the mirror image. SPF failed in auth_results, because the IP that delivered the message isn't in example.com's SPF record. DKIM passed with d=example.com, which aligns, so DMARC passed. Your own DKIM intact and SPF failing from an IP you don't recognize is what forwarding usually looks like: RFC 9989 notes that relayed mail will most likely fail SPF unless the relay rewrites the envelope sender, while DKIM signatures generally stay valid (Section 7.4).

Totals for the sample: 1,192 of 1,589 messages passed DMARC (rows 1 and 3), or 75 percent. All four rows still read disposition none, because the policy is p=none. Counting passes by disposition would call this domain 100 percent healthy.

RFC 7489 reports and RFC 9990 reports

RFC 9990 replaced the reporting part of RFC 7489 in May 2026, and receivers move to it on their own schedules, so expect both formats in the same mailbox for a while. The structure is the same. These are the differences that matter when you read one, taken from the two schemas (RFC 7489, Appendix C and RFC 9990, Appendix A):

ItemRFC 7489RFC 9990
Namespace on feedbackSchema declares http://dmarc.org/dmarc-xml/0.1; Microsoft’s documented example uses noneurn:ietf:params:xml:ns:dmarc-2.0
versionRequiredOptional; 1.0 when present
pct in policy_publishedPresentGone
np, testing, discovery_methodNot in the schemaOptional
disposition valuesnone, quarantine, rejectAdds pass
Override reasonsforwarded, sampled_out, trusted_forwarder, mailing_list, local_policy, otherlocal_policy, mailing_list, trusted_forwarder, policy_test_mode, other
DKIM selector in auth_resultsOptionalRequired
SPF scopehelo or mfrom, requiredmfrom only, optional
SPF results per rowAt least oneZero or one
envelope_fromRequiredOptional; may be empty for a null reverse-path
ExtensionsNoneA namespaced extension element

If you parse reports with your own script, the namespace is the change most likely to bite: a namespace-aware parser looking for an unqualified feedback element can miss everything in an RFC 9990 report. A script that expects pct or envelope_from on every report will trip too.

What to do with each kind of row

RFC 9989 is direct about the order of work: legitimate mail that fails alignment MUST be fixed before you publish an enforcing policy (Section 5.1.6). Sort rows into four piles. Each pile has one job.

1. Your sender, passing (row 1)

Aligned DKIM and aligned SPF. Nothing to fix. Write down which service each of these rows belongs to; the list of passing rows is your inventory of what works.

2. Your sender, failing (row 2)

A service you use, sending as your domain. There are two versions:

  • Authenticated as itself, like row 2. Have the service sign DKIM with your domain, or use a MAIL FROM in your domain so SPF aligns. If it can do neither, give it a subdomain with its own DMARC record. Microsoft's guide walks through the same three options. Prefer DKIM: it survives forwarding, and RFC 9989 requires it for any domain at p=reject.
  • Failing raw SPF and DKIM. The service isn't in your SPF record and isn't signing at all. Set up its DKIM, and add it to SPF if it sends with your domain in MAIL FROM. Watch the ten-lookup limit while you do (SPF PermError).

The sender-by-sender diagnosis is in DMARC alignment failure. If the failing rows come from Microsoft 365 itself, with a DKIM pass for your onmicrosoft.com domain, custom-domain DKIM isn't on yet; the Microsoft 365 DKIM setup guide covers it.

3. Forwarding and mailing lists (row 3)

SPF fails because the connecting IP belongs to the forwarder. DKIM survives a plain forward and breaks when a list adds a footer or rewrites the subject. A reason of mailing_list, trusted_forwarder, or local_policy means the receiver recognized the situation and went easy on it. You can't fix these rows by adding forwarders to your SPF record. What you control is aligned DKIM on everything you send. On the receiving side, ARC lets a receiver trust results from earlier hops, and Microsoft 365 trusts ARC seals from major providers such as Google and Yahoo. RFC 9989 is candid that none of the methods meant to keep mailing lists working without rewriting the From header, ARC included, has become widely used. The longer version is in email forwarding and DMARC.

4. Sources you can't name (row 4)

No DKIM signature, SPF failing, an IP you don't recognize, sending as your domain. There are two explanations: a service someone in your organization signed up for and never mentioned, or someone spoofing you. The identifiers help. An envelope_from or DKIM domain that belongs to a known service points to the first. So does an IP whose owner (from reverse DNS or a WHOIS lookup on the address) is a mail provider you recognize. If you can't attribute it, don't authorize it. Microsoft's report guide reads a high-volume unknown source failing everything as a potential spoofing campaign. At p=none nothing is stopping it yet; once your own senders align, this is the mail an enforcing policy stops.

Patterns worth watching across days

  • A new source_ip with real volume for a domain you thought you knew. New senders show up here whether or not anyone told you about them.
  • A sender that aligned last week and doesn't now. Look for a DNS change, a key rotation, or a change on the sending service's side.
  • DKIM temperror or permerror on your own mail, which points at the DNS records for your selectors.
  • Counts that don't match what you sent. Reports only cover receivers that report, and RFC 9990 warns that report data can be forged (Section 8.2), so treat one strange report with care before acting on it.

Reading them by hand, and when to stop

One report from one receiver takes a few minutes to read. A domain gets one per reporting receiver per day, so the pile grows quickly, and both RFCs say as much: RFC 9989 recommends machine parsing (Section 5.1.5), and RFC 9990 notes the XML isn't human readable in bulk.

The DMARC report parser reads one report in your browser and groups its rows by sender, with a next action for each. If you want report history kept and a per-sender view across every report, that is what DMARC monitoring is for; plans are on the pricing page.

Aggregate report FAQ

What is the difference between auth_results and policy_evaluated in a DMARC report?

auth_results holds the raw SPF and DKIM results and the domains they were checked against, without regard to DMARC. policy_evaluated holds the DMARC alignment results: its dkim or spf value says pass only when that check passed with a domain aligned to the From domain. A row can show spf=pass in auth_results and spf=fail in policy_evaluated when SPF passed for a domain that is not yours.

Why does every row in my DMARC report say disposition none?

disposition is what the receiver did with the mail, not whether it passed. At p=none you asked receivers to take no action, so passing and failing rows both show none. Count DMARC passes from the dkim and spf values in policy_evaluated instead.

How often do DMARC aggregate reports arrive?

RFC 9989 says receivers SHOULD send them at least once every 24 hours, and RFC 9990 says a reporting period typically covers one UTC day. Not every receiver sends them, so expect gaps.

Do DMARC aggregate reports contain personal data?

RFC 9990 says aggregate reports contain domain-level authentication data about the servers sending as your domain, with no personal information such as individual mail addresses, IP addresses of individuals, or message content. Failure reports, a separate format, carry more detail.

How this guide was checked

RFC 9990, the schema appendix of RFC 7489, the cited sections of RFC 9989, and Microsoft's DMARC setup guide were read on 2026-10-19. The format comparison was built from the two XML schemas themselves, not from summaries of them.

The sample report was validated against the RFC 9990 schema with xmllint. This page's automated test parses the same sample with the parser behind the free report tool, so the totals quoted above (1,192 of 1,589, 75 percent) come from the parser and fail the build if the sample changes.

Sources

  1. RFC 9990: DMARC Aggregate ReportingEvery element and its meaning, the namespace, delivery by email with gzip and the filename format, one policy domain per report, the UTC reporting day, duplicates and report_id, policy changes mid-period, forged reports, privacy of report contents, the schema (Appendix A) and the differences from RFC 7489 (Appendix C).
  2. RFC 7489: DMARC, Appendix C (the original XML schema)The older format: required version, pct, and envelope_from, optional selector, helo scope, and the forwarded and sampled_out reasons.
  3. RFC 9989: DMARCAggregate reports at least every 24 hours (5.3.8), machine parsing (5.1.5), fixing legitimate unaligned mail before enforcement (5.1.6), relaxed alignment (3.2.10.1), and the state of ARC for mailing lists (7.4).
  4. Microsoft Learn: Set up DMARC to validate the From address domainAuthentication pass with alignment fail as the classic alignment problem, the report patterns and fixes, 70 to 90 percent typical coverage, when Microsoft 365 sends aggregate reports, and that it sends no failure reports.
  5. Microsoft Learn: Troubleshoot email authentication, forwardingMicrosoft 365 trusts ARC seals from major providers such as Google and Yahoo.
  6. RFC 5737: IPv4 Address Blocks Reserved for DocumentationThe sample report uses 192.0.2.0/24, 198.51.100.0/24, and 203.0.113.0/24.
  7. RFC 2606: Reserved Top Level DNS NamesThe sample report uses example.com (Section 3) and the .example top-level domain (Section 2).

Read your own report

Drop an aggregate report into the free parser to see its rows grouped by sender. It runs in your browser and the file isn't uploaded. No reports yet? Check that your record has a rua= address first.

Related reading

Fix what the report shows