# Email Security Audit – SPF, DKIM, DMARC, MTA-STS (bulk) (`vemka/email-domain-security-audit`) Actor

Check hundreds of domains for SPF (incl. 10-lookup limit), DKIM, DMARC, MX provider, MTA-STS, TLS-RPT, BIMI, DNSSEC and CAA. Get a 0–100 score, A–F grade, Google/Yahoo bulk-sender readiness and fix recommendations. Monitor mode reports only changes.

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

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-usage

## 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 Security Audit – SPF, DKIM, DMARC, MTA-STS (bulk)

Check the e-mail security of **hundreds of domains in one run**. For every domain you get a **0–100 score, an A–F grade, a Google/Yahoo bulk-sender readiness flag and concrete fix recommendations** – not just raw DNS records. Optionally, get a **client-ready report** for every domain in English or Czech, with your own brand.

Paste domains, website URLs or e-mail addresses; the Actor normalizes and de-duplicates them for you.

### What it checks

| Check | What you get |
|---|---|
| **MX & provider** | MX hosts, null-MX detection, mail provider (Google Workspace, Microsoft 365, Proton, Zoho, Seznam, …) |
| **SPF** | Record, syntax errors, multiple records, `+all` / `?all` problems, **recursive count of DNS lookups (RFC 7208 limit of 10)**, void lookups, include loops, deprecated `ptr` |
| **DMARC** | Policy, subdomain policy, `pct`, report addresses, fallback to the organizational domain |
| **DKIM** | Keys found on ~30 common selectors (Google, Microsoft 365, Proton, Zoho, Mailjet, SendGrid, …) + your own selectors, **estimated key size** (flags 1024-bit keys), revoked keys |
| **MTA-STS** | DNS record **and** the HTTPS policy file (mode, max_age, mx); tells an unreachable policy file apart from an invalid one |
| **TLS-RPT** | Reporting record |
| **BIMI** | Logo and certificate URLs |
| **DNSSEC** | Validation status (AD flag) |
| **CAA** | Certificate authority restrictions |

### Why it matters

- **Gmail and Yahoo** require SPF, DKIM and DMARC from bulk senders since February 2024; unauthenticated mail is rejected or sent to spam.
- **Supply-chain security questionnaires** (e.g. under NIS2) increasingly ask suppliers to prove SPF/DKIM/DMARC.
- **Spoofing protection:** without an enforcing DMARC policy anyone can send e-mail as your domain.

### Typical uses

- **MSPs, IT and security consultants:** audit all client domains in minutes and hand over a prioritized fix list or a branded report.
- **Agencies and deliverability specialists:** check sending domains before a campaign.
- **Sales and lead qualification:** find companies whose domains are not bulk-sender ready.
- **Monitoring:** schedule a weekly run with monitor mode and get only the domains whose configuration changed.

### Client-ready report (white label)

Turn on **Create a client-ready report** and every domain gets a print-ready report you can send to a client or a manager as it is:

- **Grade and one-sentence verdict** in plain language
- **Three key questions** with a traffic light: *Can someone send e-mail in your name? Do you meet Gmail, Yahoo and Outlook sender requirements? Is incoming mail protected by encryption?*
- **Overview of all checks** with a short explanation of what each one is for
- **Prioritised fixes** – for each one, what it means for the business and how to fix it
- **Technical details** for the administrator (the actual DNS records)

Choose **Neutral** (no logo, small credit line) or **Your brand**: your company name or logo and your contact in a *Want this fixed?* box, with no mention of this Actor. The report link is in the `reportUrl` field; open it in a browser and print or save it as PDF.

### Input example

```json
{
  "domains": ["example.com", "https://www.shop.example.org/", "info@company.cz"],
  "language": "en",
  "monitorMode": true,
  "onlyChanges": false
}
```

With a branded client report in Czech:

```json
{
  "domains": ["client-one.cz", "client-two.cz"],
  "language": "cs",
  "clientReport": true,
  "reportBranding": "custom",
  "reportCompanyName": "Acme IT",
  "reportLogoUrl": "https://acme.example/logo.png",
  "reportContact": "help@acme.example · acme.example"
}
```

### Output example (shortened)

```json
{
  "domain": "example.com",
  "score": 62,
  "grade": "C",
  "bulkSenderReady": false,
  "mx": { "provider": "Microsoft 365", "receivesMail": true },
  "spf": { "record": "v=spf1 include:spf.protection.outlook.com ~all", "valid": true, "lookupCount": 2 },
  "dmarc": { "policy": "none", "rua": [] },
  "dkim": { "selectorsFound": [{ "selector": "selector1", "keyBits": 2048 }] },
  "findings": [
    { "id": "DMARC_NONE", "severity": "medium", "title": "DMARC policy is \"none\" (monitoring only)", "recommendation": "After reviewing reports, move to \"p=quarantine\" and then \"p=reject\"…" }
  ],
  "changes": ["dmarc"],
  "reportUrl": "https://api.apify.com/v2/key-value-stores/…/records/report-example.com"
}
```

A run summary (counts per grade, failures, DNS queries) is saved to the key-value store as `OUTPUT`.

### Scoring

SPF 20 · DMARC 25 · DKIM 15 · MX 5 · MTA-STS 10 · TLS-RPT 5 · DNSSEC 10 · CAA 5 · BIMI 5 points. Checks that do not apply (e.g. DKIM for a domain that does not handle mail) are excluded and the score is normalized to 100. Grades: A ≥ 90, B ≥ 75, C ≥ 60, D ≥ 40, F < 40.

### Pricing

**Free during the launch period** – you only pay Apify platform usage, which is a fraction of a cent per 100 domains (no proxies or browsers are used). A small pay-per-domain price is planned for 2027; any change is announced in Apify Console in advance. Invalid inputs and failed lookups will always be free.

### Limitations

- **DKIM selectors cannot be listed via DNS.** If your provider uses a custom selector, add it to *Extra DKIM selectors*; otherwise "no DKIM found" means "not found on common selectors".
- `bulkSenderReady` is `true` (SPF, DKIM and DMARC verified), `false` (a requirement fails) or `null` (SPF and DMARC pass, but no DKIM key was found on common selectors – add your selector to confirm).
- Domains that publish a wildcard DKIM record (common for domains that never send mail) are reported once as `dkim.wildcard`, not per selector.
- Results reflect public DNS at the time of the run (via Cloudflare or Google DNS-over-HTTPS, with automatic fallback between the two).
- **A failed lookup is never reported as a missing record.** If MX, SPF or DMARC cannot be looked up even after a retry, the domain is returned with an `error` and is not charged. Other checks that fail are listed in `uncheckedChecks` and left out of the score.
- The Actor only reads **public DNS records and the public MTA-STS policy file**. It does not send e-mail or scan servers.

### FAQ

**Can I check subdomains?** Yes. DMARC falls back to the organizational domain like real mail receivers do.

**Is this legal?** The Actor reads public DNS records that every mail server reads when delivering e-mail. It does not collect personal data.

**Czech output?** Set `language` to `cs` for Czech finding texts and a Czech client report.

### Support

Built and maintained by VEmKa (Czech Republic, EU). Questions or ideas? Open an issue on the Issues tab – support in English and Czech.

# Actor input Schema

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

Domains to check. You can paste domains, website URLs or e-mail addresses – they are normalized and de-duplicated automatically. Invalid entries are skipped for free.

## `language` (type: `string`):

Language of finding titles, fix recommendations and the client report.

## `clientReport` (type: `boolean`):

Also create a print-ready report for every domain: grade, three key questions in plain language, all checks and prioritised fixes with business impact. Open it in a browser and print or save it as PDF. The link is added to each result as reportUrl.

## `reportBranding` (type: `string`):

Neutral: no logo and a small credit line. Your brand: your company name or logo and your contact, with no mention of this Actor.

## `reportCompanyName` (type: `string`):

Shown in the report header when no logo is given.

## `reportLogoUrl` (type: `string`):

https:// link to your logo (PNG or SVG), shown in the report header. The image is linked, not uploaded.

## `reportContact` (type: `string`):

For example: help@youragency.com · youragency.com. Shown in a "Want this fixed?" box at the end of the report.

## `checks` (type: `object`):

Turn individual checks off by setting them to false, e.g. {"bimi": false}. Available: spf, dmarc, dkim, mtaSts, tlsRpt, bimi, dnssec, caa. All are on by default.

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

DKIM keys can only be found if the selector name is known. ~30 common selectors are tested automatically; add your own here (e.g. "s2024").

## `monitorMode` (type: `boolean`):

Remember the result of each domain and mark what changed since the previous run (field "changes"). Ideal with a weekly schedule.

## `onlyChanges` (type: `boolean`):

With monitor mode on, push only new or changed domains to the dataset.

## `monitorStoreName` (type: `string`):

Named key-value store that keeps monitor state. Use different names for different domain lists.

## `dnsResolver` (type: `string`):

Public resolver used for all DNS lookups.

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

How many domains are checked in parallel.

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

Safety limit for the number of domains processed in one run.

## Actor input object example

```json
{
  "domains": [
    "apify.com",
    "example.com"
  ],
  "language": "en",
  "clientReport": false,
  "reportBranding": "neutral",
  "checks": {
    "spf": true,
    "dmarc": true,
    "dkim": true,
    "mtaSts": true,
    "tlsRpt": true,
    "bimi": true,
    "dnssec": true,
    "caa": true
  },
  "monitorMode": false,
  "onlyChanges": false,
  "monitorStoreName": "email-security-monitor",
  "dnsResolver": "cloudflare",
  "maxConcurrency": 10,
  "maxDomains": 10000
}
```

# Actor output Schema

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

Per-domain results in the Overview table view.

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

Counts per grade, failures, invalid inputs and changed domains.

# 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",
        "example.com"
    ],
    "checks": {
        "spf": true,
        "dmarc": true,
        "dkim": true,
        "mtaSts": true,
        "tlsRpt": true,
        "bimi": true,
        "dnssec": true,
        "caa": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("vemka/email-domain-security-audit").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",
        "example.com",
    ],
    "checks": {
        "spf": True,
        "dmarc": True,
        "dkim": True,
        "mtaSts": True,
        "tlsRpt": True,
        "bimi": True,
        "dnssec": True,
        "caa": True,
    },
}

# Run the Actor and wait for it to finish
run = client.actor("vemka/email-domain-security-audit").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",
    "example.com"
  ],
  "checks": {
    "spf": true,
    "dmarc": true,
    "dkim": true,
    "mtaSts": true,
    "tlsRpt": true,
    "bimi": true,
    "dnssec": true,
    "caa": true
  }
}' |
apify call vemka/email-domain-security-audit --silent --output-dataset

```

## MCP server setup

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

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/oAiDovPp4Wp9QPnuP/builds/tdW7V7ubjFCciALJl/openapi.json
