# Domain & Email DNS Checker: WHOIS/RDAP, SPF, DMARC, DKIM, MX (`veedel_digital/domain-check`) Actor

Bulk check domains: registrar, creation and expiry date via RDAP (WHOIS), nameservers, DNSSEC, plus email security with SPF, DMARC, DKIM and MX records. Flags spoofable domains and domains about to expire. No proxies, pay per domain.

- **URL**: https://apify.com/veedel_digital/domain-check.md
- **Developed by:** [Tim Tonat](https://apify.com/veedel_digital) (community)
- **Categories:** Developer tools, SEO tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 domains

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

### What does Domain & Email DNS Checker do?

This Actor **checks domains in bulk for registration data and email security**. Paste a list of domains, website URLs or email addresses and get **one row per domain** with:

- **Registration data via RDAP** (the modern WHOIS): registrar, creation date, **expiry date and days left**, status codes, nameservers and DNSSEC
- **Email security**: **SPF**, **DMARC** and **DKIM** records, parsed and checked
- **DNS records**: MX, A, AAAA, NS, TXT and CAA
- **Issue codes** and an **email security score from 0 to 100**

No proxies, no browser and no scraping of WHOIS websites: it queries public DNS resolvers and the official RDAP services of the registries.

### Why use this domain and DMARC checker?

- **Agencies and IT service providers**: audit all client domains at once and find the ones that can be spoofed (no SPF, no DMARC, `p=none`)
- **Since 2024 Gmail and Yahoo require SPF, DKIM and DMARC** for bulk senders. Find out which of your domains are not ready
- **Domain portfolio monitoring**: schedule a weekly run and catch domains that expire within 30 days
- **Sales and lead research**: see which mail provider a company uses (MX: Google Workspace, Microsoft 365, …) and its registrar
- **Security teams**: find lookalike or parked domains without MX and without SPF
- **Pay per domain**: invalid input and failed lookups are free

### What does it check?

| Issue code | Meaning |
|---|---|
| `NXDOMAIN` | The domain does not exist in DNS |
| `NO_MX` | No mail server (the domain can't receive email) |
| `SPF_MISSING` | No SPF record, anyone can send email in the domain's name |
| `SPF_MULTIPLE` | More than one SPF record, which makes SPF invalid |
| `SPF_PASS_ALL` | SPF ends with `+all` and allows every server on the internet |
| `SPF_NOT_STRICT` | SPF ends with `?all` or has no `all` at all |
| `SPF_TOO_MANY_LOOKUPS` | More than 10 DNS lookups in the SPF record (SPF fails with a permerror) |
| `DMARC_MISSING` | No DMARC record |
| `DMARC_POLICY_NONE` | DMARC is only monitoring (`p=none`), spoofed mail is still delivered |
| `DKIM_NOT_FOUND` | No DKIM key under the common selectors (see FAQ) |
| `DOMAIN_EXPIRES_SOON` | The domain expires in less than 30 days |

#### Email security score

The score is simple and transparent, maximum 100:

| Check | Points |
|---|---|
| Exactly one SPF record | 25 |
| SPF ends with `-all` (or `~all`) | 10 (5) |
| SPF needs at most 10 DNS lookups | 5 |
| DMARC record present | 25 |
| DMARC policy `reject` (or `quarantine`) | 20 (10) |
| DMARC reports enabled (`rua`) | 5 |
| DKIM key found | 10 |

### How to check domains in bulk

1. Click **Try for free**.
2. Paste your domains into **Domains**. URLs like `https://www.example.com/contact` and email addresses like `info@example.com` work too.
3. Click **Start**.
4. Open the **Overview** table, or download everything as JSON, CSV or Excel.

### How much does it cost?

This Actor uses **pay per event**: a fixed price **per checked domain**, and **platform usage is included**. The current price is shown in the **Pricing** tab. Invalid input and domains that fail because of DNS errors are not charged. A domain that does not exist (`NXDOMAIN`) is a valid result and is charged.

### Input

See the **Input** tab for all options.

```json
{
    "domains": ["example.com", "https://www.example.org/contact", "info@example.de"],
    "checkDkim": true
}
```

### Output

You can download the dataset in various formats such as JSON, HTML, CSV, or Excel.

```json
{
    "domain": "example.com",
    "exists": true,
    "issues": ["DMARC_POLICY_NONE"],
    "emailSecurityScore": 70,
    "mx": ["aspmx.l.google.com", "alt1.aspmx.l.google.com"],
    "spfRecord": "v=spf1 include:_spf.google.com ~all",
    "spfAll": "~all",
    "spfLookups": 1,
    "dmarcRecord": "v=DMARC1; p=none; rua=mailto:dmarc@example.com",
    "dmarcPolicy": "none",
    "dmarcRua": "mailto:dmarc@example.com",
    "dmarcInherited": false,
    "dkimSelectors": ["google"],
    "a": ["93.184.215.14"],
    "ns": ["ns1.example.net", "ns2.example.net"],
    "caa": ["critical 0 issue letsencrypt.org"],
    "rdapAvailable": true,
    "registeredDomain": "example.com",
    "registrar": "Example Registrar, Inc.",
    "createdAt": "2007-10-09T18:20:50Z",
    "expiresAt": "2028-10-09T18:20:50Z",
    "daysUntilExpiry": 725,
    "status": ["client transfer prohibited"],
    "nameservers": ["ns1.example.net", "ns2.example.net"],
    "dnssec": false,
    "error": null
}
```

### Related Actors

Combine it with the SEO Site Audit: check the technical health of a client's website and the email security of its domain in one go.

- [SEO Site Audit & Broken Link Checker](https://apify.com/veedel_digital/seo-audit): crawl a whole website and find on-page SEO issues and broken pages
- [PageSpeed Insights & Core Web Vitals Bulk Audit](https://apify.com/veedel_digital/pagespeed-audit): Lighthouse scores and real-user Core Web Vitals for many URLs
- [Structured Data, Schema.org & Open Graph Extractor](https://apify.com/veedel_digital/structured-data): extract JSON-LD, schema.org types and Open Graph tags from many pages
- [PDF to Text with OCR](https://apify.com/veedel_digital/pdf-to-text): extract text from PDFs in bulk, scanned pages included

### FAQ

#### Why is the expiry date missing for some domains?

Registration data comes from the official RDAP service of each registry. Some registries publish less: **DENIC (.de)** shows status, nameservers and DNSSEC, but no registrar and no expiry date. Some country registries (for example **.at** and **.eu**) offer no public RDAP service at all. Then `rdapAvailable` is `false` and all DNS and email checks still work.

#### Why does it say DKIM_NOT_FOUND although my domain signs with DKIM?

DKIM keys are stored under a *selector* that only the sending server knows (`selector._domainkey.example.com`). The Actor tries the common selectors of Google Workspace, Microsoft 365 and other providers. If your provider uses a custom selector, the key exists but can't be found from the outside. Treat this code as a hint, not as an error.

#### Does it follow SPF includes?

The lookup count covers the top level of the SPF record (`include`, `a`, `mx`, `ptr`, `exists`, `redirect`). Includes are not expanded, so the real number can be higher.

#### What about subdomains?

You can check subdomains like `mail.example.com`. The registration data is taken from the registered domain, and if the subdomain has no own DMARC record, the policy of the registered domain is used (`dmarcInherited: true`), just like mail servers do.

#### Something does not work

Please open an issue in the **Issues** tab with the domain and the run ID. You can also start the Actor programmatically from the **API** tab.

# Actor input Schema

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

Domains to check. You can also paste URLs or email addresses, the domain is extracted automatically.

## `checkDkim` (type: `boolean`):

Try common DKIM selectors (Google Workspace, Microsoft 365, and others). DKIM keys can only be found if the selector is known.

## Actor input object example

```json
{
  "domains": [
    "apify.com",
    "github.com",
    "denic.de"
  ],
  "checkDkim": true
}
```

# Actor output Schema

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

Dataset with all checked 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",
        "github.com",
        "denic.de"
    ]
};

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

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

```

## MCP server setup

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