# DMARC Aggregate Report Analyzer (`automation-lab/dmarc-aggregate-report-analyzer`) Actor

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

- **URL**: https://apify.com/automation-lab/dmarc-aggregate-report-analyzer.md
- **Developed by:** [Automation Lab](https://apify.com/automation-lab) (community)
- **Categories:** Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $8.16 / 1,000 item extracteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## DMARC Aggregate Report Analyzer

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 matching source-IP rows across recurring reports.

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](https://apify.com/automation-lab/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 10 MB compressed/download limit. Unpacked content is also limited to
10 MB. 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

```json
{
  "reports": [
    {
      "name": "Google daily report",
      "xml": "<feedback>...</feedback>"
    }
  ],
  "aggregateBySourceIp": false,
  "failOnInvalid": true,
  "maxItems": 10000
}
```

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

### Example output

```json
{
  "reportName": "Google report for sendgrid.net",
  "reporter": "google.com",
  "reportId": "2026-09-01-google-sendgrid",
  "reportIds": ["2026-09-01-google-sendgrid"],
  "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 domain, disposition, DKIM
evaluation, and SPF evaluation all match. This avoids hiding a change from pass
to fail or from `none` to `reject`.

Aggregated rows sum `messageCount`, list unique `reportIds`, expand the reporting
period, and set `reportId` to null. `reportsCount` records how many original rows
contributed. 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. The provisional market-led BRONZE
item price is **$0.0136 per row**; higher Apify tiers receive lower item prices.
The final live price shown in Apify Console is authoritative.

Example BRONZE costs at the provisional curve:

| 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.

### 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

```bash
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

```javascript
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

```python
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:

```bash
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:

```json
{
  "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.
- Each downloaded and unpacked report is limited to 10 MB.
- ZIP archives must contain at least one `.xml` member; the first is parsed.
- XML external entities are not resolved or fetched.
- Unknown provider extension fields are ignored.
- Results stop at `maxItems`; reports are parsed in input order.
- The Actor performs no reverse-DNS, ASN, geolocation, or reputation lookup.
- URL availability and authorization remain the user's responsibility.

### Troubleshooting

**“XML is not a DMARC aggregate feedback report”** means the root does not
contain both `report_metadata` and `policy_published`. Confirm that the file is
an aggregate XML report rather than an email body or forensic report.

**“Provide exactly one of xml, url, or base64”** 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](https://apify.com/automation-lab/bulk-dmarc-record-checker) — inspect published DMARC DNS records.
- [Bulk SPF Record Checker](https://apify.com/automation-lab/bulk-spf-record-checker) — validate SPF DNS policy across domains.
- [Bulk DKIM Record Checker](https://apify.com/automation-lab/bulk-dkim-record-checker) — inspect known DKIM selectors.
- [Bulk MX Record Checker](https://apify.com/automation-lab/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?

An aggregate can include several reports. Use `reportIds` and `reportsCount` to
trace the contributing evidence.

#### 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.

# Actor input Schema

## `reports` (type: `array`):

One to 100 reports. Give each report a name and exactly one source: `xml`, `url`, or `base64`. URLs may return XML, GZIP, or ZIP; base64 values may contain XML, GZIP, or ZIP.

## `aggregateBySourceIp` (type: `boolean`):

Combine rows that share source IP, header-from domain, disposition, DKIM evaluation, and SPF evaluation. Message counts are summed across reports.

## `failOnInvalid` (type: `boolean`):

Stop the run if any report cannot be downloaded, unpacked, or parsed. Disable to skip invalid reports while processing valid ones.

## `maxItems` (type: `integer`):

Maximum normalized or aggregated source rows saved to the dataset.

## Actor input object example

```json
{
  "reports": [
    {
      "name": "Google report for sendgrid.net",
      "xml": "<feedback><report_metadata><org_name>google.com</org_name><email>noreply-dmarc-support@google.com</email><report_id>2026-09-01-google-sendgrid</report_id><date_range><begin>1788220800</begin><end>1788307199</end></date_range></report_metadata><policy_published><domain>sendgrid.net</domain><adkim>r</adkim><aspf>r</aspf><p>reject</p><sp>reject</sp><pct>100</pct></policy_published><record><row><source_ip>209.85.220.41</source_ip><count>24</count><policy_evaluated><disposition>none</disposition><dkim>pass</dkim><spf>pass</spf></policy_evaluated></row><identifiers><header_from>sendgrid.net</header_from><envelope_from>sendgrid.net</envelope_from></identifiers><auth_results><dkim><domain>sendgrid.net</domain><selector>s1</selector><result>pass</result></dkim><spf><domain>sendgrid.net</domain><scope>mfrom</scope><result>pass</result></spf></auth_results></record></feedback>"
    }
  ],
  "aggregateBySourceIp": false,
  "failOnInvalid": true,
  "maxItems": 10
}
```

# Actor output Schema

## `overview` (type: `string`):

Default dataset view containing normalized or source-IP-aggregated report rows.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "reports": [
        {
            "name": "Google report for sendgrid.net",
            "xml": "<feedback><report_metadata><org_name>google.com</org_name><email>noreply-dmarc-support@google.com</email><report_id>2026-09-01-google-sendgrid</report_id><date_range><begin>1788220800</begin><end>1788307199</end></date_range></report_metadata><policy_published><domain>sendgrid.net</domain><adkim>r</adkim><aspf>r</aspf><p>reject</p><sp>reject</sp><pct>100</pct></policy_published><record><row><source_ip>209.85.220.41</source_ip><count>24</count><policy_evaluated><disposition>none</disposition><dkim>pass</dkim><spf>pass</spf></policy_evaluated></row><identifiers><header_from>sendgrid.net</header_from><envelope_from>sendgrid.net</envelope_from></identifiers><auth_results><dkim><domain>sendgrid.net</domain><selector>s1</selector><result>pass</result></dkim><spf><domain>sendgrid.net</domain><scope>mfrom</scope><result>pass</result></spf></auth_results></record></feedback>"
        }
    ],
    "aggregateBySourceIp": false,
    "failOnInvalid": true,
    "maxItems": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/dmarc-aggregate-report-analyzer").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "reports": [{
            "name": "Google report for sendgrid.net",
            "xml": "<feedback><report_metadata><org_name>google.com</org_name><email>noreply-dmarc-support@google.com</email><report_id>2026-09-01-google-sendgrid</report_id><date_range><begin>1788220800</begin><end>1788307199</end></date_range></report_metadata><policy_published><domain>sendgrid.net</domain><adkim>r</adkim><aspf>r</aspf><p>reject</p><sp>reject</sp><pct>100</pct></policy_published><record><row><source_ip>209.85.220.41</source_ip><count>24</count><policy_evaluated><disposition>none</disposition><dkim>pass</dkim><spf>pass</spf></policy_evaluated></row><identifiers><header_from>sendgrid.net</header_from><envelope_from>sendgrid.net</envelope_from></identifiers><auth_results><dkim><domain>sendgrid.net</domain><selector>s1</selector><result>pass</result></dkim><spf><domain>sendgrid.net</domain><scope>mfrom</scope><result>pass</result></spf></auth_results></record></feedback>",
        }],
    "aggregateBySourceIp": False,
    "failOnInvalid": True,
    "maxItems": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/dmarc-aggregate-report-analyzer").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "reports": [
    {
      "name": "Google report for sendgrid.net",
      "xml": "<feedback><report_metadata><org_name>google.com</org_name><email>noreply-dmarc-support@google.com</email><report_id>2026-09-01-google-sendgrid</report_id><date_range><begin>1788220800</begin><end>1788307199</end></date_range></report_metadata><policy_published><domain>sendgrid.net</domain><adkim>r</adkim><aspf>r</aspf><p>reject</p><sp>reject</sp><pct>100</pct></policy_published><record><row><source_ip>209.85.220.41</source_ip><count>24</count><policy_evaluated><disposition>none</disposition><dkim>pass</dkim><spf>pass</spf></policy_evaluated></row><identifiers><header_from>sendgrid.net</header_from><envelope_from>sendgrid.net</envelope_from></identifiers><auth_results><dkim><domain>sendgrid.net</domain><selector>s1</selector><result>pass</result></dkim><spf><domain>sendgrid.net</domain><scope>mfrom</scope><result>pass</result></spf></auth_results></record></feedback>"
    }
  ],
  "aggregateBySourceIp": false,
  "failOnInvalid": true,
  "maxItems": 10
}' |
apify call automation-lab/dmarc-aggregate-report-analyzer --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/dmarc-aggregate-report-analyzer"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/AzZmOMMBUh0dQEKRb/builds/NTmo2H83FY2TTOdnD/openapi.json
