DMARC Aggregate Report Analyzer avatar

DMARC Aggregate Report Analyzer

Pricing

from $8.16 / 1,000 item extracteds

Go to Apify Store
DMARC Aggregate Report Analyzer

DMARC Aggregate Report Analyzer

Parse DMARC aggregate XML into normalized sender, authentication, disposition, policy, and failure records for recurring email-security monitoring.

Pricing

from $8.16 / 1,000 item extracteds

Rating

0.0

(0)

Developer

Automation Lab

Automation Lab

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

1

Monthly active users

6 days ago

Last modified

Categories

Share

Turn DMARC aggregate XML reports into normalized source-IP, message-volume, policy, disposition, DKIM, SPF, and failure records. This DMARC aggregate report analyzer accepts pasted XML, downloadable report files, and base64 attachments, making recurring email-authentication evidence ready for Apify Datasets, spreadsheets, alerts, and security pipelines.

The Actor analyzes aggregate (rua) feedback. It does not collect mailbox messages, change DNS records, or parse forensic (ruf) reports.

What does this Actor do?

DMARC receivers such as Google, Yahoo, and Microsoft send machine-readable XML. Each file may contain many source rows and nested authentication results. The Actor:

  1. loads XML from input, an HTTP(S) URL, or base64;
  2. decompresses GZIP and ZIP attachments when necessary;
  3. validates the XML and required DMARC feedback sections;
  4. emits one stable dataset row per reported source record;
  5. derives a DMARC pass flag and concise failure signals;
  6. optionally combines context-compatible source-IP rows across recurring reports;
  7. writes a message-weighted SUMMARY JSON artifact alongside the dataset.

Bare, default-namespace and prefixed aggregate XML normalize to the same fields.

The original message count is preserved. One output item can represent many emails, so messageCount is the metric to sum for volume analysis.

Who is it for

  • Email administrators investigating unauthorized or misconfigured senders.
  • Security teams tracking reject, quarantine, and authentication-failure volume.
  • Deliverability teams comparing SPF and DKIM alignment over time.
  • Managed service providers processing daily reports for multiple domains.
  • Data engineers feeding normalized DMARC evidence into a warehouse or SIEM.
  • Developers who need a repeatable parser without maintaining XML edge cases.

Use Bulk DMARC Record Checker when you need to inspect the current DNS policy for domains. Use this Actor when you already have aggregate report files and need to understand observed traffic.

What is a DMARC aggregate report?

A DMARC aggregate report is XML feedback from a receiving mail provider. It summarizes messages claiming to be from a domain during a reporting period. Rows contain a sender IP, message count, receiver disposition, aligned DKIM/SPF results, identifiers, and detailed authentication evidence.

Reports do not contain message bodies. They can still reveal infrastructure, domains, addresses, and operational patterns, so store and share them according to your organization's security policy.

How to read a DMARC aggregate report

Start with these fields:

QuestionOutput field
Which domain was evaluated?domain, headerFrom
Which host sent the mail?sourceIp
How much traffic did it send?messageCount
What action did the receiver apply?disposition
Did aligned DKIM pass?dkimEvaluation
Did aligned SPF pass?spfEvaluation
Did DMARC pass through either mechanism?dmarcPass
Why should this row be reviewed?failureReasons
Which policy was published?policyDisposition, policyPercentage

A DMARC pass requires aligned DKIM or aligned SPF. A detailed SPF result may say pass while spfEvaluation says fail if the authenticated envelope domain does not align with the visible From domain.

Input

reports is required and accepts 1–100 objects. Each object requires a name and exactly one source:

  • xml: the complete XML string;
  • url: an HTTP(S) URL returning XML, GZIP, or ZIP;
  • base64: base64-encoded XML, GZIP, or ZIP bytes.
ParameterTypeDefaultPurpose
reportsarrayrequiredNamed report sources to analyze
aggregateBySourceIpbooleanfalseCombine matching source/authentication rows
failOnInvalidbooleantrueFail closed, or skip bad files in a mixed batch
maxItemsinteger10000Limit normalized output rows to 1–100,000

A URL is fetched once with redirects, a 30-second timeout, status validation, and a streamed 10 MiB (10,485,760 bytes) download limit. Inline XML, decoded base64 bytes and unpacked XML each have the same 10 MiB limit. Base64 size is checked before decoding; GZIP/ZIP expansion is bounded during decoding. Only the first XML ZIP member is decompressed; unrelated members are ignored. The Actor does not use a proxy and cannot access private intranet URLs unless they are reachable from the Apify run.

Example input: pasted XML

{
"reports": [
{
"name": "Example daily report",
"xml": "<feedback><report_metadata><report_id>example-1</report_id></report_metadata><policy_published><domain>example.org</domain><p>reject</p></policy_published><record><row><source_ip>192.0.2.1</source_ip><count>90</count><policy_evaluated><disposition>none</disposition><dkim>pass</dkim><spf>fail</spf></policy_evaluated></row><identifiers><header_from>example.org</header_from></identifiers></record><record><row><source_ip>192.0.2.2</source_ip><count>10</count><policy_evaluated><disposition>reject</disposition><dkim>fail</dkim><spf>fail</spf></policy_evaluated></row><identifiers><header_from>example.org</header_from></identifiers></record></feedback>"
}
],
"aggregateBySourceIp": false,
"failOnInvalid": true,
"maxItems": 10000
}

This runnable input saves two source rows: 192.0.2.1 has 90 messages and dmarcPass: true (DKIM alone is sufficient); 192.0.2.2 has 10 messages and dmarcPass: false. Open Message-weighted analysis summary in the Output tab, or fetch https://api.apify.com/v2/key-value-stores/<defaultKeyValueStoreId>/records/SUMMARY. The corresponding overall summary is:

{
"savedRows": 2,
"totalMessages": 100,
"passedMessages": 90,
"failedMessages": 10,
"passRatePercent": 90,
"dispositions": {"none": 90, "reject": 10},
"topSources": [
{"sourceIp": "192.0.2.1", "messageCount": 90},
{"sourceIp": "192.0.2.2", "messageCount": 10}
]
}

For automation, base64 avoids JSON escaping issues. Keep the source label stable so downstream users can identify the originating file.

Example output

{
"reportName": "Google report for sendgrid.net",
"reporter": "google.com",
"reportId": "2026-09-01-google-sendgrid",
"reportIds": ["2026-09-01-google-sendgrid"],
"reportPeriods": [{"reportId":"2026-09-01-google-sendgrid","begin":"2026-09-01T00:00:00.000Z","end":"2026-09-01T23:59:59.000Z"}],
"reportsCount": 1,
"periodBegin": "2026-09-01T00:00:00.000Z",
"periodEnd": "2026-09-01T23:59:59.000Z",
"domain": "sendgrid.net",
"policyDisposition": "reject",
"policyDkimAlignment": "r",
"policySpfAlignment": "r",
"policyPercentage": 100,
"policySubdomainDisposition": "reject",
"sourceIp": "209.85.220.41",
"messageCount": 24,
"disposition": "none",
"dkimEvaluation": "pass",
"spfEvaluation": "pass",
"dmarcPass": true,
"headerFrom": "sendgrid.net",
"envelopeFrom": "sendgrid.net",
"envelopeTo": null,
"dkimResults": [{"domain":"sendgrid.net","result":"pass","selector":"s1"}],
"spfResults": [{"domain":"sendgrid.net","result":"pass","scope":"mfrom"}],
"policyOverrideReasons": [],
"failureReasons": [],
"analyzedAt": "2026-09-02T00:05:00.000Z"
}

Provider-optional values are nullable. Arrays remain arrays even when the XML contains one result, which keeps warehouse and TypeScript contracts stable.

Aggregating recurring reports

Set aggregateBySourceIp to true when scheduled runs ingest multiple report periods. Rows combine only when source IP, header-from, envelope-from, envelope-to, disposition, aligned DKIM/SPF evaluations, published domain, policy/subdomain dispositions, percentage and alignment settings all match. Conflicting policy or identifier context remains in separate rows.

Compatible periods can still combine: periodBegin/periodEnd describe the outer interval, while reportPeriods preserves each distinct report-ID/period combination. Aggregated rows sum messageCount, list unique reportIds, and always set reportId to null, even for a single contributing row. reportsCount counts contributing source rows, not distinct reports. Repeated supplied reports remain repeated traffic; there is no silent deduplication. Schedule one run after new files land, or call the Actor from an email-ingestion or object-storage workflow.

Failure and disposition analysis

failureReasons is designed for filters and alerts:

  • dkim:fail or dkim:missing indicates no aligned DKIM pass;
  • spf:fail or spf:missing indicates no aligned SPF pass;
  • disposition:quarantine or disposition:reject records enforcement;
  • override:<type> preserves a receiver policy override.

Do not classify every listed reason as malicious activity. Forwarding, mailing lists, third-party senders, and temporary configuration changes can produce legitimate failures. Correlate source IP ownership and approved sender inventory before blocking traffic.

How much does it cost to analyze DMARC report source rows?

Pay-per-event billing has a $0.005 Actor start plus one item event for each saved normalized or aggregated source row. Published per-row prices are: FREE $0.01564, BRONZE $0.0136, SILVER $0.010608, and GOLD/PLATINUM/DIAMOND $0.00816. The live price shown in Apify Console is authoritative.

Example BRONZE costs:

Saved rowsEstimated charge
1$0.0186
10$0.1410
25$0.3450
100$1.3650

Invalid reports, skipped files, and XML records beyond maxItems do not produce item events. One item can summarize many messages; billing is not per email. SUMMARY adds no dataset rows or charge events. The start fee is attempted before input validation, including invalid runs. Budget capacity limits saved rows; SUMMARY.truncationReasons includes charge-budget when it reduces output.

Dataset saving and charging are not a distributed transaction. The Actor saves only the budget-affordable rows in bounded chunks, then submits one item charge for that saved count using the Apify client's run-specific idempotency key. A persistence failure prevents item charging. A definitive charge refusal removes the run's dataset (which can then return 404), with up to three verified cleanup attempts. No previously charged prefix is discarded because there is only one item transaction per run. SUMMARY is written only after settlement.

The BILLING key-value-store record contains the dataset ID, event count, idempotency key and recovery state, without report contents. Lost charge responses are reconciled against the platform run count or replayed with the same key and count, at most three charge attempts. If acknowledgement remains unavailable, the run fails with charge-unknown and retains possibly paid output; it does not claim settlement. If storage deletion remains unavailable, it fails with cleanup-required (unpaid rows can remain until cleanup).

Recovery is owner-triggered, not an automatic execution loop. Sanitized failure/timeout/abort telemetry enters our existing maintenance intake; our Developer owns stranded billing reconciliation. After the unavailable service recovers, the authorized run owner or Developer uses Console Resurrect on the original run, build and storage, not Start. The operator API procedure uses a 60-second timeout and disables restart-on-error. The Actor registers no self-resurrection webhook, so failed recovery cannot automatically launch another execution even when webhook mutation is unavailable. Recovery runs before input parsing or any start/item charge. It checks the original BILLING identity and authoritative platform item count: a matching paid count preserves the rows; a confirmed zero removes unpaid output and verifies dataset absence. Unknown, changing or partial counts preserve output and fail for investigation. Delayed recovery never charges or replays the old key: Apify's charge-idempotency keys expire after three minutes. Repeated recovery is safe, including after deletion succeeded but its receipt write failed.

This is eventual compensation, not atomic publication: output can be retrievable between saving and recovery. Maintenance intake, owner action and API/storage availability can delay recovery; unattended reconciliation is not promised. A failed recovery stops and retains the same owned incident with its exact retry condition; only a new authorized owner action re-enters recovery, without new event charges. Private runs use Support's consent/run-owner path. Never replace the key, manually charge, or delete possibly paid output. See the docs/billing-recovery.md.

Reconciliation retains a FAILED analysis status; it does not claim that a missing summary was generated or that failed analysis became complete. A receipt or summary-write failure after acknowledged charging retains the already paid useful rows. The start fee and event prices are unchanged.

Summary scope and completeness

SUMMARY contains overall and domains metrics with savedRows, totalMessages, passedMessages, failedMessages, passRatePercent, dispositions and a top-10 source-IP list. Volume is weighted by messageCount, not row percentages. A zero-message denominator produces a null pass rate. Domain totals reconcile to the overall saved-output totals; aggregation does not count the same saved traffic twice.

Coverage fields are reportsSupplied, reportsAccepted, reportsSkipped, parsedRows (source rows in accepted reports), selectedRows (before budget limiting), savedRows, aggregationEnabled, truncated, truncationReasons and complete. scope is always saved-output-only. complete is false if any report was skipped or output was truncated. Metrics exclude skipped or unsaved traffic; they are not an assertion about all mail received by a domain. A report is committed atomically: no partial rows survive a rejected report. Strict-invalid batches fail before any source rows are saved or item-charged. All-invalid batches fail even when skipping is enabled.

Getting started

  1. Open the Actor input page.
  2. Add a report name and paste XML, a URL, or base64 content.
  3. Keep Fail on an invalid report enabled for strict pipelines.
  4. Enable source-IP aggregation only when combining periods is desired.
  5. Set a practical maxItems safety limit.
  6. Run the Actor and open the DMARC source results dataset view.
  7. Filter dmarcPass = false or non-empty failureReasons for investigation.
  8. Export CSV, JSON, Excel, or connect the dataset to another service.

API usage with cURL

curl -X POST \
"https://api.apify.com/v2/acts/automation-lab~dmarc-aggregate-report-analyzer/runs?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"reports": [{"name":"daily-report","url":"https://your-authorized-host/reports/dmarc.xml.gz"}],
"aggregateBySourceIp": false,
"maxItems": 10000
}'

Poll the returned run or use run-sync-get-dataset-items for small synchronous jobs. Do not put tokens or confidential XML into logs or public Task inputs.

JavaScript API client

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/dmarc-aggregate-report-analyzer').call({
reports: [{ name: 'daily-report', base64: process.env.DMARC_REPORT_BASE64 }],
aggregateBySourceIp: true,
maxItems: 10000,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.filter((row) => !row.dmarcPass));

Python API client

import os
from apify_client import ApifyClient
client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/dmarc-aggregate-report-analyzer').call(run_input={
'reports': [{'name': 'daily-report', 'url': 'https://your-authorized-host/reports/dmarc.xml'}],
'failOnInvalid': True,
'maxItems': 10000,
})
rows = client.dataset(run['defaultDatasetId']).list_items().items
failures = [row for row in rows if not row['dmarcPass']]
print(failures)

MCP

Expose this Actor as a tool in Claude Code:

claude mcp add --transport http apify \
"https://mcp.apify.com?tools=automation-lab/dmarc-aggregate-report-analyzer"

For Claude Desktop, Cursor, and VS Code, add this MCP JSON block to the client's MCP configuration:

{
"mcpServers": {
"apify": {
"url": "https://mcp.apify.com?tools=automation-lab/dmarc-aggregate-report-analyzer"
}
}
}

Example prompts:

  • “Analyze this base64 DMARC aggregate report and list failing source IPs.”
  • “Combine these three authorized report URLs and rank senders by message count.”
  • “Return rejected rows where neither aligned DKIM nor aligned SPF passed.”

Integrations and monitoring patterns

  • Google Sheets or Excel: export source rows for a weekly deliverability review.
  • Make or Zapier: launch a run when an attachment reaches object storage.
  • Webhooks: notify a security workflow after a successful scheduled run.
  • SIEM: ingest rows and alert on new IPs or rising reject counts.
  • Data warehouse: partition by periodEnd, domain, and sourceIp.
  • Apify schedules: rerun after daily aggregate-report collection completes.

The Actor analyzes supplied reports; it does not monitor an inbox or remember prior runs. Retain datasets or compare them downstream for change detection.

Limits and data handling

  • Aggregate DMARC XML is supported; forensic message reports are not.
  • One run accepts at most 100 report inputs.
  • Inline XML, decoded/downloaded bytes and unpacked XML each have a 10 MiB limit.
  • Dataset output rows, including accumulated authentication/provenance, have an 8 MiB safety bound below the platform's 9 MiB item limit; oversized aggregates fail before saving/charging source rows. Use separate runs or disable aggregation when a single combined row would exceed that bound.
  • Core source IPs must be valid IPv4/IPv6; counts must be nonnegative safe integers (zero is valid, missing is not). Supplied dates must be valid nonnegative Unix seconds with begin no later than end. Core policy/evaluation and header-from structures are required; legitimately absent optional fields remain null.
  • ZIP archives must contain at least one .xml member; the first is parsed.
  • DTD/entity declarations and nesting deeper than 64 elements are rejected. No external entities or XML-driven network fetches are permitted.
  • Unknown provider extension fields are ignored.
  • Results select the first maxItems rows/groups globally, in input order. All supplied reports are still validated; later compatible contributions to selected aggregate groups are retained. Later new groups are not selected.
  • The default allocation is 256 MiB, with a 300-second timeout. Very large batches may need a higher run timeout/allocation or smaller batches. Limits are byte/row ceilings, not a guarantee that every maximum-size combination finishes in the default time. Processing uses bounded memory and temporary disk, not a retained cross-report list.
  • The Actor performs no reverse-DNS, ASN, geolocation, or reputation lookup.
  • URL availability and authorization remain the user's responsibility.

Failed operations send sanitized diagnostic input, exceptions and Actor/build/run IDs to our private GlitchTip service for repair; reports are retained for 30 days. Secret fields and URL queries are removed. For this Actor, the entire reports input is excluded: raw XML/base64, report URLs, arbitrary names/comments and recipient identifiers are not included in diagnostic snapshots. Skip warnings contain only bounded report indices and error categories, never report content. Dataset and summary outputs still contain the supplied report evidence; apply normal access controls to those outputs. Temporary parsing/aggregation files are removed when processing completes; interrupted containers discard ephemeral files when destroyed. Inputs, datasets, summaries and platform logs follow your Apify storage settings and retention; delete the run's input/storage through Apify when no longer needed. There is no separate report archive or inbox.

The Actor uses deterministic parsing, not AI. Dependencies are Apify storage and billing, your authorized download host (URL mode only), and private GlitchTip for sanitized failure diagnostics. No external enrichment or paid API key is required. Provider names describe report formats only; this Actor is not endorsed by or affiliated with those mail providers. For help, open an issue on this Actor's Apify Store page; share minimal sanitized examples, not signed report links or confidential report bodies.

Troubleshooting

not-aggregate-feedback or invalid-core-structure means the XML lacks an aggregate feedback root or the required metadata, policy or source structures. Confirm that the file is an aggregate XML report rather than an email body or forensic report.

invalid-report-source means a report object has no source or several source fields. Keep one source and preserve the required name.

A ZIP archive fails when it contains no .xml member. Extract or rebuild the archive, or submit the XML member as base64.

Some reports succeed and one fails. Keep failOnInvalid: true for atomic processing. Set it to false only when skipped-file warnings are acceptable.

SPF details pass while spfEvaluation fails. The detailed domain likely did not align with headerFrom; this is valid DMARC behavior.

Legality and responsible use

Process only reports you are authorized to access. Treat report URLs as secrets when they contain signed query parameters. DMARC data can reveal internal or third-party email infrastructure, sender IPs, domains, and operational volume. Apply suitable access controls, retention limits, and regional privacy rules.

This tool provides technical normalization, not a legal conclusion, abuse verdict, or guarantee that a sender is authorized. Validate findings against business and infrastructure ownership records before enforcement.

FAQ

Does the Actor send or receive email?

No. It only processes report content supplied in Actor input.

Can it parse .xml.gz and .zip attachments?

Yes. Submit the bytes as base64 or provide an accessible HTTP(S) URL. ZIP processing selects the first .xml member.

Does messageCount equal the number of dataset rows?

No. A dataset row represents one DMARC source record. messageCount is the number of messages summarized by that record.

Can it identify unauthorized senders automatically?

It identifies authentication and disposition signals. Authorization depends on your sender inventory, forwarding rules, vendors, and business context.

Why is reportId null in an aggregated row?

Aggregation always makes reportId null, even for one contributing source row. Use reportIds and reportPeriods for provenance; reportsCount counts source rows, not distinct reports.

Can I schedule recurring analysis?

Yes. Use Apify schedules after your report-collection workflow makes new XML or compressed files available. Store prior datasets if you need trend comparison.