DMARC Engine
Home/Articles/Understanding DMARC reports: aggregate (RUA) and forensic (RUF)
Articles

Understanding DMARC reports: aggregate (RUA) and forensic (RUF)

A definitive field-by-field guide to DMARC aggregate (RUA) and forensic (RUF) reports: how to read the XML, tell a spoofer from a misconfigured legitimate sender, and turn the data into a safe path to enforcement.

24 June 2026 · DMARC Engine · 13 min read

Understanding DMARC reports: aggregate (RUA) and forensic (RUF)

Publishing a DMARC record is the easy part. The real value of DMARC lies in the reports it sends back to you: a steady stream of data telling you exactly who is sending email using your domain, whether those messages pass authentication, and how receiving mailboxes are treating them. Those reports are the difference between guessing at your email ecosystem and seeing it clearly. This guide is the definitive reference to the two report types defined by RFC 7489: aggregate reports (RUA) and forensic reports (RUF). We will read the aggregate XML schema field by field, learn how to distinguish a genuine attacker from a legitimate-but-misconfigured sender, cover what forensic reports actually contain and their privacy implications, and finish with a practical workflow for turning raw report data into a safe journey to p=reject.

The two report types at a glance

DMARC defines two completely separate feedback channels, requested by two different tags in your DNS record.

  • Aggregate reports (RUA) are requested with the rua= tag. They are scheduled, statistical XML documents (usually one per reporting organisation per day) that summarise all the messages a receiver saw claiming to be from your domain. They contain no message content and no personal data: just counts, source IP addresses, and authentication results. This is the workhorse of DMARC and the report you will rely on for 99% of your work.
  • Forensic reports (RUF), sometimes called failure reports, are requested with the ruf= tag. They are sent (in principle) in near real time, one per failing message, and contain a redacted copy of the individual email that failed DMARC. They are governed by a separate format, AFRF (RFC 6591). In practice almost no major receiver sends them any more, for the privacy reasons we discuss below.

A minimal record requesting both might look like this:

v=DMARC1; p=none; rua=mailto:dmarc-rua@example.com; ruf=mailto:dmarc-ruf@example.com; fo=1

Two things to know up front. First, if you ask a third party at a different domain to receive your reports, that third party must publish an authorisation record proving they accept reports for you, otherwise compliant receivers will not send them. For reports.dmarcengine.com to receive reports for example.com, a TXT record is needed at example.com._report._dmarc.reports.dmarcengine.com with the value v=DMARC1;. Hosted DMARC sets this up automatically. Second, the fo= tag controls when forensic reports are generated: fo=0 (default) only when both SPF and DKIM fail to align, fo=1 when either fails to produce an aligned pass, fo=d on any DKIM failure, and fo=s on any SPF failure. fo=1 is the most useful setting.

Reading the aggregate (RUA) report field by field

Aggregate reports arrive as a gzip-compressed XML file (.xml.gz or .zip) attached to an email, with a filename of the form receiver.com!example.com!1718064000!1718150400.xml.gz, that is reporter!yourdomain!startUnixTime!endUnixTime. Once decompressed, every report follows the same schema. Here is a representative, abbreviated example, which we will dissect section by section:

<feedback>
  <report_metadata>
    <org_name>google.com</org_name>
    <email>noreply-dmarc-support@google.com</email>
    <report_id>14829348572938</report_id>
    <date_range>
      <begin>1718064000</begin>
      <end>1718150400</end>
    </date_range>
  </report_metadata>
  <policy_published>
    <domain>example.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>1287</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>s1</selector>
        <result>pass</result>
      </dkim>
      <spf>
        <domain>example.com</domain>
        <result>pass</result>
      </spf>
    </auth_results>
  </record>
</feedback>

The report_metadata block

This identifies who sent the report and the time window it covers.

  • org_name and email: the receiving organisation (Google, Microsoft, Yahoo, Mimecast, and so on). Useful for sanity-checking volume by mailbox provider.
  • report_id: a unique identifier for the report, handy when deduplicating.
  • date_range: begin and end as Unix timestamps. Crucially, this is the window in which the messages were seen, not when the report was sent. Reports typically arrive a day or two after the activity. Always group your analysis by this window, not by receipt date.

The policy_published block

This is the receiver echoing back the DMARC policy it found at the time, which lets you confirm your record is being read as intended.

  • domain: the domain whose policy was evaluated.
  • adkim and aspf: the alignment mode for DKIM and SPF respectively. r means relaxed (the organisational domain must match, so mail.example.com aligns with example.com), s means strict (the exact domain must match). Relaxed is the default and right for most organisations.
  • p: the policy at the organisational domain (none, quarantine or reject).
  • sp: the subdomain policy, if set. If absent, subdomains inherit p.
  • pct: the percentage of messages the policy was applied to during a phased rollout.

If policy_published does not match what you intended (for example it shows p=none when you believe you published quarantine), you have a DNS propagation or syntax problem; verify with the DMARC checker.

The record block: where the truth lives

Each <record> is one row of aggregated data: a unique combination of source IP and authentication results, with a count. A single report can contain dozens of records. This is the part you actually analyse.

The row and policy_evaluated:

  • source_ip: the IP address that connected to the receiver to deliver the mail. This is your single most important pivot for identifying senders.
  • count: how many messages matched this exact row in the window. Volume matters: one message from an unknown IP is noise; fifty thousand is a signal.
  • policy_evaluated: the receiver's verdict after applying your policy and any local overrides. Its dkim and spf children report the aligned DMARC result (pass/fail), and disposition is what the receiver actually did: none, quarantine or reject. Watch for a <reason> child here; receivers sometimes override your policy (for example forwarded, trusted_forwarder, mailing_list, local_policy, or sampled_out due to pct), and this is where they tell you.

The identifiers:

  • header_from: the domain in the visible From header. DMARC alignment is always evaluated against this identifier, so it is the anchor of the whole assessment.

The auth_results, the raw mechanics:

This block reports the underlying SPF and DKIM results before alignment is collapsed into the policy_evaluated verdict. This distinction is the key to diagnosis.

  • dkim: for each signature, the domain it signed for (RFC 6376 d=), the selector, and the result (pass, fail, none, etc.). A message can carry several DKIM signatures; you may see multiple <dkim> entries.
  • spf: the domain checked (this is typically the MAIL FROM/return-path domain, per RFC 7208, not the From header) and the result.

The art of report reading is comparing auth_results to policy_evaluated. A message can have spf=pass in auth_results yet spf=fail in policy_evaluated, which means SPF authenticated successfully but for the wrong domain, so it did not align. That single pattern explains a large share of confusing reports.

Spoofer or misconfigured friend? A diagnostic framework

When you see DMARC failures, every source falls into one of four buckets. Your job is to sort them correctly, because the response is completely different: legitimate sources get fixed, malicious ones get blocked. Work through each failing source_ip with these questions.

1. Is the IP one I recognise?

Pivot on source_ip. Run a reverse-DNS / PTR lookup (PTR checker) and a WHOIS on the IP block. If it resolves to a sending platform you use (.sendgrid.net, .outbound.protection.outlook.com, *.amazonses.com, your own MX, a CRM or invoicing tool), it is almost certainly a legitimate but unconfigured source. If it resolves to a residential ISP in a country you do not operate in, a bulletproof host, or a random VPS provider, treat it as suspicious.

2. Does SPF or DKIM pass at all, just not aligned?

Look at auth_results.

  • SPF passes but for a different domain (e.g. SPF domain is sendgrid.net, not yours): this is a legitimate sender using a shared platform without a custom return-path. The fix is to configure that platform to use a subdomain of yours as the MAIL FROM (custom bounce domain), which brings SPF into alignment. No attacker can do this; they cannot make a third-party platform send as your aligned domain.
  • DKIM passes but the d= is the platform's domain, not yours: same diagnosis. Configure custom DKIM signing on the platform so it signs with d=example.com and publishes the selector in your DNS. The DKIM checker confirms the published key.
  • Both SPF and DKIM are fail or none, and the IP is unknown: this is the strongest spoofing signal. A real attacker has no DKIM private key for your domain and cannot pass aligned SPF, so they will show dkim=none/fail and spf=fail together, typically from IPs you have never authorised, often with a header_from of exactly your domain.

3. Is the volume and pattern consistent with real business mail?

A legitimate ESP shows steady, business-hours-correlated volume from a stable set of IP ranges, with a recognisable header_from and usually some passing authentication you can repair. A spoofing campaign tends to spike, originate from scattered or geographically odd IPs, and show authentication failing on every axis. Forwarding is the classic false positive: a forwarder relays your mail from its own IP, breaking SPF (the path changed) while DKIM often survives. Look for spf=fail but dkim=pass, frequently with a <reason> of forwarded. That is a friend, not a foe.

4. The decisive question

Ask: could an external attacker reproduce this result? Aligned DKIM requires your private key. Aligned SPF requires sending from infrastructure you authorised. If a source produces an aligned pass, it is, by definition, authorised or has compromised your secrets (rotate keys immediately if you suspect the latter; see DKIM key rotation). If it only ever fails alignment and you do not recognise it, it is spoofing, and enforcement will stop it.

Forensic / failure reports (RUF) and the privacy problem

Forensic reports were designed to give you a sample of the actual failing message (headers and, depending on the receiver, body content) so you could see precisely what a phishing email looked like. They use the AFRF format and arrive one per failing message, in near real time. In theory this is invaluable for incident response.

In practice, two things have happened. First, almost no major mailbox provider sends RUF reports today. Google, Microsoft and Yahoo do not, primarily because a forensic report can contain the personal data of third parties: recipients, subject lines, even body text from messages that may belong to real users. Forwarding such content to a domain owner raises serious privacy and data-protection concerns (think GDPR). Second, because they are sent per message, an active spoofing run can flood your RUF mailbox with thousands of near-identical reports.

Practical guidance:

  • Still publish ruf= if you can, with fo=1, because a minority of receivers (some European and security-focused providers) do honour it, and the occasional report is genuinely useful.
  • Send RUF to a dedicated, monitored mailbox separate from your RUA processing, and expect low or zero volume.
  • Treat any received forensic report as potentially containing personal data. Restrict access, retain only as long as needed, and redact before sharing internally. DMARC senders may already redact local-parts using the rf/redaction conventions, but do not rely on it.
  • Do not depend on RUF for your enforcement decision. Aggregate reports give you everything needed to safely reach p=reject. Forensic reports are a nice-to-have for investigating a specific campaign, not a requirement.

Turning reports into action: a workflow to enforcement

Reports are only useful if they change what you do. Here is the loop that takes you from p=none visibility to p=reject protection without losing legitimate mail. This mirrors the enforcement journey.

  1. Start at p=none and collect. Publish DMARC with p=none and a working rua=. Change nothing else. Let two to four weeks of reports accumulate so you capture monthly senders (payroll, invoicing, marketing bursts).
  2. Build a sender inventory. Aggregate every source_ip and header_from across all reports, grouped by count. The goal is a complete list of every system sending as your domain, and you will almost always discover legitimate senders you forgot about. Manual XML parsing is painful at scale; the DMARC report analyzer ingests and groups reports for you, and hosted DMARC does this continuously.
  3. Classify each source using the four-bucket framework above: authorised-and-aligned, authorised-but-misconfigured, forwarder, or unknown/malicious.
  4. Fix the legitimate, misconfigured sources. For each one, get it to an aligned pass: configure custom DKIM signing (your d=) and a custom return-path for alignment. Add SPF includes carefully, remembering the SPF 10-lookup limit; flattening helps if you are near it. Re-check after each change in the next reporting window.
  5. Move to p=quarantine, ramping with pct. Once your known traffic is overwhelmingly aligned, raise the policy. Use pct= to phase it in (e.g. p=quarantine; pct=25, then 50, then 100), watching policy_evaluated dispositions in the reports for any legitimate source you missed. Note that pct is a sampling mechanism, not a guarantee, but it is the recommended ramp.
  6. Advance to p=reject. When quarantine has run clean and the only failures left are genuinely unknown/spoofed sources, set p=reject. Spoofed mail is now rejected outright. Keep sp= aligned with your subdomain reality.
  7. Keep watching forever. Enforcement is not "set and forget". New SaaS tools, mergers, and rotated keys all show up first in your aggregate reports. Configure alerting on authentication drift so a newly broken legitimate source is caught before it disrupts business mail. This is also where BIMI becomes available: an enforced DMARC policy (quarantine or reject) is a prerequisite for displaying your verified logo.

Common pitfalls when interpreting reports

  • Assuming "fail" means "attack". Most early failures are your own misconfigured legitimate senders and forwarders. Diagnose before you enforce; otherwise you risk blocking your own mail.
  • Confusing the SPF domain with the From domain. auth_results SPF reports the return-path domain. An SPF pass there does not mean DMARC SPF alignment; check the policy_evaluated verdict.
  • Reacting to a single low-count record. One or two messages from an odd IP is usually backscatter, a test, or noise. Weight your attention by count.
  • Ignoring <reason> overrides. A disposition of none under a reject policy is normal when the receiver records a forwarded or local_policy reason; it is not a failure of your record.
  • Waiting for RUF that will never come. Plan your programme around aggregate data; forensic reports are a bonus.
  • Letting reports pile up unread. A rua= address that no human or tool ever processes provides zero protection. The reports must feed a decision loop.

Next steps

Aggregate reports are the lens that turns DMARC from a hopeful DNS entry into measurable email security. Read them by pivoting on source_ip, compare auth_results against policy_evaluated to tell aligned passes from unaligned ones, and use the four-bucket framework to separate misconfigured friends from genuine spoofers. Fix the friends, then ramp confidently to p=reject to block the rest. Forensic reports remain a useful-but-rare supplement that you should handle with care for privacy reasons.

To start: confirm your record and reporting addresses with the DMARC checker, drop a sample report into the DMARC report analyzer to see the parsing in action, and when you are ready to automate ingestion, classification and the safe path to enforcement, explore hosted DMARC from DMARC Engine.

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.