# Bulk DMARC, SPF & DKIM Checker — Email Domain Security Audit (`probelane/email-domain-security-auditor`) Actor

Check SPF, DKIM, DMARC, MX, BIMI, MTA-STS, TLS-RPT and DNSSEC for thousands of domains. Get a 0-100 score, A–F grade, spoofability flag, Gmail/Yahoo bulk-sender compliance and exact fixes. DNS-only, no API key.

- **URL**: https://apify.com/probelane/email-domain-security-auditor.md
- **Developed by:** [Probelane](https://apify.com/probelane) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 domain auditeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## Bulk DMARC, SPF & DKIM Checker — Email Domain Security Audit

**Audit email authentication for thousands of domains at once.** For every domain, this DMARC checker tests **SPF, DKIM, DMARC, MX, BIMI, MTA-STS, TLS-RPT and DNSSEC**. It returns a **0–100 security score**, an **A–F grade**, a **spoofable yes/no flag**, a **Gmail & Yahoo bulk-sender readiness** flag, and a prioritised list of issues with the **exact DNS fix** for each one.

The audit runs on DNS only. It sends no email, scrapes nothing and needs no API key, so it is cheap and safe to run on any list: 100 domains take about 2 minutes on the Apify platform.

### What does it check?

| Check | What you get |
|---|---|
| **SPF** | The record itself, a validity check, the `all` qualifier (‑all/~all/?all/+all), a **recursive DNS-lookup count against the 10-lookup limit**, broken includes, duplicate records, and detected senders (Google, Microsoft 365, SendGrid, Mailchimp, HubSpot, Amazon SES, Salesforce …) |
| **DMARC** | Policy (none/quarantine/reject), subdomain policy, pct, rua/ruf reporting addresses, alignment mode, and inheritance from the parent domain |
| **DKIM** | Probes about 60 common ESP selectors plus any you add. Reports keys found, **1024-bit weak keys**, revoked keys and wildcard records |
| **MX** | Mail servers, null MX, and mail provider detection (Google Workspace, Microsoft 365, Proofpoint, Mimecast, Cisco, Zoho …) |
| **BIMI** | Record, logo URL and VMC certificate presence |
| **MTA-STS / TLS-RPT** | Records, plus the live policy mode (enforce/testing) read from the public policy file |
| **DNSSEC** | Whether the domain is signed (DS record at the parent) |

### Who is it for?

- **MSPs and IT consultants.** Audit every client domain and send each one a fix list.
- **Email deliverability agencies and cold-email teams.** Check sending domains against the **Google and Yahoo bulk-sender requirements** (SPF + DKIM + DMARC).
- **Security teams.** Find spoofable domains across a brand portfolio or supply chain.
- **Sales prospecting for agencies.** Build a list of prospects whose email is spoofable or will fail Gmail/Yahoo rules, with the exact problem to open the conversation with.
- **Lead generation.** Turn on **"Output only domains that fail"** and you get a list of companies that need DMARC help, along with the reason.

### Pricing: pay per result, nothing else

This Actor uses Apify **pay-per-event** pricing, so the price is simple and predictable:

| You pay | When |
|---|---|
| **$0.003 per domain** ($3 per 1,000) | Once for each domain that returns a result (event `domain-audited`). Every charged row in the dataset has `"charged": true`. |
| $0.00005 per run | Apify's standard Actor-start fee. |
| **$0** | Invalid inputs, domains that do not exist in DNS (`status: nxdomain`) and unexpected errors. |

There are **no platform-usage fees on top** of the event price: compute, storage and traffic are included.

| Run size | Cost |
|---|---|
| 100 domains | $0.30 |
| 1,000 domains | $3.00 |
| 10,000 domains | $30.00 |
| 100,000 domains | $300.00 |

**Control your spend.** Set **Max cost per run** (Run options → "Maximum cost per run", or `maxTotalChargeUsd` in the API). The Actor checks your remaining budget before every domain, stops cleanly when the limit is reached and never charges beyond it. The status message tells you how many domains were left unprocessed.

**Try it free.** Apify's free plan includes $5 of monthly credit, enough for about 1,666 domains.

**Lead-gen mode note.** With **Output only domains that fail** switched on, passing domains are still audited and charged, but are left out of the dataset to keep your list clean. Their count is shown in the final status message and in the `RUN_SUMMARY` record of the run's key-value store.

### Input

```json
{
  "domains": ["apify.com", "github.com", "paypal.com", "jane@takealot.co.za"],
  "dkimSelectors": ["s1"],
  "probeCommonSelectors": true,
  "onlyFailing": false,
  "failBelowScore": 70
}
```

Email addresses and URLs are accepted. A leading `www.` is stripped, and other subdomains are kept, because mail is often sent from subdomains such as `mail.example.com`.

### Output example

```json
{
  "domain": "apify.com",
  "grade": "A",
  "securityScore": 97,
  "spoofable": false,
  "meetsGoogleYahooBulkSenderRules": true,
  "mailProvider": "Google Workspace",
  "spfRecord": "v=spf1 include:_spf.google.com … -all",
  "spfAllQualifier": "fail (-all)",
  "spfDnsLookups": 10,
  "dmarcPolicy": "reject",
  "dkimStatus": "found",
  "dkimSelectorsFound": [{"selector": "google", "keyBits": 2048}],
  "mtaStsMode": "enforce",
  "bimiRecord": "v=BIMI1; l=https://…",
  "topIssue": "1024-bit DKIM key on selector(s) intercom.",
  "issues": [
    {"severity": "low", "check": "DKIM", "issue": "1024-bit DKIM key on selector(s) intercom.", "fix": "Rotate to a 2048-bit key."}
  ],
  "status": "ok",
  "charged": true
}
```

### Scoring

Points are awarded as follows:

- **SPF:** 25
- **DMARC:** 35 (reject > quarantine > none, plus pct and rua)
- **DKIM:** 20
- **MX:** 5
- **MTA-STS, TLS-RPT, BIMI and DNSSEC:** 15 combined

Grades map to scores as A ≥ 85, B ≥ 70, C ≥ 55, D ≥ 40, F below 40. A domain that publishes `v=spf1 -all` is treated as **non-sending** and is not penalised for missing DKIM.

### FAQ

**Why does it say "DKIM not found" when I use DKIM?** DKIM keys sit under a *selector* that cannot be listed from outside. The Actor tries about 60 common selectors. If your provider uses a custom one, add it in `dkimSelectors`. In that case `dkimStatus` is `not_found_on_common_selectors`, not a confirmed failure.

**Does it send test emails?** No. It only reads DNS and fetches the public MTA-STS policy file.

**What is the `charged` field?** It is `true` on every row you paid for, so the bill always matches the dataset. Rows for invalid or non-existent domains are delivered with an `error` and are free.

**Can AI agents use it?** Yes. It runs with limited permissions and per-result pricing, so it works through the Apify API, the Apify MCP server and other AI-agent integrations.

**Can I monitor domains over time?** Yes. Schedule the Actor weekly and connect it to Slack, email or Google Sheets to catch DMARC or SPF changes.

### Related Actors by the same publisher

- ⭐ **[Security & Website Pain Prospect Finder](https://apify.com/probelane/security-prospect-finder):** selling to SMBs? It runs this check *plus* DMARC spoofing, SSL, outdated WordPress/PHP, downtime and domain expiry, then ranks companies by opportunity score (0–100) with evidence and a ready cold-open line. Built for MSPs, security consultants and web agencies.
- **[Bulk WHOIS & RDAP Domain Lookup](https://apify.com/probelane/bulk-domain-whois-rdap):** returns registrar, age, expiry and DNS.
- **[Bulk Website Tech Stack, SSL Expiry & Security Headers Checker](https://apify.com/probelane/website-tech-stack-health).**

*Keywords: dmarc checker, spf checker, dkim checker, bulk dmarc lookup, email deliverability, email deliverability audit, email authentication checker, spf record check, mta-sts check, bimi checker, google yahoo bulk sender requirements, cold email domain check, lead generation, sales prospecting, MSP, agencies, email security audit.*

# Actor input Schema

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

Domains to audit. Email addresses and URLs are accepted and reduced to their domain. Sub-domains are kept (mail is often sent from e.g. mail.example.com).

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

Optional: paste a large list separated by new lines, commas or spaces.

## `dkimSelectors` (type: `array`):

DKIM keys can only be found if you know the selector. Add any you know (e.g. 's1', 'google'); they are checked in addition to ~60 common ESP selectors.

## `probeCommonSelectors` (type: `boolean`):

Try ~60 selectors used by Google Workspace, Microsoft 365, SendGrid, Mailchimp, HubSpot, Amazon SES, Zoho, Brevo and others.

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

When an MTA-STS record exists, download the public policy file to read its mode (enforce/testing).

## `onlyFailing` (type: `boolean`):

Lead-gen mode: write only domains scoring below the threshold to the dataset (every audited domain is still counted).

## `failBelowScore` (type: `integer`):

Used with 'Output only domains that fail'.

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

Parallel checks. 10 is a safe default; raise for very large lists.

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

Optional cap (0 = no cap).

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

Optional resolver IPs, e.g. \["1.1.1.1"].

## Actor input object example

```json
{
  "domains": [
    "apify.com",
    "github.com",
    "paypal.com",
    "example.com"
  ],
  "probeCommonSelectors": true,
  "fetchMtaStsPolicy": true,
  "onlyFailing": false,
  "failBelowScore": 70,
  "maxConcurrency": 10,
  "maxItems": 0
}
```

# Actor output Schema

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

All results in the default dataset

# 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": [
        "apify.com",
        "github.com",
        "paypal.com",
        "example.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("probelane/email-domain-security-auditor").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": [
        "apify.com",
        "github.com",
        "paypal.com",
        "example.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("probelane/email-domain-security-auditor").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": [
    "apify.com",
    "github.com",
    "paypal.com",
    "example.com"
  ]
}' |
apify call probelane/email-domain-security-auditor --silent --output-dataset

```

## MCP server setup

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

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/peGwcyqt5iVSN8si5/builds/DIkZMHCJegUj6eYQ8/openapi.json
