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
dkimandspfvalues inpolicy_evaluatedfirst. If either says pass, the row passed DMARC. - Then read
auth_resultsto see which domain each check ran against. A pass there and a fail inpolicy_evaluatedis an alignment problem, not an authentication problem. dispositionis what the receiver did, not whether the mail passed. Atp=noneit 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_infois 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,
1792195200is 2026-10-17 00:00:00 UTC and1792281599is 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
ruaor 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.
| Element | What it is | Format |
|---|---|---|
domain | The policy domain: where the receiver found your DMARC record | Both |
p | Your policy for the domain | Both |
sp | Your policy for subdomains | Both (required in RFC 7489, optional in RFC 9990) |
np | Your policy for subdomains that do not exist | RFC 9990 |
adkim, aspf | DKIM and SPF alignment mode: r (relaxed, the default) or s (strict) | Both |
fo | Failure reporting options | Both |
pct | The percentage of failing mail your policy applied to | RFC 7489 only |
testing | Your t= value; y asks receivers to apply one level below your policy | RFC 9990 |
discovery_method | How 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_ipis the IP address that connected to the receiver, andcountis how many messages it delivered with these results.policy_evaluatedis the DMARC part.dispositionis 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. Itsdkimandspfvalues are the results of the alignment test, so pass means that check passed with a domain aligned to your From domain.reasonappears 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 arelocal_policy,mailing_list,trusted_forwarder,policy_test_mode, andother(Section 3.1.6).
identifiers
header_fromis the domain in the message's From header: the one DMARC protects and the one alignment is measured against.envelope_fromis 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_resultsasks whether the signature verified and whether the sending IP matched the SPF record. For row 2, yes and yes, formailer.exampleandbounces.mailer.example.policy_evaluatedasks whether either check passed with a domain that aligns withheader_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):
| Item | RFC 7489 | RFC 9990 |
|---|---|---|
Namespace on feedback | Schema declares http://dmarc.org/dmarc-xml/0.1; Microsoft’s documented example uses none | urn:ietf:params:xml:ns:dmarc-2.0 |
version | Required | Optional; 1.0 when present |
pct in policy_published | Present | Gone |
np, testing, discovery_method | Not in the schema | Optional |
disposition values | none, quarantine, reject | Adds pass |
Override reasons | forwarded, sampled_out, trusted_forwarder, mailing_list, local_policy, other | local_policy, mailing_list, trusted_forwarder, policy_test_mode, other |
DKIM selector in auth_results | Optional | Required |
SPF scope | helo or mfrom, required | mfrom only, optional |
SPF results per row | At least one | Zero or one |
envelope_from | Required | Optional; may be empty for a null reverse-path |
Extensions | None | A 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_ipwith 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
temperrororpermerroron 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
- 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).
- 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.
- 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).
- 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.
- Microsoft Learn: Troubleshoot email authentication, forwardingMicrosoft 365 trusts ARC seals from major providers such as Google and Yahoo.
- 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.
- 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.