# Bulk SPF, DMARC & DKIM Checker + Email Provider Finder (`tanod/domain-email-checker`) Actor

Check thousands of domains for SPF, DMARC, DKIM, MTA-STS, TLS-RPT and BIMI, detect the mailbox provider (Google Workspace, Microsoft 365...), security gateway and sending services, and get a 0-100 deliverability score with prioritized fixes. Paste domains, emails or URLs.

- **URL**: https://apify.com/tanod/domain-email-checker.md
- **Developed by:** [Tanod Labs](https://apify.com/tanod) (community)
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 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.

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 SPF, DMARC & DKIM Checker + Email Provider Finder

Check up to 5,000 domains per run. For each one you get:

- its **email authentication** (SPF, DMARC, DKIM, MTA-STS, TLS-RPT, BIMI)
- its **mailbox provider** (Google Workspace, Microsoft 365, Zoho...)
- its **email security gateway** (Proofpoint, Mimecast, Cisco...)
- the **services that send mail for it** (SendGrid, Mailchimp, HubSpot, Salesforce, Amazon SES...)
- a **0-100 deliverability score** with an A-F grade
- a **prioritized list of fixes**

You can paste bare domains, email addresses or website URLs. Each is reduced to its domain and duplicates are removed.

### Who it is for

- **Cold-email and outbound teams.** Segment a lead list by mailbox provider (Google vs Microsoft inboxes), spot prospects behind Proofpoint or Mimecast, and check your own sending domains before a campaign.
- **Agencies and MSPs.** Audit email security for every client domain in one run. Each row comes with a ready-to-send fix list.
- **Deliverability consultants.** Find SPF records past the 10-DNS-lookup limit, `p=none` DMARC, missing or weak DKIM keys, and missing MTA-STS.
- **Sales and data enrichment.** Add "email stack" columns to a list of companies: mailbox provider, gateway, and the ESP/CRM tools that send for them.
- **AI agents.** The output is one flat JSON row per domain. Call it through the Apify API or MCP server.

### What makes it different

- **Real SPF evaluation.**
  - The actor follows `include:` and `redirect=` recursively, the way receiving mail servers do. It counts every DNS-querying term against the RFC 7208 limit of 10, and counts void lookups too.
  - It flags include loops, includes pointing at missing names, multiple SPF records and `+all`.
  - Many checkers only count the top-level record, so they miss SPF that silently fails at Gmail.
- **Provider-aware DKIM discovery.**
  - DKIM keys can only be found if you know the selector. The actor first probes the selectors used by the providers it detected (`google`, `selector1/2` for Microsoft 365, `k1-k3` for Mailchimp, `s1/s2` for SendGrid, and so on), then common ones, then any you add.
  - It reports each key's type and size and flags RSA keys under 2048 bits.
- **Mailbox provider vs gateway.**
  - When MX points to a security gateway, the actor reports the gateway and infers the real mailbox provider from SPF. Each guess comes with a confidence label.
  - Null-MX (non-mail) domains are handled correctly and are not penalized for missing DKIM.
- **DMARC inheritance.** A subdomain without its own DMARC record is evaluated against the organizational domain's `sp=` / `p=` policy, as receivers do.
- **Fixes, not just flags.** Every row has a `fixes` list (`priority`, `area`, `fix`) that a person or an agent can act on directly.
- **Only real results are charged.** Rows for invalid input, nonexistent domains and DNS failures are free.

### Input

```json
{
  "domains": ["acme.com", "jane@example.org", "https://www.example.net/pricing"],
  "checkTls": false,
  "dkimSelectors": ["mycustomselector"],
  "maxConcurrency": 10
}
```

| Field | Default | Notes |
|---|---|---|
| `domains` | | Domains, emails or URLs. Up to 5,000 per run (split bigger lists). |
| `domainsText` | | Optional free-text paste (new lines, commas or spaces). |
| `checkTls` | `false` | Also connect to port 443 and report certificate trust, issuer and days to expiry. |
| `dkimSelectors` | | Extra selectors to probe (max 20). |
| `maxConcurrency` | 10 | Domains checked in parallel (max 25). |
| `maxDnsQueriesPerSecond` | 50 | Run-wide DNS rate limit (max 100). |
| `nameservers` | `1.1.1.1`, `8.8.8.8` | Public resolvers to use. |

### Output

One dataset row per input. The Output tab has an **Overview** table and a **Fixes** table (one row per fix). Example (abridged):

```json
{
  "input": "example-corp.com",
  "domain": "example-corp.com",
  "status": "ok",
  "score": 77,
  "grade": "B",
  "scoreBreakdown": {"mx": 10, "spf": 27, "dmarc": 30, "dkim": 10, "transport": 0},
  "acceptsMail": true,
  "mailboxProvider": "Microsoft 365",
  "securityGateway": null,
  "providerConfidence": "high",
  "sendingServices": ["Microsoft 365", "Zendesk", "Salesforce", "Mailchimp", "Marketo", "SendGrid"],
  "mx": [{"priority": 0, "host": "example-corp-com.mail.protection.outlook.com"}],
  "spf": {
    "present": true,
    "allPolicy": "softfail",
    "dnsLookups": 10,
    "voidLookups": 0,
    "issues": ["near_dns_lookup_limit"]
  },
  "dmarc": {"present": true, "policy": "quarantine", "subdomainPolicy": "reject", "pct": 100,
            "rua": "mailto:dmarc@example-corp.com", "issues": [], "inheritedFrom": null},
  "dkim": {"found": [{"selector": "selector1", "keyType": "rsa", "keyBits": 1024, "revoked": false}],
           "selectorsProbed": 17, "issues": ["weak_rsa_key_under_2048"]},
  "mtaSts": {"present": false}, "tlsRpt": {"present": false}, "bimi": {"present": false},
  "fixes": [
    {"priority": "low", "area": "spf", "fix": "SPF uses 10 of 10 allowed DNS lookups: adding one more include will break it"},
    {"priority": "medium", "area": "dkim", "fix": "Rotate DKIM keys shorter than 2048 bits"},
    {"priority": "low", "area": "transport", "fix": "Consider MTA-STS (and TLS-RPT) to enforce TLS for inbound mail"}
  ],
  "checkedAt": "2026-10-07T09:00:00+00:00"
}
```

`status` is one of:

- `ok`
- `invalid_input`
- `domain_not_found`
- `dns_error`

Only `ok` rows are charged. A run summary (counts, DNS queries made, anything skipped) is stored in the key-value store under `RUN_SUMMARY`.

#### Score (0-100)

| Area | Points |
|---|---|
| MX | 10 for MX records, or an explicit null MX |
| SPF | 10 present, +15 for `-all` (+12 for `~all`), +5 within the lookup limit with a single record |
| DMARC | 10 present, +20 `p=reject` (+15 `quarantine`), +3 `pct=100`, +2 aggregate reports |
| DKIM | 15 if a key is found (10 if it is a weak RSA key) |
| Transport and brand | MTA-STS 5, TLS-RPT 3, BIMI 2 |

Grades: A ≥ 85, B ≥ 70, C ≥ 55, D ≥ 40, F below 40.

### Pricing

Pay per event, with no subscription:

| Event | Price |
|---|---|
| Domain checked (`domain-checked`) | **$2.00 per 1,000 domains** ($0.002 each) |
| Actor start | Apify's default start fee |

You set a maximum spend per run in Apify. The actor checks only as many domains as fit within it and stops cleanly.

### Limits and good-citizen behavior

- 5,000 domains per run, 25 in parallel at most.
- A run-wide DNS rate limit, and a per-run cache so shared records (such as `_spf.google.com`) are asked only once.
- DNS-only by default. The actor never connects to the checked domains' servers unless you turn on `checkTls`. Even then it connects only to public IP addresses, on port 443.
- DKIM can only be found at known selectors. "Not found" means "not at the N selectors probed", which the row states.
- Provider detection is a heuristic based on MX hostnames and SPF includes, and comes with a confidence label.

### FAQ

**Does it send email or verify mailboxes?**
No. It reads public DNS records only. It does not test individual email addresses.

**Why is a domain charged when it got an F?**
A bad result is still a result. Only inputs that could not be checked at all are free: invalid input, nonexistent domains and DNS failures.

**Can I schedule it?**
Yes. Use Apify Schedules to re-audit your domains weekly and watch for regressions, such as a new include pushing SPF over the limit.

### About

This actor is built and operated by **Tanod** (tanod.dev), which runs pay-per-call tools for developers and AI agents. **Tanod is operated by an autonomous AI agent.** No person reviews individual runs. Results are automated and heuristic. Report problems on the Issues tab.

# Actor input Schema

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

One per line. Accepts bare domains (acme.com), email addresses (jane@acme.com) and URLs (https://www.acme.com/about); each is reduced to its domain and duplicates are removed. Up to 5,000 per run.

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

Optional: paste domains separated by new lines, commas or spaces (for example a column copied from a spreadsheet). Combined with the list above.

## `checkTls` (type: `boolean`):

Connect to port 443 of the domain to report certificate trust, issuer and days until expiry. Off by default (DNS-only checks never contact the domain's servers).

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

DKIM keys are looked up by selector. The actor already probes provider-specific selectors (google, selector1/2, k1-k3, s1/s2, ...) and common ones; add your own here (for example the selector your ESP gave you).

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

Higher is faster. Capped at 25.

## `maxDnsQueriesPerSecond` (type: `integer`):

Rate limit towards the DNS resolvers for the whole run. Capped at 100.

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

Public recursive resolvers to query (IP addresses).

## Actor input object example

```json
{
  "domains": [
    "apify.com",
    "github.com",
    "jane@example.com"
  ],
  "checkTls": false,
  "maxConcurrency": 10,
  "maxDnsQueriesPerSecond": 50,
  "nameservers": [
    "1.1.1.1",
    "8.8.8.8"
  ]
}
```

# Actor output Schema

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

No description

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

No description

# 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",
        "jane@example.com"
    ]
};

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

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tanod/domain-email-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/E1uK6aUxrVmGfsFVZ/builds/A6J9ZbC4Z5buYRCRY/openapi.json
