# DMARC SPF MX Lookup API - Email DNS Security Check (`martin10291/email-dns-dmarc-spf-check`) Actor

No API key, no login. One call per domain list returns MX, SPF (parsed, DNS lookups against the limit of 10, includes resolved), DMARC policy and reports, DKIM keys, MTA-STS, TLS-RPT, BIMI, a score and a plain fix list. Monitor mode returns only domains whose records changed. A resolver outage says

- **URL**: https://apify.com/martin10291/email-dns-dmarc-spf-check.md
- **Developed by:** [Martin Petrov](https://apify.com/martin10291) (community)
- **Categories:** Developer tools, Automation, Business
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.70 / 1,000 results

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

## DMARC SPF MX Lookup API - Email DNS Security Check

**Problem:** you onboard customers, run a deliverability tool, manage client domains or audit mail security and need to know whether a domain's email DNS is set up correctly. The usual way is a handful of `dig` commands and manual reading of SPF includes.

**This API:** up to 50 domains per call, answered from public DNS in a few seconds. One row per domain with everything in one place:

- **MX:** every host with its priority and whether the host resolves to an IP. Null MX is recognised.
- **SPF:** the raw record, every mechanism parsed, the `all` qualifier, and the **DNS lookup count against the limit of 10**, with every `include:` and `redirect=` resolved recursively. Multiple SPF records, missing includes and loops are reported.
- **DMARC:** raw record, policy `p`, subdomain policy `sp`, `pct`, `rua` / `ruf`, `adkim` / `aspf`.
- **DKIM:** the selectors you give plus the common ones (google, selector1, selector2, default, k1, s1, mail): key found, key type, key size in bits, revoked keys.
- **MTA-STS:** the TXT record and, if present, the policy mode, max age and mx list (fetched from `https://mta-sts.<domain>/.well-known/mta-sts.txt`). **TLS-RPT** and **BIMI** records.
- **Score, grade and issues:** a 0-100 score, a letter grade and a plain `issues` list. Every issue has a severity and a concrete `fix`.

### Why it is different

- **An outage is never a false "no record".** DNS-over-HTTPS from Google DNS with Cloudflare DNS as fallback. If neither answers, the row says `unavailable` with the reason and is not counted as a missing record.
- **One call, whole picture.** No stitching of separate SPF, DMARC and MX calls.
- **Domain change monitor:** `onlyNew: true` + `monitorId` returns only domains whose records changed since the last run, with the list of changed sections.
- Domain level only: no mailbox checks, no SMTP connections, no personal data.

### Example (API)

```bash
curl -X POST "https://api.apify.com/v2/acts/martin10291~email-dns-dmarc-spf-check/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domains": ["github.com", "example.com"], "dkimSelectors": ["selector1"]}'
```

Output row (trimmed):

```json
{"domain": "google.com", "status": "ok", "score": 100, "grade": "A", "issueCount": 0,
 "mx": {"found": true, "nullMx": false, "records": [{"host": "smtp.google.com", "priority": 10, "resolves": true}]},
 "spf": {"found": true, "record": "v=spf1 include:_spf.google.com ~all", "qualifierAll": "softfail",
         "dnsLookups": 1, "lookupLimit": 10, "lookupLimitExceeded": false, "includes": ["_spf.google.com"], "errors": []},
 "dmarc": {"found": true, "record": "v=DMARC1; p=reject; rua=mailto:mailauth-reports@google.com", "policy": "reject", "pct": 100, "adkim": "r", "aspf": "r"},
 "dkim": {"found": false, "selectorsChecked": ["google", "selector1", "selector2", "default", "k1", "s1", "mail"], "keys": []},
 "mtaSts": {"txtFound": true, "policyFetched": true, "mode": "enforce", "maxAge": 86400},
 "tlsRpt": {"found": true}, "bimi": {"found": false},
 "issues": [{"severity": "info", "area": "spf", "issue": "SPF ends with '~all' (softfail). This is common and acceptable when DMARC is enforced.", "fix": "Use '-all' once you are sure every sender is listed."}],
 "checkedAt": "2026-11-12T14:29:05Z"}
```

Issue severities: `high` (mail fails or can be spoofed), `medium`, `low`, `info` (hints; `info` does not lower the score and is not counted in `issueCount`).

### Schedule it / monitor domains

Run it daily or hourly with an Apify schedule and set `onlyNew: true` with a `monitorId` such as `my-customers`. The first run returns every domain (`change: first_check`); later runs return only domains where MX, SPF, DMARC, DKIM, MTA-STS, TLS-RPT or BIMI changed (`change: changed`, plus `changedFields`). Connect the dataset to a webhook, Slack, Zapier or Make through an Apify integration to get an alert when a customer's SPF or DMARC changes. A run that finds no change returns no rows and no charge.

### Use from agents (MCP)

Add the Apify MCP server (`https://mcp.apify.com?tools=martin10291/email-dns-dmarc-spf-check`) to Claude, Cursor or any MCP client and ask: "Check the email DNS of these domains and list the fixes: ...".

### Input

| Field | What it does |
|---|---|
| `domains` | Domains, up to 50 (bare names like example.com) |
| `domain` | Shortcut for a single domain |
| `dkimSelectors` | Your own DKIM selectors, checked besides the common ones |
| `onlyNew` / `monitorId` | Return only domains whose records changed since the last run with this Monitor ID |

### Honest limits

- **DKIM selectors are arbitrary names.** Only the selectors you pass and the common ones are checked. "No DKIM key found" for the common selectors is an `info` hint, not proof that DKIM is missing. Pass your selector in `dkimSelectors`.
- **DMARC is read at `_dmarc.<the domain you give>`.** Subdomains do not fall back to the organizational domain; pass the domain mail is sent from.
- **SPF macros** (`%{i}` and similar) are not expanded. Such an include counts as one lookup and is noted in `spf.notes`.
- The `mx` mechanism's own host lookups and the void-lookup limit are not part of the count; the count follows the 10-term limit of RFC 7208.
- The MTA-STS policy file is fetched only from `mta-sts.<domain>` and only if that host resolves to public addresses; redirects are not followed.
- DNS answers come from public resolvers and can be cached for a short time (the record's TTL).

### FAQ

**Pricing?** $1.00 / 1000 results (one row per domain, or per changed domain in monitor mode).
**Do I need a key?** No. Public DNS only.
**Does it send email or connect to mail servers?** No. DNS lookups and the MTA-STS policy file only.
**What does `unavailable` mean?** The DNS resolvers did not answer for that domain. Try again; it never means the domain has no records.

# Actor input Schema

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

Domains to check, up to 50 per call, bare names like example.com (no https://, no mailbox). The API answers 400 above 50 (the Actor checks the first 50). Each row shows your input and the checked (lowercased) domain; duplicates are checked once and listed in duplicateInputs. A malformed entry returns a row with status invalid_format and is not looked up. A subdomain without its own DMARC record gets the organisational domain's policy (dmarc.inheritedFrom).

## `domain` (type: `string`):

Optional: exactly one domain, e.g. example.com. It is never split: a value with spaces, commas, semicolons or new lines returns one invalid_format row. Use domains for a list.

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

Optional. Your own DKIM selector names (the part before .\_domainkey), up to 20 (the API answers 400 above 20 or for an invalid name), for example s1 or mimecast20240101. They are checked in addition to the common ones: google, selector1, selector2, default, k1, s1, mail. Only real DKIM records count (v=DKIM1 or a base64 p= key).

## `onlyNew` (type: `boolean`):

Return only domains whose MX, SPF, DMARC, DKIM, MTA-STS, TLS-RPT or BIMI records changed since the last run with this Monitor ID (the first run returns all). Use it on a schedule to watch your domains or your customers' domains.

## `monitorId` (type: `string`):

Name of the watch list for the changed-domains mode, e.g. my-domains. Each Monitor ID keeps its own memory.

## Actor input object example

```json
{
  "domains": [
    "google.com",
    "github.com",
    "example.com"
  ],
  "dkimSelectors": [
    "selector1"
  ],
  "onlyNew": false,
  "monitorId": "default"
}
```

# Actor output Schema

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

One row per domain: MX records, SPF (parsed, DNS lookups vs the limit of 10), DMARC, DKIM keys, MTA-STS, TLS-RPT, BIMI, score, grade and a list of issues with a fix for each. In monitor mode only domains whose records changed.

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

// Run the Actor and wait for it to finish
const run = await client.actor("martin10291/email-dns-dmarc-spf-check").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",
        "example.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("martin10291/email-dns-dmarc-spf-check").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",
    "example.com"
  ]
}' |
apify call martin10291/email-dns-dmarc-spf-check --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,martin10291/email-dns-dmarc-spf-check"
        }
    }
}
```

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/QZAumyP1SQPdBQHG0/builds/sR8lAROP0gawurJAH/openapi.json
