# Email Domain Auth Checker: SPF, DKIM, DMARC, BIMI, MTA-STS (`br_data_hub/email-domain-auth-checker`) Actor

Bulk check SPF (real recursive lookup count vs. the 10-lookup limit), DKIM, DMARC, BIMI, MTA-STS, TLS-RPT and MX for any domain. A-F grade and concrete fixes for cold email deliverability.

- **URL**: https://apify.com/br_data_hub/email-domain-auth-checker.md
- **Developed by:** [br_data_hub](https://apify.com/br_data_hub) (community)
- **Categories:** Lead generation, Developer tools, Marketing
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.10 / 1,000 domain checkeds

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

## Email Domain Auth Checker: SPF, DKIM, DMARC, BIMI, MTA-STS

Check the email authentication of **thousands of domains in one run** and get an **A-F grade with concrete fixes** for each one. Built for cold email, deliverability and RevOps teams: before warming up a new sending domain, auditing client domains or qualifying leads by email maturity.

What makes it different: the **real SPF lookup count**. Most checkers only read the top-level SPF record. This Actor expands every `include`, `redirect`, `a` and `mx` recursively, counts DNS lookups exactly as RFC 7208 does (limit 10, void-lookup limit 2), and tells you which include costs the most: *"SPF makes 13 DNS lookups; the limit is 10. Cut at least 3: replace include:\_spf.google.com (4 lookups, 27 IP ranges) with its ip4/ip6 ranges, or send from a subdomain."* Over 10 lookups, SPF fails for **all** your mail at Gmail, Outlook and Yahoo, and the sender rarely notices.

### What it checks

| Check | Details |
|---|---|
| **MX** | Records, null MX (RFC 7505), mailbox provider (Google Workspace, Microsoft 365, Zoho, Proton, ...) |
| **SPF** | Record, recursive lookup count vs. 10, void lookups vs. 2, lookups per include, `all` qualifier, multiple records, include loops, missing includes, `ptr`, length |
| **DMARC** | Policy, subdomain policy, `pct`, `rua`/`ruf` (masked), DKIM and SPF alignment (`adkim`/`aspf`), inheritance from the organizational domain, external report authorization |
| **DKIM** | About 40 common selectors (Google, Microsoft 365, SendGrid, Mailchimp/Mandrill, Mailgun, Amazon SES, Zoho, Proton, HubSpot, Brevo, ...) plus your own; key type, **key size** (1024 vs 2048 bits), revoked keys, test mode |
| **BIMI** | `default._bimi` record, logo and certificate URLs, whether DMARC is strong enough for the logo to show |
| **MTA-STS** | `_mta-sts` record and the HTTPS policy file: mode, `max_age` and whether every MX host is covered |
| **TLS-RPT** | `_smtp._tls` record and report addresses |

### Input

| Field | Description |
|---|---|
| `domains` | Domains to check, up to 5,000 per run. URLs and email addresses work too (`https://www.acme.com`, `john@acme.com` → `acme.com`). |
| `dkimSelectors` | Your own DKIM selectors. DNS cannot list selectors, so only tested ones can be found. |
| `testCommonSelectors` | Also test the ~40 common selectors (default `true`). |
| `fetchMtaStsPolicy` | Download and validate the MTA-STS policy file over HTTPS (default `true`). |
| `dnsMode` | `auto` (default): normal DNS, switching to DNS over HTTPS when port 53 is blocked. `udp` or `doh` to force one. |
| `dohProvider` / `dohUrl` | Cloudflare (default) or Google Public DNS, or any RFC 8484 endpoint. |
| `nameservers` | Resolvers for normal DNS (default `1.1.1.1`, `8.8.8.8`). |

The default input checks 3 well-known domains and finishes in a few seconds.

### Output

One dataset item per domain (abridged):

```json
{
  "domain": "outreach-co.com.br",
  "status": "ok",
  "grade": "D",
  "score": 57,
  "mailProvider": null,
  "summary": {"mx": true, "spf": false, "spfLookups": 13, "dmarcPolicy": "none", "dkim": true,
              "bimi": false, "mtaSts": false, "tlsRpt": false, "criticalIssues": 1},
  "spf": {
    "record": "v=spf1 mx a include:_spf.google.com include:spf.protection.outlook.com include:sendgrid.net ... ~all",
    "lookups": 13, "lookupLimit": 10, "voidLookups": 0, "allQualifier": "~all",
    "lookupBreakdown": [{"term": "include:_spf.google.com", "lookups": 4, "ipRanges": 27},
                        {"term": "include:sendgrid.net", "lookups": 1, "ipRanges": 9}],
    "errors": ["SPF needs 13 DNS lookups; RFC 7208 allows 10 (permerror)"]
  },
  "dmarc": {"policy": "none", "pct": 100, "rua": [{"address": "r***@dmarc-vendor.example", "domain": "dmarc-vendor.example"}],
            "dkimAlignment": "relaxed", "spfAlignment": "relaxed"},
  "dkim": {"found": true, "selectorsTested": 38,
           "selectors": [{"selector": "s1", "keyType": "rsa", "keyBits": 1024, "revoked": false, "testing": true}]},
  "bimi": {"found": false},
  "mtaSts": {"found": false},
  "tlsRpt": {"found": false},
  "recommendations": [
    {"severity": "critical", "area": "SPF",
     "message": "SPF makes 13 DNS lookups; the limit is 10, so receivers return permerror and SPF fails for all your mail.",
     "fix": "Cut at least 3 lookup(s): replace include:_spf.google.com (4 lookups, 27 IP ranges) with the ip4/ip6 ranges they resolve to ..."},
    {"severity": "high", "area": "DMARC", "message": "DMARC policy is p=none: spoofed mail is still delivered.", "fix": "..."},
    {"severity": "medium", "area": "DKIM", "message": "DKIM selector \"s1\" uses a 1024-bit RSA key.", "fix": "Rotate to a 2048-bit key; ..."}
  ],
  "checkedAt": "2026-10-30T23:01:57+00:00",
  "dnsTransport": "udp"
}
```

**Grade.** 100 points: MX 10, SPF 25, DMARC 30, DKIM 20, MTA-STS 5, TLS-RPT 5, BIMI 5. A ≥ 90, B ≥ 80, C ≥ 65, D ≥ 50, F below. Caps: `+all` → F; broken SPF (permerror) → at most D; no SPF, no DMARC or no DKIM found → at most C (Gmail and Yahoo require all three for bulk senders); DMARC `p=none` → at most B.

The **Overview** view shows grade, checks and fixes; the **SPF lookups** view shows the lookup breakdown per domain.

Domains that do not exist or whose DNS cannot be read, and inputs that are not a domain, URL or email address (for example an IP address), return `status: "error"` with an `error` message and **are not charged**.

### How much does it cost?

Pay per event, with no platform usage added to your bill:

| Event | When | Price |
|---|---|---|
| `apify-actor-start` | Once per run, charged by Apify | US$ 0.00005 (Apify default) |
| `domain` | Once per domain successfully checked | US$ 0.003 |

Domains that end in an error are free.

How the `domain` price (**US$ 3 per 1,000 domains**) was set:

- Apify compute costs US$ 0.20 per compute unit (1 CU = 1 GB of memory for 1 hour). The Actor runs with **256 MB** (peak measured: 90 MB for 1,000 domains).
- A domain needs about 46 DNS queries and ~20 ms of CPU. Measured locally (4 cores, 30 ms simulated DNS latency): 1,000 domains in 58 s. At 256 MB Apify gives 1/16 of a CPU core, so expect about 5 minutes per 1,000 domains there: 0.25 GB × 5 min ≈ 0.021 CU ≈ **US$ 0.004 per 1,000 domains**, plus ~US$ 0.0003 of start-up per run. More memory means more CPU and a faster run at about the same cost.
- After Apify's 20% commission, US$ 0.003 leaves US$ 0.0024 per domain, hundreds of times the compute cost, so even a one-domain run is profitable. The start event covers the first seconds of compute of each run.

Store comparison (October 2026, prices from the Store listings): `tanod/domain-email-checker` from US$ 2.00 per 1,000, `mambalabs/domain-deliverability-checker` from US$ 4.25 per 1,000, `apifmcpfactory/email-domain-checker` US$ 5 per 1,000, `taroyamada/dns-dmarc-security-checker` from US$ 7 per 1,000. At US$ 3 per 1,000 we sit below most of them and add the real recursive SPF lookup count and the per-include breakdown.

**$0.003 per domain checked** (`domain` event, pay per event). Charged once per domain successfully checked (grade, SPF, DKIM, DMARC, BIMI, MTA-STS and TLS-RPT). Domains that end in an error are free.

| You get | Free tier | Bronze tier (10% off) | Gold tier (30% off) |
|---|---|---|---|
| 100 domain checkeds | $0.30 | $0.27 | $0.21 |
| 1,000 domain checkeds | $3.00 | $2.70 | $2.10 |
| 10,000 domain checkeds | $30.00 | $27.00 | $21.00 |

Discounts follow your Apify subscription tier: Bronze 10%, Silver 20%, Gold and above 30%. No subscription to this Actor and no charge for platform usage: you pay only for results.
The Apify Free plan includes $5 of monthly credit, enough for about **1,666 domain checkeds** at the list price.
You can cap the cost of any run with **Maximum cost per run**; the Actor stops when it is reached.

### Data source

Public DNS. Domain owners publish these records so that any mail server on the internet can read them; nothing is scraped and no login is needed. Queries go to public recursive resolvers (Cloudflare 1.1.1.1 and Google Public DNS by default, over UDP/TCP 53 or DNS over HTTPS) and, for MTA-STS, to the domain's own `https://mta-sts.<domain>/.well-known/mta-sts.txt`, which RFC 8461 makes public for every sender. Results are a snapshot at `checkedAt`: DNS caching (TTL) can delay changes by minutes to hours.

Privacy: DMARC and TLS-RPT report addresses can name a person, so the mailbox part is masked (`j***@acme.com`); only the domain is kept. No email is sent and no mailbox is probed.

Rate limits: public resolvers limit queries per IP (Google Public DNS documents about 1,500 queries per second). The Actor caches every answer for the whole run, so shared records such as `_spf.google.com` are read once, and it runs at most 25 domains at a time, well below that limit.

Limits: DKIM keys cannot be enumerated, so a "no DKIM" result means none of the tested selectors answered; add your selector to confirm. The organizational domain for DMARC inheritance uses a built-in list of common two-label suffixes (`com.br`, `co.uk`, ...), not the full Public Suffix List. SPF macros (`%{i}`) are counted as one lookup but not expanded.

Not affiliated: this Actor is an independent tool. It is not affiliated with, endorsed by or sponsored by Google, Microsoft, Cloudflare, Yahoo or any email or DNS provider named in this page; their names are used only to describe which records and resolvers are checked.

### More questions

#### Why does it say DKIM was not found when my mail is signed?

DNS cannot list DKIM selectors, so a checker only finds the ones it tries. The Actor tests about 40 common selectors (Google, Microsoft 365, SendGrid, Mailchimp, Mailgun, Amazon SES and others). If your provider uses a custom one, add it in `dkimSelectors`. You can see the selector in the `DKIM-Signature` header (`s=`) of any email you sent.

#### How is the SPF lookup count different from other checkers?

Many tools read only the top-level SPF record. This Actor expands every `include`, `redirect`, `a` and `mx` recursively and counts DNS lookups the way RFC 7208 does (limit 10, void lookups limit 2). `spf.lookupBreakdown` shows what each include costs, so you know which one to replace with `ip4`/`ip6` ranges.

#### How is the A-F grade calculated?

Points out of 100, weighted toward what decides inbox placement: SPF (valid, 10 lookups or fewer, `-all` or `~all`), DMARC (policy, `pct=100`, `rua` set), DKIM (found, 2048-bit key), plus MX, MTA-STS, TLS-RPT and BIMI. Some problems cap the grade whatever the points: `+all` gives F, an invalid SPF at most D, missing SPF, DMARC or DKIM at most C, and DMARC `p=none` at most B.

#### Can I check a subdomain such as `news.acme.com`?

Yes. Subdomains are checked as typed (only a leading `www.` is removed). If the subdomain has no DMARC record of its own, the Actor reads the organizational domain's record and marks it `inherited: true`, which is how receivers apply it.

#### Does it send emails or test mailboxes?

No. It only reads public DNS records and, for MTA-STS, the public policy file at `https://mta-sts.<domain>/.well-known/mta-sts.txt` (turn that off with `fetchMtaStsPolicy: false`). DMARC and TLS-RPT report addresses come back masked (`j***@acme.com`).

#### I just changed a record. Why does the result still show the old one?

Results are a snapshot of public resolvers at `checkedAt`. Resolvers cache records for their TTL, so a change can take minutes to hours to show. Run it again after the old record's TTL has passed.

#### How many domains can I check per run?

Up to 5,000. URLs and email addresses are accepted and reduced to the domain, duplicates are checked once, and up to 25 domains are checked at the same time. Domains that do not exist or whose DNS fails come back with `status: error` and are not charged.

### Use it via API

Run it from any language and get the results as JSON. Replace `<YOUR_API_TOKEN>` with your [Apify API token](https://console.apify.com/settings/integrations).

```bash
curl -X POST "https://api.apify.com/v2/acts/br_data_hub~email-domain-auth-checker/run-sync-get-dataset-items?token=<YOUR_API_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"domains": ["google.com", "github.com", "apify.com"]}'
```

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")
run = client.actor("br_data_hub/email-domain-auth-checker").call(run_input={'domains': ['google.com', 'github.com', 'apify.com']})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)
```

You can also schedule runs, send results to webhooks, or connect the Actor to Make, Zapier, n8n, Google Sheets and other [integrations](https://docs.apify.com/platform/integrations).

### Use it with AI agents (MCP)

This Actor is ready for AI agents: typed input and output schemas, pay-per-event pricing, limited permissions and short runs.

- **Apify MCP server:** add `https://mcp.apify.com/?tools=br_data_hub/email-domain-auth-checker` to Claude, ChatGPT, Cursor, VS Code or any MCP client, and the agent can call this Actor as a tool.
- **Agent frameworks:** call it from LangChain, LlamaIndex, CrewAI or the OpenAI Agents SDK through the Apify integrations, or with the API call above.
- Results come as structured JSON with stable field names, documented in the **Output** tab.

### FAQ

#### Where does the data come from?

Public DNS. Domain owners publish these records so that any mail server on the internet can read them; nothing is scraped and no login is needed. Queries go to public recursive resolvers (Cloudflare 1.1.1.1 and Google Public DNS by default, over UDP/TCP 53 or DNS over HTTPS) and, for MTA-STS, to the domain's own `https://mta-sts.<domain>/.well-known/mta-sts.txt`, which RFC 8461 makes public for every sender. Results are a snapshot at `checkedAt`: DNS caching (TTL) can delay changes by minutes to hours.

#### How much does it cost?

$0.003 per domain checked. Discounts follow your Apify subscription tier: Bronze 10%, Silver 20%, Gold and above 30%. See **How much does it cost?** above for examples.

#### Do I pay for errors or empty results?

No. Charged once per domain successfully checked (grade, SPF, DKIM, DMARC, BIMI, MTA-STS and TLS-RPT). Domains that end in an error are free.

#### Can I run it on a schedule or connect it to other tools?

Yes. Use Apify schedules to run it daily or weekly, webhooks to get notified, and integrations such as Make, Zapier, n8n and Google Sheets. Everything also works through the API.

#### Can AI agents use it?

Yes. It works as a tool in the Apify MCP server and in agent frameworks, and AI agents can pay per event. See **Use it with AI agents (MCP)**.

#### Is it legal to use this data?

The Actor reads only public sources. You are responsible for how you use the data and for complying with the source's terms and with privacy laws such as GDPR.

#### I found a bug or need a feature. What should I do?

Open an issue in the **Issues** tab. We answer within 14 days, usually much sooner.

### More Actors from BR Data Hub

| Actor | What it does |
|---|---|
| [Domain RDAP Lookup: modern WHOIS for any TLD](https://apify.com/br_data_hub/domain-rdap-lookup) | Bulk RDAP lookup on each TLD's official registry server (IANA bootstrap): registrar, creation, update and expi |
| [CVE Risk Prioritizer, from NVD, CISA KEV and EPSS data](https://apify.com/br_data_hub/cve-risk-prioritizer) | One row per CVE built from public NVD (CVSS, CWE, affected products), CISA KEV (exploited, due date, ransomwar |
| [Food, Drug & Device Recalls Monitor (from openFDA data)](https://apify.com/br_data_hub/fda-recalls-monitor) | Food, drug and medical device recalls from the official openFDA enforcement API in one uniform format |
| [Clinical Trials Tracker, from ClinicalTrials.gov data](https://apify.com/br_data_hub/clinical-trials-tracker) | Search public ClinicalTrials.gov data by condition, drug, sponsor, phase, status and country, and track what c |

See all our Actors at [apify.com/br_data_hub](https://apify.com/br_data_hub).

### Usar em português

Informe domínios (ou URLs e e-mails) e receba, para cada um, a nota de A a F e as correções concretas de SPF, DKIM, DMARC, BIMI, MTA-STS e TLS-RPT. O diferencial é a contagem real de consultas DNS do SPF, com expansão recursiva dos `include` e o custo de cada um, contra o limite de 10 da RFC 7208: acima disso o SPF falha para todos os seus e-mails. Útil para cold email e entregabilidade. Informe seus seletores DKIM em `dkimSelectors` se não forem os comuns. A cobrança é por domínio verificado; domínios inexistentes ou com erro de DNS não são cobrados.

**Quanto custa:** $0.003 por resultado (`domain`); 1.000 resultados saem por $3.00. Desconto pelo nível da assinatura Apify: Bronze 10%, Silver 20%, Gold ou acima 30%. Erros e consultas sem resultado não são cobrados. Use também pela API ou por agentes de IA no servidor MCP da Apify (`https://mcp.apify.com/?tools=br_data_hub/email-domain-auth-checker`).

# Actor input Schema

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

Enter domains to check, one per line, up to 5,000 per run, e.g. acme.com. Website URLs and e-mail addresses work too: 'https://www.acme.com' and 'john@acme.com' are both read as acme.com.

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

Enter your own DKIM selectors (the part before .\_domainkey), one per line, e.g. s1 or mailjet. DNS cannot list DKIM keys, so only tested selectors can be found.

## `testCommonSelectors` (type: `boolean`):

Keep on to also try about 40 selectors used by Google Workspace, Microsoft 365, SendGrid, Mailchimp, Mailgun, Amazon SES, Zoho, Proton and others.

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

Keep on to download https://mta-sts.<domain>/.well-known/mta-sts.txt and check its mode and that it covers every MX host. Turn off for DNS-only checks.

## `dnsMode` (type: `string`):

Keep Auto to use normal DNS (port 53) and switch to DNS over HTTPS if port 53 is blocked. Pick Normal DNS or DNS over HTTPS to force one.

## `dohProvider` (type: `string`):

Pick the public resolver used for DNS over HTTPS.

## `dohUrl` (type: `string`):

Type an RFC 8484 endpoint to override the provider above, e.g. https://dns.quad9.net/dns-query.

## `nameservers` (type: `array`):

Enter recursive resolvers for normal DNS (port 53), one per line, e.g. 9.9.9.9. Leave empty for 1.1.1.1 and 8.8.8.8.

## Actor input object example

```json
{
  "domains": [
    "google.com",
    "github.com",
    "apify.com"
  ],
  "dkimSelectors": [],
  "testCommonSelectors": true,
  "fetchMtaStsPolicy": true,
  "dnsMode": "auto",
  "dohProvider": "cloudflare"
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset with all results

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

// Run the Actor and wait for it to finish
const run = await client.actor("br_data_hub/email-domain-auth-checker").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",
        "github.com",
        "apify.com",
    ] }

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

```

## MCP server setup

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

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/SsCTdlP51UqB01snI/builds/tM5a6g77MBAHdlmXu/openapi.json
