# DMARC Checker Bulk - SPF, DKIM & Email Security (`lentic_labs/email-dmarc-security-prospector`) Actor

Bulk DMARC, SPF and DKIM checker for domain lists. Get mail provider (M365, Google Workspace), DMARC policy, SPF lookup count, MTA-STS, BIMI, TLS-RPT, cert expiry, a 0-100 score and a fix list per domain. Passive DNS checks only. Find p=none prospects, check Google/Yahoo bulk-sender compliance.

- **URL**: https://apify.com/lentic_labs/email-dmarc-security-prospector.md
- **Developed by:** [Clear Tenant](https://apify.com/lentic_labs) (community)
- **Categories:** Lead generation, Developer tools, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.50 / 1,000 domains

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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

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

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## DMARC Checker Bulk - SPF, DKIM & Email Security

This **bulk DMARC checker** and **SPF checker** audits whole lists of domains in one run and returns one flat, CSV-ready row per domain. Use it for an **email deliverability audit**, Google/Yahoo bulk-sender compliance checks, or to find spoofable prospects (DMARC `p=none`) for MSP and security outreach.

### What does the bulk DMARC & SPF checker do?

For every domain you get the **mail provider** (Microsoft 365, Google Workspace, Zoho, Proofpoint, Mimecast or other), **SPF** validity and **recursive DNS-lookup count**, the **DMARC policy** (none / quarantine / reject, pct, sp, rua/ruf), **DKIM** selectors found, **MTA-STS**, **TLS-RPT**, **BIMI**, the website's **HTTPS certificate expiry and HSTS**, a **0-100 score**, a **plain-English verdict**, a **fix list** and **flags** such as `p=none on M365` or `SPF over 10 lookups`.

#### Which email security records does it check? (SPF, DMARC, DKIM, MTA-STS, TLS-RPT, BIMI)

| Area | Fields |
|---|---|
| Summary | `score` (0-100, higher = better protected), `grade` (A-F), `prospectPriority` (High/Medium/Low), `verdict`, `flags` + `flagsText`, `fixes` + `fixesText`, `fixCount` |
| Mail provider | `mailProvider`, `mailProviderDetail` (e.g. Barracuda, Cisco, Cloudflare), `mxHosts`, `nullMx`, `spfSendingServices` (M365, Google, SendGrid, Mailchimp, Amazon SES ... seen in SPF includes) |
| SPF | `spfPresent`, `spfValid`, `spfRecordCount`, `spfLookupCount`, `spfOverLookupLimit`, `spfAll` (`-all`, `~all`, `?all`, `+all`), `spfRecord`, `spfErrors` |
| DMARC | `dmarcPolicy` (p), `dmarcSubdomainPolicy` (sp), `dmarcEffectivePolicy`, `dmarcPct`, `dmarcRuaPresent`, `dmarcRufPresent`, `dmarcCoverage` (own record / inherited from the organizational domain / none), `dmarcRecord`, `dmarcErrors` |
| DKIM | `dkimSelectorsFound` among ~30 common selectors (selector1, selector2, google, k1, k2, s1, s2, default, mxvault, dkim, mail, smtp, zoho, mandrill, everlytickey1, ...), `dkimRevokedSelectors`, `dkimWildcard` |
| Transport | `mtaStsTxt`, `mtaStsMode` (read from the policy file over HTTPS), `mtaStsError`, `tlsRpt`, `tlsRptRua`, `bimi`, `bimiLogo`, `bimiVmc` |
| Website | `certValid`, `certDaysLeft`, `certExpiresAt`, `hsts`, `hstsMaxAge`, `hstsIncludeSubdomains`, `hstsPreload`, `httpsStatus` |
| Other | `runbookUrl` (optional link you supply), `checkedAt`, `status` |

#### Is it passive? (no port scans, no SMTP, no personal data)

Yes. The Actor reads only public DNS records, makes at most one HTTPS GET to the website (falling back to `www.` only if the apex is unreachable; the page body is not downloaded), and fetches the public MTA-STS policy file when one is advertised. It never opens SMTP connections, never verifies mailboxes, never scans ports, and doesn't collect names, email addresses or other personal data.

### Use cases

#### MSP and MSSP lead generation: find domains with DMARC p=none

Find prospects with `p=none`, no DMARC or a broken SPF record and lead with a concrete finding. Filter by Microsoft 365 or Google Workspace to match your stack, and sort by `prospectPriority`.

#### Email deliverability audit for cold-email agencies

Pre-check client and sending domains for SPF, DKIM and DMARC before a campaign. Spot SPF records over the 10-lookup limit that silently fail.

#### Google, Yahoo and Microsoft bulk sender requirements check

Check a whole portfolio of brands or customer domains for the authentication records the big mailbox providers now require (see the FAQ below).

#### Vendor and third-party email security risk

Add an email-security posture snapshot to supplier reviews and security questionnaires.

#### Research: how many domains are spoofable

Measure how many domains in a list have no enforced DMARC, with aggregate counts in the `OUTPUT` summary.

### How to bulk check DMARC for a list of domains

1. Paste domains into **Domains**, or paste a CSV column into **Paste domains / CSV**. URLs, `www.` hosts and email addresses are normalized and duplicates removed.
2. Optionally turn on **Generate summary report** and set a **runbook link** to copy into every row.
3. Click **Start**, then download the dataset as CSV, Excel or JSON, or read the `OUTPUT` summary.

The default input contains 10 well-known domains, so you can try it immediately.

### Input example

```json
{ "domains": ["contoso.com", "fabrikam.com", "northwind.example"], "summary": true, "maxConcurrency": 10 }
```

Advanced options: `domainsText`, `maxDomains`, `dnsServers` (default `1.1.1.1`, `8.8.8.8`), `dnsTransport` (auto / classic DNS / DNS-over-HTTPS), `dnsTimeoutMs`, `httpTimeoutMs`, `maxConcurrency` (max 20), `checkDkim`, `extraDkimSelectors`, `fetchMtaStsPolicy`, `checkHttps`, `stripWww`, `runbookUrl`.

### Output example

One dataset row (real run on mit.edu, 2026-10-18, trimmed to the main fields):

```json
{
  "domain": "mit.edu",
  "status": "ok",
  "score": 50,
  "grade": "D",
  "prospectPriority": "High",
  "verdict": "Spoofable: DMARC is monitor-only (p=none) (M365).",
  "mailProvider": "M365",
  "spfPresent": true,
  "spfLookupCount": 10,
  "spfAll": "-all",
  "spfOverLookupLimit": false,
  "dmarcPolicy": "none",
  "dmarcPct": 100,
  "dmarcRuaPresent": true,
  "dmarcCoverage": "own-record",
  "dkimSelectorsFound": "selector2, google, k1, k2, s1, s2",
  "mtaStsMode": null,
  "tlsRpt": false,
  "bimi": false,
  "certDaysLeft": 25,
  "hsts": false,
  "flags": [
    "SPF near lookup limit (10/10)",
    "p=none on M365"
  ],
  "fixes": [
    "Move DMARC from p=none to p=quarantine, then p=reject, once reports show legitimate mail passes.",
    "Add MTA-STS (TXT at _mta-sts plus a policy at https://mta-sts.mit.edu/.well-known/mta-sts.txt).",
    "Add a TLS-RPT record at _smtp._tls to receive SMTP TLS failure reports.",
    "Send a Strict-Transport-Security (HSTS) header on the website."
  ],
  "checkedAt": "2026-10-19T00:52:56.344Z"
}
```

Also written: an `OUTPUT` record in the key-value store (counts by mail provider, DMARC policy distribution, grades, average score, % spoofable, top flags, lowest-scoring domains) and, with `summary` on, a Markdown `REPORT`. Non-existent domains get a `status: "nxdomain"` row and are not charged.

#### Score and fix list explained

| Area | Points |
|---|---|
| SPF: single record (8), `-all`/`~all` (6/4), at most 10 lookups (6); `+all` scores 0 | 20 |
| DMARC: valid (8), reject/quarantine scaled by pct (17/12), rua (5), subdomains enforced (5) | 35 |
| DKIM found on a common selector | 10 |
| MTA-STS enforce/testing (10/5), TLS-RPT (5) | 15 |
| BIMI | 5 |
| Website: valid certificate (5), not expiring within 14 days (2), HSTS (8) | 15 |

Domains with a null MX (no inbound mail) are not penalized for DKIM or MTA-STS. `prospectPriority` is **High** when the domain is spoofable (no enforced DMARC), uses `+all`, or exceeds 10 SPF lookups. Each finding adds a plain-English fix to `fixes`.

### How much does it cost to check 1,000 domains?

Pay per event: **from $0.004 per audited domain** (cheaper on paid Apify plans) and an optional **$0.01** per run for the summary report. No subscription and no monthly fee.

| Apify plan (Store discount tier) | Per domain | 1,000 domains | 10,000 domains |
|---|---|---|---|
| Free | $0.004 | $4.00 | $40.00 |
| Starter | $0.0035 | $3.50 | $35.00 |
| Scale | $0.003 | $3.00 | $30.00 |
| Business | $0.0025 | $2.50 | $25.00 |

Tiers follow Apify's Store discount tier for your plan. Enterprise pricing is available on request.

Duplicates, invalid input and non-existent (NXDOMAIN) domains are free. Set a maximum total charge per run to cap spend; the Actor stops cleanly when it's reached and keeps everything audited so far.

#### Can I try it for free?

Yes. The Apify free plan includes $5 of usage credit every month with no credit card, enough to audit up to about **1,250 domains a month** (each optional summary report adds $0.01 per run; other Apify usage draws on the same credit). Start with the 10 sample domains in the input (about $0.04), check the output, then paste your own prospect or client list.

### Use it with AI agents: Apify MCP and x402

Add the hosted Apify MCP server to Claude, Cursor, VS Code or any MCP client and expose just this Actor as a tool:

```json
{ "mcpServers": { "apify": { "url": "https://mcp.apify.com?tools=lentic_labs/email-dmarc-security-prospector" } } }
```

The client signs in with OAuth on first use (or send `Authorization: Bearer <APIFY_TOKEN>`). Then ask: *"Audit these 200 domains and list the M365 ones with DMARC p=none."*

**x402 agent payments (no Apify account):** per Apify's x402 docs, an agent makes one x402 payment in USDC to buy a prepaid, spend-capped Apify token and then uses it as a normal bearer token with the API or MCP server. See [docs.apify.com/integrations/x402](https://docs.apify.com/integrations/x402) for the current minimum, token lifetime and wallet setup. Agentic payments are marked experimental by Apify and may change.

### Use it via API (Python, JavaScript, cURL)

```bash
curl -X POST "https://api.apify.com/v2/acts/lentic_labs~email-dmarc-security-prospector/run-sync-get-dataset-items?maxTotalChargeUsd=1" \
  -H "Authorization: Bearer $APIFY_TOKEN" -H "Content-Type: application/json" \
  -d '{"domains": ["contoso.com", "fabrikam.com"]}'
```

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('lentic_labs/email-dmarc-security-prospector').call({ domains: ['contoso.com'], summary: true }, { maxTotalChargeUsd: 2 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map((r) => [r.domain, r.score, r.verdict]));
```

```python
from apify_client import ApifyClient
client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("lentic_labs/email-dmarc-security-prospector").call(run_input={"domains": ["contoso.com"]})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(row["domain"], row["score"], row["flagsText"])
```

### FAQ

#### Is DMARC checking legal / is this scanning?

The Actor reads only records that domain owners publish in public DNS for exactly this purpose, plus one ordinary HTTPS request to the website and the public MTA-STS policy file. It does no port scanning, no SMTP probing and no vulnerability testing.

#### Why is DKIM "not found" when my domain signs mail?

DKIM selectors can't be listed from DNS, so the Actor probes ~30 common ones. Large senders often use custom or rotating selectors (Google does), so "not found" doesn't prove DKIM is missing. Add known selectors in `extraDkimSelectors`.

#### How are SPF lookups counted (10-lookup limit)?

Following RFC 7208: every `include`, `a`, `mx`, `ptr`, `exists` and `redirect` costs one DNS lookup, counted recursively through nested includes. More than 10 is a permanent error and receivers treat SPF as failing ("too many DNS lookups").

#### What is MTA-STS and why does it matter?

MTA-STS (RFC 8461) lets a domain tell sending servers to deliver mail only over verified TLS to its listed MX hosts, which blocks downgrade attacks. It needs a `_mta-sts` TXT record and a policy at `https://mta-sts.<domain>/.well-known/mta-sts.txt`; `mode: enforce` is the protective setting. The Actor flags domains whose TXT exists but whose policy can't be fetched.

#### What do I need to show a BIMI logo in Gmail?

An enforced DMARC policy (quarantine or reject), a `default._bimi` TXT record pointing to an SVG logo, and for Gmail a Verified Mark Certificate (VMC) or Common Mark Certificate. The Actor reports `bimi`, `bimiLogo` and whether a certificate (`a=`) is referenced (`bimiVmc`).

#### What are Google and Yahoo's bulk sender requirements?

Since 2024, Gmail and Yahoo require bulk senders (roughly 5,000+ messages a day) to authenticate with SPF and DKIM, publish a DMARC record (at least `p=none`) with alignment, offer one-click unsubscribe, and keep spam rates low. Microsoft introduced similar requirements for Outlook.com in 2025. Primary sources: [Google sender guidelines](https://support.google.com/a/answer/81126) and [Yahoo sender best practices](https://senders.yahooinc.com/best-practices/). This Actor checks the DNS side (SPF, DKIM selectors, DMARC); unsubscribe headers and spam rates can't be checked from DNS.

#### Which resolvers are used?

Public resolvers 1.1.1.1 and 8.8.8.8 by default (configurable). If classic DNS is blocked in the runtime network, it falls back to DNS-over-HTTPS with the same providers.

### Changelog

- 0.1: first release. Bulk SPF, DMARC, DKIM, MTA-STS, TLS-RPT, BIMI and HTTPS audit with score, verdict and fix list.

# Actor input Schema

## `domains` (type: `array`):

Domains to audit. URLs, 'www.' hosts and email addresses are accepted and normalized (scheme, path and local part are stripped, duplicates removed).

## `domainsText` (type: `string`):

Paste a list or CSV column of domains, websites or email addresses. Anything that looks like a domain is extracted; other text (company names) is ignored.

## `maxDomains` (type: `integer`):

Stop after this many unique domains (0 = no limit). The run also stops cleanly when your maximum total charge is reached.

## `summary` (type: `boolean`):

Also write a Markdown REPORT (provider mix, DMARC policy distribution, top findings, lowest-scoring prospects) to the key-value store. Charged once per run as the 'report-summary' event.

## `runbookUrl` (type: `string`):

Optional URL copied into every row (column runbookUrl), e.g. your DMARC rollout guide or a booking page for outreach.

## `checkDkim` (type: `boolean`):

Look up about 26 common DKIM selectors (selector1, selector2, google, k1, s1, default, mxvault, ...).

## `extraDkimSelectors` (type: `array`):

Additional selectors to probe, e.g. your ESP's selector.

## `fetchMtaStsPolicy` (type: `boolean`):

When an \_mta-sts TXT record exists, fetch https://mta-sts.<domain>/.well-known/mta-sts.txt to read the mode.

## `checkHttps` (type: `boolean`):

One HTTPS GET to the apex (falls back to www. only if the apex is unreachable) to read certificate expiry and the HSTS header. The body is not downloaded.

## `stripWww` (type: `boolean`):

Audit example.com instead of www.example.com.

## `dnsServers` (type: `array`):

Public recursive resolvers to query (IP addresses; for DoH you may also give https:// endpoints).

## `dnsTransport` (type: `string`):

Auto uses classic DNS and falls back to DNS-over-HTTPS (same providers) if classic DNS is blocked.

## `dnsTimeoutMs` (type: `integer`):

Per-query timeout.

## `httpTimeoutMs` (type: `integer`):

Timeout for the site GET and MTA-STS policy fetch.

## `maxConcurrency` (type: `integer`):

Domains audited in parallel (max 20).

## Actor input object example

```json
{
  "domains": [
    "google.com",
    "microsoft.com",
    "apple.com",
    "github.com",
    "cloudflare.com",
    "mozilla.org",
    "mit.edu",
    "python.org",
    "zoho.com",
    "mimecast.com"
  ],
  "maxDomains": 0,
  "summary": false,
  "runbookUrl": "",
  "checkDkim": true,
  "extraDkimSelectors": [],
  "fetchMtaStsPolicy": true,
  "checkHttps": true,
  "stripWww": true,
  "dnsServers": [
    "1.1.1.1",
    "8.8.8.8"
  ],
  "dnsTransport": "auto",
  "dnsTimeoutMs": 4000,
  "httpTimeoutMs": 8000,
  "maxConcurrency": 10
}
```

# Actor output Schema

## `results` (type: `string`):

Default dataset: one flat, CSV-friendly row per domain. Views: overview, email_auth, outreach.

## `summary` (type: `string`):

JSON summary: counts by mail provider, DMARC policy distribution, grade distribution, average score, spoofable %, top flags and lowest-scoring domains.

## `report` (type: `string`):

Markdown report, written only when the 'summary' input is enabled.

# 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 = {
    "domains": [
        "google.com",
        "microsoft.com",
        "apple.com",
        "github.com",
        "cloudflare.com",
        "mozilla.org",
        "mit.edu",
        "python.org",
        "zoho.com",
        "mimecast.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("lentic_labs/email-dmarc-security-prospector").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 = { "domains": [
        "google.com",
        "microsoft.com",
        "apple.com",
        "github.com",
        "cloudflare.com",
        "mozilla.org",
        "mit.edu",
        "python.org",
        "zoho.com",
        "mimecast.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("lentic_labs/email-dmarc-security-prospector").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 '{
  "domains": [
    "google.com",
    "microsoft.com",
    "apple.com",
    "github.com",
    "cloudflare.com",
    "mozilla.org",
    "mit.edu",
    "python.org",
    "zoho.com",
    "mimecast.com"
  ]
}' |
apify call lentic_labs/email-dmarc-security-prospector --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,lentic_labs/email-dmarc-security-prospector"
        }
    }
}
```

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/fRxgmX5sRvutJbXND/builds/SmOx9oNdCL8Eivsss/openapi.json
