# Email DNS Change Monitor — MX, SPF & DMARC (`longish_whistle/email-dns-change-monitor`) Actor

Check domain lists for email DNS changes, missing MX, multiple SPF records and published DMARC policies. Get before/after evidence and CSV. No emails sent or mailbox probing.

- **URL**: https://apify.com/longish\_whistle/email-dns-change-monitor.md
- **Developed by:** [Tender Delta](https://apify.com/longish_whistle) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Email DNS Change Monitor — MX, SPF & DMARC

**See exactly what changed in your domains' email settings after a migration or DNS edit.**

Check a domain list in one run and export published MX, SPF and direct DMARC records with DNS evidence. Compare against a previous run to identify changed mail routing or policy records without manually checking each domain.

### Who this helps

- IT teams verifying public DNS after an email-provider migration.
- Agencies maintaining several client domains and reviewing DNS changes.
- Operations teams that want source-backed records in a spreadsheet or automation workflow.

This is a published-record check, not an inbox-delivery guarantee or a full authentication evaluation. It sends **no email** and never contacts a mailbox over SMTP.

### Try two domains

```json
{"domains":["apify.com","example.com"],"maxDomains":2}
```

Two delivered domain checks cost **$0.008**, plus the displayed Actor start charge. No source account, paid resolver key or proxy subscription is needed. The example.com domain intentionally publishes a null MX; the tool reports this explicit no-mail configuration rather than treating it as a timeout.

### Read the findings correctly

| Finding | Meaning |
|---|---|
| explicit\_null\_mx | The domain explicitly advertises that it accepts no email |
| mx\_not\_published | No MX observed; SMTP's possible A/AAAA fallback is not evaluated |
| multiple\_spf\_records | More than one SPF policy observed at the queried domain |
| spf\_allows\_every\_sender | An explicit unqualified all or +all mechanism was observed |
| no\_direct\_dmarc\_record\_inheritance\_not\_checked | No policy at \_dmarc of the supplied domain; a parent policy may still apply |
| dmarc\_monitoring\_only | Direct p=none observed; this can be intentional |
| domain\_nxdomain | The resolver returned NXDOMAIN for the supplied name |

No arbitrary 0–100 security score is invented. TTL changes and DNS answer reordering do not create false configuration-change events. An SPF ~all policy is reported as a note, not automatically labeled broken.

### Compare before and after

Run once to save SNAPSHOT. On the next run select the previous run's key-value store in previousSnapshotStoreId, keep the domain list the same and optionally enable changesOnly. The changes array includes each changed field's before and after values. Your workflow must pass the preceding run's store ID each time; no automatic persistent global history or email alert is created.

The dataset contains one row per delivered domain: domain, mxRecords, spfRecords, dmarcRecords, dmarcPolicy, findings, notes, evidence, checkedAt and optional change fields. Evidence includes resolver URLs, DNS statuses and relevant answer records. Other unrelated domain-verification TXT values are excluded.

OUTPUT-CSV is UTF-8 and spreadsheet-formula-safe; JSON preserves original text. SUMMARY reports coverage and failures, DELIVERY records export completion, DIAGNOSTICS lists unchecked targets, and SNAPSHOT supports the next comparison. Source absence is distinct from network failure.

### Pricing and limits

**$4 per 1,000 delivered domain checks ($0.004 each)** plus the displayed Actor start charge. Platform usage is included; no monthly rental. Completed checks with missing records or NXDOMAIN are useful diagnostic results and are charged. Resolver HTTP failures, SERVFAIL, truncation or incomplete checks are unchecked diagnostics and are not charged as domain checks. Suppressed unchanged rows are not charged. The start charge may apply even when no result is delivered.

Up to 50 domains per input, 10 checked by default, three DNS queries per domain, an 8-second request timeout and an approximately 90-second work budget. Set Apify's maximum charge before starting. Snapshots retain up to 10,000 recent records. No parallel batch flood: three record queries per domain, then a short pause before the next domain.

### Scope and source

Uses Google's public DNS-over-HTTPS JSON API with client-subnet disclosure disabled. Results reflect the resolver's cache at check time, not a guarantee that DNS propagation has completed worldwide. Only the supplied name and its direct \_dmarc name are queried. No recursive SPF include evaluation, sender IP evaluation, DMARC organizational-domain fallback, alignment evaluation, DKIM signature/selector discovery, blocklist, WHOIS or mailbox verification. Findings are practical observations, not standards certification.

[Google DNS API documentation](https://developers.google.com/speed/public-dns/docs/doh/json), [SPF specification](https://www.rfc-editor.org/rfc/rfc7208), [Null MX specification](https://www.rfc-editor.org/rfc/rfc7505). Public resolver policies and availability apply. You control reuse of the public records.

Independent tool by **Tender Delta**. Use this Actor's Issues tab for a reproducible problem with your input and a redacted run link. Do not include credentials or private domain inventories.

# Actor input Schema

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

Public domain names only, without URLs or email addresses. Up to 50 domains.

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

Limits work and delivered results; inspect SUMMARY for coverage.

## `changesOnly` (type: `boolean`):

Needs your previous snapshot to suppress unchanged observations. New means not seen in that snapshot.

## `previousSnapshotStoreId` (type: `string`):

Optional previous run store containing SNAPSHOT. Your workflow must pass the preceding run defaultKeyValueStoreId on each run. Keep target lists and filters identical.

## Actor input object example

```json
{
  "domains": [
    "apify.com",
    "example.com"
  ],
  "maxDomains": 10,
  "changesOnly": false
}
```

# Actor output Schema

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

No description

## `csv` (type: `string`):

No description

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

No description

## `files` (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",
        "example.com"
    ],
    "maxDomains": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("longish_whistle/email-dns-change-monitor").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",
    ],
    "maxDomains": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("longish_whistle/email-dns-change-monitor").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"
  ],
  "maxDomains": 10
}' |
apify call longish_whistle/email-dns-change-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,longish_whistle/email-dns-change-monitor"
        }
    }
}

```

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/88gK8uexUkBQ5zntk/builds/PJNAGVk2MhfY4RtTQ/openapi.json
