# Email Deliverability Report: SPF, DKIM, DMARC, MTA-STS (`steadydata/email-deliverability-report`) Actor

One deliverability report per domain, up to 500 per run: MX and mail provider, the SPF record with its DNS lookup count against the limit of ten, DKIM selectors that really exist, the DMARC policy and reporting, MTA-STS mode, TLS-RPT, BIMI and DNSSEC, plus ranked issues and a score.

- **URL**: https://apify.com/steadydata/email-deliverability-report.md
- **Developed by:** [Steadydata Team](https://apify.com/steadydata) (community)
- **Categories:** Developer tools
- **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 Deliverability Report: SPF, DKIM, DMARC, MTA-STS

One deliverability report per domain, **up to 500 domains per run**: who handles the mail,
the SPF record **with its DNS lookup count against the limit of ten**, the DKIM selectors
that really exist, the DMARC policy and whether anyone receives the reports, MTA-STS in
enforce or testing mode, TLS-RPT, BIMI and DNSSEC. Every report ends with ranked issues and
a score. You pay per delivered report.

### Why this scraper

- **It counts the SPF lookups.** A receiving server is allowed to give up after ten DNS
  lookups, and a record over that limit quietly fails for part of the world. Counting it
  means following every `include` down the tree, which is exactly what this does. Measured
  on 14-09-2026: apify.com sits at 9 of 10, github.com at exactly 10. Most checkers never
  tell you that.
- **DKIM selectors that actually answer.** DKIM cannot be listed, only asked for by name, so
  thirteen selectors that the large providers use are tried and the ones that exist are
  reported. In the same test github.com returned five and mollie.com one.
- **MTA-STS in enforce or testing.** The DNS record only says a policy exists. Whether it is
  enforced is in the policy file, which this actor reads for you, under that host's
  robots.txt, and you can switch that request off.
- **It ends with a conclusion.** Every report carries issues worst first, each with a
  severity, a sentence in plain language and the measured value, plus a score from 0 to 100
  and a grade from A to F.
- **Public DNS only.** No site is crawled, nothing is logged in, no proxy is needed. The
  records exist so that mail servers world-wide can read them.
- **No personal data.** An e-mail address in your input is reduced to its domain immediately
  and never delivered. DMARC reporting addresses are reported as a yes or no, never as the
  addresses themselves.

### Who this is for

Agencies and IT teams auditing a domain portfolio. Deliverability consultants qualifying
prospects (a list of 500 domains, sorted by score, is a pipeline). Security teams checking
that spoofing protection is actually switched on, and sales teams that want to know which
mail platform a company uses.

### Who this is not for

**Read this before you buy.**

- **DKIM is a guess, not a listing.** There is no way to enumerate selectors, so a domain
  with DKIM under a private selector comes back as `no-dkim-selector-found`. Add your own
  selectors in the input if you know them. That is a property of DKIM, not of this actor.
- **It reads records, it does not send mail.** No test message, no inbox placement, no
  blacklist check. If you need to know whether Gmail puts you in spam, this is the wrong
  tool; this tells you whether the records that decide it are correct.
- **The score is an opinion, and a documented one.** Every issue costs points: 25 for a
  critical one, 10 for a warning, 3 for an informational one, starting from 100. A domain
  with no SPF and no DMARC scores 50 or below. Use the issue list for decisions and the
  score for sorting.
- **A domain without MX gets a report too.** It simply does not receive mail, which is
  reported as an issue of its own rather than as an error.

### Input example

```json
{
    "domains": ["apify.com", "github.com", "info@mollie.com"],
    "checkMtaStsPolicy": true,
    "extraDkimSelectors": ["myselector"]
}
```

A domain, a URL or an e-mail address all work; only the domain is used and kept.

### Output example

```json
{
    "domain": "github.com",
    "score": 75,
    "grade": "B",
    "issueCount": 3,
    "issues": [
        {"code": "spf-lookups-near-limit", "severity": "warning",
         "message": "SPF uses 10 of the 10 allowed DNS lookups", "value": 10},
        {"code": "spf-softfail", "severity": "info",
         "message": "SPF ends in ~all (softfail); -all is the stricter choice once you are sure", "value": null},
        {"code": "dmarc-policy-quarantine", "severity": "info",
         "message": "DMARC is set to p=quarantine; p=reject is the end state", "value": null}
    ],
    "mxRecords": ["10 alt3.aspmx.l.google.com."],
    "mailProvider": "Microsoft 365",
    "acceptsMail": true,
    "hasSpf": true,
    "spfRecord": "v=spf1 ip4:192.30.252.0/22 include:_spf.google.com ~all",
    "spfQualifier": "softfail",
    "spfLookupCount": 10,
    "spfIncludes": ["_spf.google.com", "spf.protection.outlook.com"],
    "hasDkim": true,
    "dkimSelectors": ["google", "s1", "s2", "selector1", "selector2"],
    "hasDmarc": true,
    "dmarcPolicy": "quarantine",
    "dmarcSubdomainPolicy": null,
    "dmarcPercent": 100,
    "dmarcAggregateReports": true,
    "dmarcForensicReports": false,
    "hasMtaSts": false,
    "mtaStsMode": null,
    "hasTlsRpt": false,
    "hasBimi": false,
    "hasDnssec": false,
    "status": "ok"
}
```

Error codes: `INVALID_DOMAIN`, `LOOKUP_FAILED`. Neither is charged. `INPUT_TRUNCATED` appears once when your input is longer than this actor accepts.

### Related actors from steadydata

- [domain-dns-ssl-report](https://apify.com/steadydata/domain-dns-ssl-report): registration, expiry, DNS and TLS for the same domain
- [website-tech-stack](https://apify.com/steadydata/website-tech-stack): what the site behind the domain runs on

### Pricing

Pay per event: one `domain-checked` event per delivered report. No start fee, no separate
platform-usage surcharge, and no charge for input that is not a domain.

**Free Apify plan:** this actor delivers up to 25 rows per run for accounts on the Apify free
plan, and then stops with a message. That limit is set by us, not by Apify. It exists so the
actor keeps paying for itself for the people who do pay. Any paid Apify plan runs it at full
size, billed per delivered row, with failed rows never charged.

**Reviews:** if this actor saves you time, a short review on this page is the one thing that
helps most. Ratings are what other buyers look at first, and we have no other way to ask.

### FAQ

**Which issues can a report carry?**
`no-mx`, `no-spf`, `spf-allows-everything`, `spf-too-many-lookups`, `spf-lookups-near-limit`,
`spf-neutral`, `spf-no-all-mechanism`, `spf-softfail`, `no-dkim-selector-found`, `no-dmarc`,
`dmarc-policy-none`, `dmarc-policy-quarantine`, `dmarc-partial-coverage`,
`dmarc-no-reporting`, `dmarc-subdomains-unprotected`, `no-mta-sts`, `mta-sts-testing-only`,
`no-tls-rpt` and `no-dnssec`.

**Why does the SPF lookup count differ from other tools?**
Because most tools count only the first level. This one follows every `include` and
`redirect` down five levels, which is how a receiving server counts it too.

**Which DKIM selectors are tried?**
default, google, selector1, selector2, k1, s1, s2, dkim, mail, smtp, zoho, mandrill and
mimecast20200114, plus anything you add yourself.

**Is personal data collected?**
No. Input addresses are reduced to their domain and discarded, and DMARC reporting addresses
are reported only as present or absent. Everything delivered is a public DNS record about a
domain, not about a person.

**What happens when a record changes?**
That is the point: run the list again and the report changes with it. The actor is run
against real domains daily and fixed fast, and while it is broken you are not charged.

# Changelog

This Actor's version history is a separate document: https://apify.com/steadydata/email-deliverability-report/changelog.md

# Actor input Schema

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

Domains or e-mail addresses, one per row, up to 500. An address is reduced to its domain and the address itself is never stored or delivered.

## `checkMtaStsPolicy` (type: `boolean`):

On, the policy file on mta-sts.<domain> is fetched so the report can say whether MTA-STS is in enforce or testing mode. That is one HTTP request per domain and it obeys that host's robots.txt.

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

DKIM can only be found by name. Thirteen common selectors are tried by default; add your own here if you know the provider uses a different one.

## Actor input object example

```json
{
  "domains": [
    "apify.com",
    "nu.nl",
    "bol.com"
  ],
  "checkMtaStsPolicy": true
}
```

# Actor output Schema

## `results` (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",
        "nu.nl",
        "bol.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("steadydata/email-deliverability-report").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",
        "nu.nl",
        "bol.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("steadydata/email-deliverability-report").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",
    "nu.nl",
    "bol.com"
  ]
}' |
apify call steadydata/email-deliverability-report --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,steadydata/email-deliverability-report"
        }
    }
}
```

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/WDB16vF9TluIrOzSx/builds/9kh4bV9bAKCOnOhbV/openapi.json
