DMARC Aggregate Report Analyzer
Pricing
from $8.16 / 1,000 item extracteds
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
Maintained by CommunityActor 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:
- loads XML from input, an HTTP(S) URL, or base64;
- decompresses GZIP and ZIP attachments when necessary;
- validates the XML and required DMARC feedback sections;
- emits one stable dataset row per reported source record;
- derives a DMARC pass flag and concise failure signals;
- optionally combines context-compatible source-IP rows across recurring reports;
- writes a message-weighted
SUMMARYJSON 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:
| Question | Output 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.
| Parameter | Type | Default | Purpose |
|---|---|---|---|
reports | array | required | Named report sources to analyze |
aggregateBySourceIp | boolean | false | Combine matching source/authentication rows |
failOnInvalid | boolean | true | Fail closed, or skip bad files in a mixed batch |
maxItems | integer | 10000 | Limit 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:failordkim:missingindicates no aligned DKIM pass;spf:failorspf:missingindicates no aligned SPF pass;disposition:quarantineordisposition:rejectrecords 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 rows | Estimated 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
- Open the Actor input page.
- Add a report name and paste XML, a URL, or base64 content.
- Keep Fail on an invalid report enabled for strict pipelines.
- Enable source-IP aggregation only when combining periods is desired.
- Set a practical
maxItemssafety limit. - Run the Actor and open the DMARC source results dataset view.
- Filter
dmarcPass = falseor non-emptyfailureReasonsfor investigation. - 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 osfrom apify_client import ApifyClientclient = 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().itemsfailures = [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, andsourceIp. - 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
.xmlmember; 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
maxItemsrows/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.
Related Automation Lab Actors
- Bulk DMARC Record Checker — inspect published DMARC DNS records.
- Bulk SPF Record Checker — validate SPF DNS policy across domains.
- Bulk DKIM Record Checker — inspect known DKIM selectors.
- Bulk MX Record Checker — inventory mail exchangers when available.
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.