Raw DMARC reports API access lets you move beyond a dashboard summary and inspect the evidence a receiving mailbox provider supplied: report metadata, source IPs, SPF and DKIM results, identifier alignment, message counts, and the policy disposition applied. The practical workflow is usually simple—list reports, select an ID, retrieve the report in JSON or XML, validate it, and store it—but the details matter if you need defensible data for security investigations or automated monitoring.

What “raw DMARC report” should mean

Before calling an API, define what you need by raw. DMARC products use that word in different ways, and treating every response as a byte-for-byte original can produce bad archival or parsing decisions.

At the protocol level, DMARC aggregate reports are structured XML documents. A mailbox provider generally sends the report to the address in your rua tag as a compressed attachment, commonly .gz or .zip. The XML contains aggregate observations for a reporting interval; it is not a copy of every email message and normally does not include message bodies.

A DMARC API may expose one of three things:

  1. A normalized JSON representation. The provider has parsed the incoming XML and exposes selected fields as JSON. This is convenient for application code, but it may omit extensions, ordering, comments, original attachment names, or other details from the received artifact.
  2. An XML representation. The API serializes the report as XML, often when the client requests Accept: application/xml. This is normally the best format for interoperable parsing, although it is still worth confirming whether it is the original attachment or a regenerated document.
  3. The original received file or MIME attachment. This is the closest thing to a true raw source artifact. It may be a .xml.gz, .zip, or complete email message. Not every DMARC service offers it through its API.

For routine analysis, normalized JSON or XML is usually enough. For an audit trail, a parser regression test, an incident involving a disputed source IP, or a need to preserve vendor-specific XML extensions, retain the original compressed file if your platform makes it available. Store the provider report ID, retrieval time, response headers, original filename, checksum, and the raw payload together.

The currently published DMARC core standard is RFC 9989, published in May 2026, while RFC 9990 defines DMARC aggregate reporting and the XML report format. These standards replace the older RFC 7489 material that many existing tutorials and implementations still reference. (rfc-editor.org)

The basic raw DMARC reports API workflow

The API route names, authentication headers, pagination rules, and export formats vary by product. However, nearly every DMARC reporting API follows the same operational pattern:

  1. Authenticate with an API key, bearer token, signed request, or service credential.
  2. List reports for a monitored domain and a bounded reporting date range.
  3. Identify the report you want using the API’s internal report ID.
  4. Retrieve that individual report, requesting JSON, XML, or a source download as supported.
  5. Validate the response and preserve the raw data before transforming it.
  6. Parse, aggregate, alert on, or export the fields needed by your workflow.

Start with a bounded report list

Do not begin by requesting every report ever received. DMARC systems may hold years of daily aggregate reports from multiple receivers, and broad queries are more likely to trigger pagination, timeouts, rate limiting, or costly downstream processing.

Use a small date range first—one day or one week—and filter by the policy domain if the API supports it. In a report listing, look for fields similar to these:

  • id or report_id: the API’s identifier used for the detail request.
  • organization_name or organization_domain: the receiver that generated the report.
  • date_range_begin and date_range_end: the UTC reporting window.
  • domain or policy_published.domain: the domain whose DMARC policy was evaluated.
  • created_at or received_at: when your reporting service ingested the report.
  • external_id: the identifier supplied by the reporting organization, when available.

Do not assume that a report’s created_at timestamp is the same as its report interval. A receiver can generate a report after the end of its aggregation window, and your provider can ingest it later still. For trend analysis, group by the report’s date range. For pipeline-lag monitoring, compare the date range end with the ingestion timestamp.

Fetch one report by its ID

Once you have an ID, make a detail request for that specific item. A well-designed API returns either a structured JSON object by default or a representation selected with the HTTP Accept header.

Use the representation the API documents rather than guessing URL suffixes such as .xml or undocumented query parameters. A generic request shape looks like this:

curl --fail-with-body --silent --show-error \
  -H "Authorization: Bearer $DMARC_API_TOKEN" \
  -H "Accept: application/xml" \
  "https://api.your-dmarc-service.example/.../reports/REPORT_ID" \
  -o report.xml

The URL above is deliberately illustrative, not a universal endpoint. Substitute only the documented host, path, authentication method, and report identifier for your service. Never copy an endpoint pattern from one vendor into another vendor’s API client.

If the service supports XML content negotiation, Accept: application/xml is preferable to relying on a JSON object that happens to contain an escaped XML string. If it exposes the original attachment as a file download, use the documented download endpoint and save the received bytes without decompressing or reserializing them first.

Treat the report ID as provider-local

An API report ID is usually an internal database key. It may be numeric, a UUID, or an opaque string. It is not necessarily the same as the report identifier in the XML’s <report_metadata><report_id> element, and it should not be used as a global cross-vendor identifier.

For durable data modeling, retain both values:

  • API report ID: lets you re-fetch the record from the same provider.
  • XML report ID: identifies the receiver’s report payload and is useful for deduplication.
  • Receiver organization and report interval: help distinguish potentially overlapping reports from separate reporting entities.
  • Payload hash: detects identical raw files even if different systems assign different IDs.

A concrete API example when your service supports XML negotiation

Some services provide a report-list endpoint and a separate endpoint for a single report. In the Postmark DMARC API, for example, you first list received reports for a date range, obtain the returned id, and then retrieve the specific report. Its documentation states that changing the Accept header to application/xml returns XML instead of JSON. The service uses an X-Api-Token authentication header, and its documented list request supports from_date, to_date, and limit parameters. (postmarkapp.com)

That model is useful even if you use another DMARC platform:

GET report collection for a domain and date range
             ↓
read API-specific report ID from the response
             ↓
GET individual report by that ID
             ↓
set Accept: application/xml if documented
             ↓
archive response, validate XML, parse fields

Avoid hard-coding a provider’s token header into a shared client library unless that client is explicitly scoped to the provider. One platform may use X-Api-Token; another may require Authorization: Bearer ...; a third may use basic authentication, OAuth, mutual TLS, or a signed request. Put authentication behavior behind configuration and avoid logging credentials, full request headers, or URLs containing secrets.

If you need to build a general-purpose ingestion service, define an adapter interface rather than forcing every provider into one guessed endpoint design. For example:

listReports(domain, startDate, endDate, cursor) -> report references
getReport(reportReference, preferredFormat) -> bytes + content metadata

Each provider adapter can then translate that neutral interface into its supported routes, headers, cursor fields, and output types.

For help implementing a reliable HTTP client, authentication handling, and idempotent retrieval jobs, consult your sending platform’s email API reference and setup guides alongside the documentation for your DMARC reporting service.

Publish DMARC reporting correctly before expecting API data

An API cannot retrieve reports that receivers were never asked to send. DMARC aggregate reporting begins with the DNS TXT record at _dmarc.your-domain.example.

Here is a practical monitoring-mode example:

_dmarc.example.com. IN TXT "v=DMARC1; p=none; rua=mailto:dmarc-reports@example.com; adkim=s; aspf=s; pct=100"

This example means:

  • v=DMARC1 declares the DMARC record version.
  • p=none asks receivers to monitor failures rather than quarantine or reject them based on the domain policy.
  • rua=mailto:dmarc-reports@example.com requests aggregate reports at that address.
  • adkim=s requests strict DKIM identifier alignment.
  • aspf=s requests strict SPF identifier alignment.
  • pct=100 applies the stated policy to 100% of applicable mail.

The rua tag is the key to aggregate reports. It can contain one or more comma-separated mailto: URIs. A practical deployment often uses a dedicated mailbox or a DMARC analysis service rather than a human inbox, because receiving organizations can send many compressed XML attachments and report volumes grow with mail volume and sender diversity.

Use DNS tools to confirm the record actually published:

dig +short TXT _dmarc.example.com

You can also inspect the record with MXToolbox’s DMARC lookup tools. For a sender-side test of visible authentication results, send a controlled message to mail-tester.com. These checks are useful, but remember that a one-message test is not equivalent to receiver aggregate data collected over days across real mailbox providers.

External report destinations need authorization

A frequent reason for absent reports is an external rua destination without the required authorization record. If example.com publishes a DMARC record that sends reports to an address at reports.example.net, the destination domain must authorize receipt.

For this policy-domain and destination-domain combination, publish this TXT record in the DNS zone for example.net:

example.com._report._dmarc.example.net. IN TXT "v=DMARC1"

The left-hand name is important. It consists of the reporting policy domain, followed by ._report._dmarc., followed by the destination domain. It is not published at _dmarc.example.net, and it is not an SPF record.

Verify it with:

dig +short TXT example.com._report._dmarc.example.net

This authorization mechanism protects external report receivers from being nominated as report destinations without consent. If you use a DMARC reporting vendor, follow its exact DNS instructions because it may provide a unique reporting mailbox and an authorization record specific to your domain.

Understand the XML before you automate decisions

A raw aggregate report normally has a top-level <feedback> element with three core areas: metadata about the report, the sender’s published DMARC policy, and one or more <record> entries.

A shortened example looks like this:

<feedback>
  <report_metadata>
    <org_name>Example Receiver</org_name>
    <email>dmarc-noreply@example-receiver.test</email>
    <report_id>receiver-generated-12345</report_id>
    <date_range>
      <begin>1767225600</begin>
      <end>1767312000</end>
    </date_range>
  </report_metadata>
  <policy_published>
    <domain>example.com</domain>
    <adkim>s</adkim>
    <aspf>s</aspf>
    <p>none</p>
    <pct>100</pct>
  </policy_published>
  <record>
    <row>
      <source_ip>192.0.2.44</source_ip>
      <count>37</count>
      <policy_evaluated>
        <disposition>none</disposition>
        <dkim>pass</dkim>
        <spf>fail</spf>
      </policy_evaluated>
    </row>
    <identifiers>
      <header_from>example.com</header_from>
    </identifiers>
    <auth_results>
      <dkim>
        <domain>example.com</domain>
        <result>pass</result>
      </dkim>
      <spf>
        <domain>mailer.example.com</domain>
        <result>fail</result>
      </spf>
    </auth_results>
  </record>
</feedback>

The details people most often misread are these:

count is a message total, not a unique recipient count

A <record> groups messages that shared relevant evaluation characteristics. If count is 37, the receiver observed 37 messages matching that record’s source and authentication outcome during the report window. It does not mean 37 separate customers complained, 37 unique messages were necessarily generated by one application request, or 37 recipients definitely saw the same result.

policy_evaluated is the receiver’s DMARC conclusion

The <row><policy_evaluated> values represent the receiver’s DMARC evaluation for the grouped mail. The dkim and spf values there answer the central question: did an aligned DKIM or SPF identifier pass in a way that satisfies DMARC?

By contrast, <auth_results> shows the underlying authentication results and domains the receiver recorded. A DKIM signature can pass cryptographically while still failing DMARC if its d= domain does not align with the visible RFC 5322 From: domain. Likewise, SPF can pass for a return-path domain that does not align with From:.

disposition is not always the policy you published

The report’s disposition records what the receiver applied to that row. It may be none, quarantine, or reject, but a none result does not automatically mean your DMARC record was ineffective. A receiver can apply local policy, sample a percentage of traffic, override a requested action, or encounter conditions that lead to a different outcome.

Review the report’s <policy_published> section and your live DNS record separately. The former shows what the receiver says it evaluated during that report interval; the latter shows today’s state. They can differ after a DNS change.

Download, decompress, and validate original report files

If your reporting destination receives reports as email attachments, your retrieval procedure is slightly different from an API XML export. Save the attachment exactly as received first. Then inspect it before parsing.

file aggregate-report.xml.gz
sha256sum aggregate-report.xml.gz

For a gzip-compressed XML report:

gunzip -c aggregate-report.xml.gz > aggregate-report.xml
xmllint --noout aggregate-report.xml

For a ZIP attachment, list its contents before extraction:

unzip -l aggregate-report.zip
unzip -p aggregate-report.zip > aggregate-report.xml
xmllint --noout aggregate-report.xml

The -p option writes file content to standard output, which is useful when your automation expects a single XML member. However, do not blindly assume every ZIP contains exactly one safe XML file. In production, defend against zip bombs, extremely large decompressed data, path traversal names, and unexpected file types.

Preserve bytes before transforming data

A dependable pipeline has two layers:

  • Evidence layer: original API response or attachment bytes, headers/content type, retrieval metadata, and a cryptographic hash.
  • Analysis layer: parsed rows in a database, normalized source IP data, enrichment results, classifications, and alert status.

That separation gives you a way to reprocess reports when your parser changes. It also makes it possible to prove whether an apparent parsing issue existed in the original report or was introduced later by your application.

Use a stable object-storage key layout such as:

dmarc-raw/policy-domain=example.com/report-date=2026-08-15/
  receiver=example-receiver/report-id=receiver-generated-12345.xml.gz

Avoid using only a provider-local numeric API ID as the filename. IDs can collide across accounts, environments, or vendors. Include a domain, receiver identifier, date, and a safe representation of the source report ID; add a hash when IDs may contain characters that do not map cleanly to object keys.

Parse reports safely in code

For a small volume, any standards-aware XML parser can process aggregate reports. For production workloads, choose a parser that disables external entities, imposes size limits, records parse errors, and supports incremental processing for large files.

Never parse untrusted XML with external entity resolution enabled. XML External Entity (XXE) behavior can expose files or cause unwanted network requests in poorly configured parsers. Also impose compressed and uncompressed size limits before parsing.

Here is a deliberately small Python example for inspection, not a complete production importer:

import xml.etree.ElementTree as ET
from pathlib import Path

root = ET.parse(Path("aggregate-report.xml")).getroot()
metadata = root.find("report_metadata")

print({
    "organization": metadata.findtext("org_name"),
    "report_id": metadata.findtext("report_id"),
    "begin": metadata.findtext("date_range/begin"),
    "end": metadata.findtext("date_range/end"),
})

for record in root.findall("record"):
    row = record.find("row")
    identifiers = record.find("identifiers")
    print({
        "source_ip": row.findtext("source_ip"),
        "count": int(row.findtext("count", "0")),
        "disposition": row.findtext("policy_evaluated/disposition"),
        "dmarc_dkim": row.findtext("policy_evaluated/dkim"),
        "dmarc_spf": row.findtext("policy_evaluated/spf"),
        "header_from": identifiers.findtext("header_from"),
    })

For real ingestion, use a hardened XML library and capture these additional fields:

  • Receiver organization name and contact fields.
  • External report ID and API report ID.
  • UTC begin/end epoch values and normalized ISO 8601 timestamps.
  • Source IP, message count, applied disposition, and policy override reason where present.
  • header_from domain.
  • All DKIM and SPF authentication-result domains and result values.
  • Published alignment modes, requested policy, subdomain policy, and percentage.
  • Unknown XML extension elements for later inspection.

Keep the parser tolerant of optional elements. DMARC aggregate reporting is extensible, and receivers do not all populate optional sections identically. A missing <auth_results> child should produce a clearly labeled null or absent value, not cause your entire batch to fail.

Use HTTP and SMTP statuses for the right purpose

Raw-report retrieval is an HTTP workflow; sending mail through your transactional provider is an SMTP or email API workflow. The status codes are useful in different ways.

HTTP responses while retrieving reports

Your client should handle common API responses explicitly:

  • 200 OK: the report or report list was returned. Check Content-Type before treating it as XML or JSON.
  • 204 No Content: the request succeeded but there is no response body; this can be normal for certain operations but is not a usable report payload.
  • 400 Bad Request: inspect date parameters, IDs, request encoding, and required headers.
  • 401 Unauthorized: the credential is missing, invalid, expired, or sent using the wrong authentication scheme.
  • 403 Forbidden: the credential is valid but lacks access to that DMARC record or export operation.
  • 404 Not Found: the ID may not exist, may belong to another account, or may have expired under retention rules.
  • 406 Not Acceptable: the API may not support the Accept media type you requested.
  • 429 Too Many Requests: honor Retry-After if present and retry with exponential backoff plus jitter.
  • 500, 502, 503, or 504: treat these as potentially transient, retry idempotent GET requests, and alert if the failure persists.

A 200 OK with HTML in the body is often an authentication portal, proxy error, or an undocumented redirect—not XML. Validate the response Content-Type, expected root element, and payload size before committing it to the raw evidence store.

SMTP responses are delivery-acceptance signals, not DMARC-report API signals

When you test your own sending infrastructure, an SMTP 250 response means the receiving SMTP server accepted the submitted command or message for further processing. It does not mean the message reached the inbox, passed DMARC, or will appear in a specific aggregate report.

Likewise, a transient 421 or 451 commonly indicates a temporary condition, while a 550 is generally a permanent rejection. These codes help diagnose the mail path, but they do not replace aggregate reports. DMARC data answers a different question: how did participating receiving organizations evaluate messages using your visible From: domain over a reporting interval?

Troubleshoot missing, incomplete, or surprising raw reports

Absence of a report is not proof that there was no traffic. DMARC reporting is receiver-generated and aggregate reporting is not a real-time delivery log. Some receiving systems send reports on their own schedules, some may send none, and report timing can vary.

Work through this checklist before blaming the API:

  1. Check live DNS. Confirm _dmarc.example.com exists as a TXT record, starts with v=DMARC1, and has a syntactically valid rua=mailto: destination.
  2. Confirm the policy domain. A report for news.example.com may be evaluated against an organizational-domain policy unless that subdomain has its own DMARC record.
  3. Validate external destination authorization. If rua points outside the policy domain, query the exact policy-domain._report._dmarc.destination-domain TXT record.
  4. Wait for receiver cadence. Aggregate reports are commonly periodic and frequently daily, but no exact schedule is guaranteed across all receivers.
  5. Inspect API filters. A date range may apply to ingestion date rather than report-period date; understand the product’s documented semantics.
  6. Follow pagination. A first page with 100 reports is not proof there are only 100. Persist the API cursor or next-page URL supplied by the service.
  7. Look for duplicates and overlaps. A receiver may resend a report, and different receivers can report the same source IP. Deduplicate with receiver, report ID, policy domain, period, and payload hash—not source IP alone.
  8. Check your own mail architecture. Marketing tools, customer-support desks, cloud applications, forwarding systems, and old SMTP relays can all send mail using your domain.

Why a legitimate sender can fail DMARC

The most common issue is a mismatch between the domain a service authenticates and the domain users see in the From: header. For example, your SaaS application could send From: billing@example.com, while DKIM signs with d=vendor-mail.example and SPF passes for bounce.vendor-mail.example. Both mechanisms may pass on their own terms, yet neither aligns with example.com under strict alignment.

The remediation is usually not “add every vendor IP to SPF.” First determine whether the sender supports custom DKIM signing for your domain and a custom return-path or MAIL FROM domain for SPF alignment. DKIM alignment is commonly more resilient than SPF alone because SPF has lookup limits and forwarding can break SPF evaluation.

Why a suspicious IP does not automatically mean spoofing

Start with the count, alignment results, header_from, and the reporting receiver. Then investigate source ownership using internal inventories and reputable IP or ASN enrichment. A cloud IP can belong to an authorized application, a forgotten vendor, a forwarded message path, or an attacker; the IP by itself is not a conclusion.

Create an allowlist only after you identify the sending system, business owner, authentication method, expected domains, and remediation plan. An IP-only permanent exception tends to age badly as vendors rotate infrastructure.

Automate retrieval without losing control of the data

A robust job normally runs at least daily, but it should fetch a safety overlap rather than assuming exact report arrival times. For example, each run can list reports covering the previous several days, then skip artifacts already stored by a durable deduplication key.

A practical deduplication key is a hash of:

policy domain + receiver organization + XML report ID + report begin + report end + raw payload SHA-256

Use the hash as a final guard, not as your only identifier. A receiver may issue a corrected report using the same report ID but a changed payload, and you may want to preserve both versions with a clear supersession record.

Retry strategy

GET requests for report lists and individual reports are normally safe to retry because they are read operations. Use exponential backoff with jitter, cap the retry count, and log enough metadata to diagnose repeated failures without storing tokens or raw reports in application logs.

For example:

attempt 1: immediate
attempt 2: wait about 2 seconds plus jitter
attempt 3: wait about 4 seconds plus jitter
attempt 4: wait about 8 seconds plus jitter
then: mark for retry queue and alert if the failure persists

Respect explicit rate-limit headers and a Retry-After response when provided. Do not fan out thousands of individual detail requests simultaneously after listing a large backlog; use bounded concurrency and a queue.

Retention and privacy

Aggregate reports are less sensitive than full message copies, but they can still reveal source IP addresses, third-party sender relationships, mail volume, policy settings, and operational contacts. Limit access, encrypt storage, define a retention schedule, and avoid exposing raw files in public dashboards or support tickets.

If you export DMARC data to a security data lake, preserve lineage: document the reporting service, API request time, parser version, transformations, and enrichment sources. This makes alert results explainable when a security, deliverability, or compliance team asks how a conclusion was reached.

Turn raw data into operational decisions

The value of raw reports is not the XML itself. It is the ability to answer specific questions with evidence.

Use your parsed data to build views such as:

  • Authorized aligned mail: sources with aligned DKIM or SPF that pass DMARC and match known systems.
  • Authorized but misaligned mail: known vendors that pass SPF or DKIM but fail DMARC alignment.
  • Unknown mail claiming your domain: sources that fail both aligned mechanisms and do not map to approved infrastructure.
  • Policy impact: counts by receiver disposition as you move from p=none to p=quarantine or p=reject.
  • Configuration regressions: new failing sources or sudden changes after a DNS, provider, or application deployment.

Do not raise your DMARC policy solely because the total fail count looks low. Inspect the failed traffic by source, volume, sender purpose, and business owner. A small but legitimate transactional stream can still include password resets, invoices, account alerts, or other high-value messages.

For a transactional email stack, confirm that every application, API integration, and SMTP relay uses a verified sending domain with aligned authentication. The transport method—REST email API or SMTP relay—does not itself determine DMARC success. The visible From: domain, DKIM signing domain, SPF envelope domain, and actual sending path do.

Conclusion

To get raw DMARC reports from an API, first list reports for a narrow date range, capture the provider-specific report ID, and then retrieve the individual report using the documented format negotiation or source-download method. Request XML when supported, but distinguish a regenerated XML export from the original compressed attachment if evidence preservation matters.

Archive the raw bytes before parsing, validate compressed files and XML safely, retain report metadata for deduplication, and interpret authentication results through the lens of identifier alignment rather than SPF or DKIM pass/fail alone. With that workflow in place, raw aggregate reports become a reliable input for sender inventory, authentication remediation, fraud detection, and safe DMARC policy enforcement.

FAQ

How do I retrieve a raw DMARC report as XML instead of JSON?

Use the individual-report endpoint documented by your DMARC provider and request XML with Accept: application/xml if the API supports content negotiation. Do not assume that every service supports XML or that an XML response is byte-identical to the original inbound attachment.

What ID do I use to fetch a DMARC report?

Use the report ID returned by that provider’s list-reports API. Keep it separate from the <report_metadata><report_id> value inside the XML, because the API ID is usually provider-local while the XML ID is supplied by the reporting organization.

Why are no raw DMARC reports appearing in my API?

Check the _dmarc TXT record, the rua address, external report-destination authorization when the address is outside your domain, API date filters, pagination, and retention limits. Also remember that aggregate reports are receiver-generated and may not arrive immediately or from every receiver.

Is a DMARC aggregate report a copy of every email?

No. Aggregate reports summarize groups of messages by source and authentication outcome over a time interval. They normally include source IP addresses, counts, SPF and DKIM results, alignment evidence, and policy disposition—not message bodies or a full per-message event stream.

Can I use DMARC reports to decide when to set p=reject?

Yes, but only after investigating every meaningful failing source. Confirm legitimate senders are authenticated and aligned, account for forwarding and third-party services, monitor the effect of stricter policy, and retain raw data so you can explain unexpected failures.