# DNS Lookup – Bulk DNS Records, MX, SPF & DMARC Checker (`gazidev/bulk-dns-lookup`) Actor

Bulk DNS lookup for thousands of domains: A, AAAA, MX, NS, TXT, CNAME, SOA, CAA records, SPF checker with 10-lookup limit, DMARC policy & rua, DKIM selectors, MTA-STS, BIMI, reverse DNS, change monitoring. Pick resolver: 1.1.1.1, 8.8.8.8 or custom. $1 per 1,000 domains.

- **URL**: https://apify.com/gazidev/bulk-dns-lookup.md
- **Developed by:** [Cemal Atakli](https://apify.com/gazidev) (community)
- **Categories:** Developer tools, SEO tools
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

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

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

## DNS Lookup – Bulk DNS Records, MX, SPF & DMARC Checker

**Bulk DNS lookup for thousands of domains in one run.** Paste a list of domains, URLs or email addresses and get one clean row per domain with **A, AAAA, MX, NS, TXT, CNAME, SOA and CAA records**. Each row also includes a full **SPF checker** (mechanisms, includes, recursive DNS-lookup count with the 10-lookup limit warning), **DMARC policy and rua**, **DKIM selector discovery**, **MTA-STS, TLS-RPT and BIMI**, the detected **email provider**, optional **reverse DNS (PTR)**, and a **monitor mode** that reports only the domains whose DNS changed.

No API key and no proxies. You choose the resolver: **Cloudflare 1.1.1.1, Google 8.8.8.8, Quad9, the system resolver or your own DNS server**. **$1 per 1,000 domains**, everything included.

### What does DNS Lookup do?

| Feature | Details |
|---|---|
| DNS records | A, AAAA, MX, NS, TXT, CNAME, SOA, CAA by default. SRV, DS, DNSKEY, HTTPS, SVCB, NAPTR, TLSA and PTR are optional. TTL for every record type. |
| MX lookup | Mail servers sorted by priority, plus the detected provider: Google Workspace, Microsoft 365, Zoho, Proofpoint, Mimecast, Null MX and about 40 more. |
| SPF checker | Parsed mechanisms (ip4, ip6, a, mx, include, exists, ptr), the `all` policy, `redirect`, a **recursive DNS-lookup count** (RFC 7208 limit of 10), void lookups, broken includes, multiple-record and `+all` warnings |
| DMARC checker | Policy `p`, subdomain policy `sp`, `pct`, `rua`/`ruf` report addresses, `adkim`/`aspf` alignment, `fo`, and fallback to the organizational domain for subdomains |
| DKIM | Probes about 29 common selectors (google, selector1/2, k1–k3, s1/s2, default, mandrill, amazonses…) plus your own selectors. Returns the key type, approximate key size and whether the key is revoked. |
| MTA-STS / TLS-RPT / BIMI | Presence, record, policy id, report address, BIMI logo and VMC URLs |
| Reverse DNS | PTR records of up to 5 IPv4 addresses per domain |
| Issues | Ready-made flags such as `spf_too_many_lookups`, `missing_dmarc`, `dmarc_policy_none`, `dkim_not_found_common_selectors`, `no_mx`, `no_caa`, `nxdomain` |
| Monitor mode | Compares each domain with the previous run and outputs only new or changed domains, with `added`/`removed` values |
| Input cleanup | URLs become host names, emails become domains, IDNs become punycode. Duplicates are removed, and invalid lines are reported for free. |

### Use cases

- **Email deliverability audits:** check SPF, DMARC and DKIM for every client or sending domain before a campaign.
- **Lead enrichment:** find which leads use Google Workspace or Microsoft 365, or sit behind a Proofpoint or Mimecast gateway.
- **DNS change monitoring:** schedule a daily run with *Monitor mode* and get alerts when MX, NS, A or TXT records change. This catches domain hijacks, migrations and expired zones.
- **Security and compliance:** find domains without DMARC enforcement, SPF records over the 10-lookup limit, missing CAA, or dangling CNAMEs.
- **Domain portfolio and migration checks:** verify that thousands of domains point to the right IPs and name servers after a move.
- **Resolver comparison:** run the same list against 1.1.1.1, 8.8.8.8 or your internal DNS server.

### Input example

```json
{
  "domains": ["apify.com", "https://www.github.com/apify", "jane@gmail.com"],
  "recordTypes": ["A", "AAAA", "MX", "NS", "TXT", "CNAME", "SOA", "CAA"],
  "resolver": "cloudflare",
  "emailSecurity": true,
  "checkDkim": true,
  "reverseDns": false,
  "onlyChanges": false
}
```

You can also paste a long list or a whole CSV into **Bulk list**, or give a **file URL** (TXT or CSV, for example a published Google Sheet).

### Output example

One row per domain. The flat columns work well in Excel or Google Sheets, and full details are in nested objects. Example (shortened):

```json
{
  "input": "apify.com",
  "domain": "apify.com",
  "rootDomain": "apify.com",
  "status": "OK",
  "ipv4": "3.160.57.105, 3.160.57.31, 3.160.57.35, 3.160.57.84",
  "mxHosts": "1 aspmx.l.google.com, 5 alt1.aspmx.l.google.com, ...",
  "nameservers": "ns-1225.awsdns-25.org, ns-1928.awsdns-49.co.uk, ...",
  "emailProvider": "Google Workspace",
  "spf": "-all (fail)",
  "spfLookups": 10,
  "dmarcPolicy": "reject",
  "dmarcRua": "mailto:dmarc-reports@apify.com",
  "dkimSelectors": "google",
  "mtaSts": true,
  "bimi": true,
  "issues": [],
  "records": { "A": ["3.160.57.105", "..."], "MX": [{ "priority": 1, "host": "aspmx.l.google.com" }], "SOA": [{ "primaryNs": "ns-839.awsdns-40.net", "serial": 1 }], "CAA": [{ "flags": 0, "tag": "issue", "value": "letsencrypt.org" }] },
  "ttls": { "A": 60, "MX": 86400, "NS": 172800 },
  "emailSecurity": {
    "spf": { "record": "v=spf1 a mx include:_spf.google.com include:mailgun.org ... -all", "dnsLookupCount": 10, "lookupLimitExceeded": false, "includes": ["_spf.google.com", "mailgun.org"], "warnings": [] },
    "dmarc": { "policy": "reject", "subdomainPolicy": "reject", "pct": 100, "rua": ["mailto:dmarc-reports@apify.com"] },
    "dkim": [{ "selector": "google", "keyType": "rsa", "keyBits": 2048, "revoked": false }],
    "mtaSts": { "found": true, "id": "29919a352ef9476a" },
    "bimi": { "found": true, "logoUrl": "https://apify.com/ext/apify-bimi-logo.svg" }
  },
  "resolver": "cloudflare",
  "checkedAt": "2026-10-12T13:57:56Z"
}
```

**Status values:** `OK` (records found), `NO_RECORDS` (the name exists but has none of the selected types), `NXDOMAIN` (the domain does not exist), `TIMEOUT` / `SERVFAIL` (the lookup failed; not charged), `INVALID_INPUT` (not charged).

In monitor mode, each row also has `changeType` (`new` / `changed`), `previousCheckedAt` and `changes`, for example:
`[{"field": "MX", "added": ["0 github-com.mail.protection.outlook.com"], "removed": ["10 old-mx.example.net"]}]`

### Pricing

Pay per event: **$0.001 per domain checked ($1 per 1,000 domains)** plus $0.00005 per run. Email security, DKIM, reverse DNS and monitoring are included. Failed lookups (timeouts, resolver errors), invalid inputs and duplicates are **free**. An `NXDOMAIN` answer is a real result, so it is charged. Set *Maximum cost per run* and the Actor stops cleanly when it is reached.

| Actor (Apify Store, Oct 2026) | Price per domain |
|---|---|
| **This Actor** | **$0.001**, no per-run fee |
| santamaria-automations/domain-whois-dns | $0.003 + $0.001 per run |
| automation-lab/domain-availability-checker | $0.0012–0.0023 + $0.035 per run |
| ryanclinton/dns-record-lookup | $0.003 |
| logiover/bulk-dns-records-lookup | $0.0035–0.005 |
| Email-security auditors (SPF/DMARC/DKIM) | $0.003–0.01 |

Speed: about 1,000 domains in 1.5–2 minutes with the default settings (measured: 1,000 hosts, 37,000 DNS queries, 78 s).

### FAQ

**Which resolver should I use?** Cloudflare 1.1.1.1 (the default) is fast and unfiltered. Use Google 8.8.8.8 or Quad9 to compare. Use *Custom* to query a specific server, such as your corporate DNS or a domain's authoritative name server.

**Why does an SPF record with only 4 includes show 12 lookups?** The 10-lookup limit counts nested includes too. The Actor follows every `include:` and `redirect=` recursively, the same way a receiving mail server does. Over 10 lookups gives an SPF `permerror`, and mail can fail authentication.

**Why "DKIM not found" when the domain signs its mail?** DNS has no way to list DKIM keys. The Actor checks about 29 common selectors. If your provider uses a custom selector, add it in *Extra DKIM selectors*.

**How does monitor mode work?** Turn on *Monitor mode* and schedule the Actor (for example, daily). The state is saved in a named key-value store in your account (`dns-lookup-monitor` by default). The first run outputs every domain as `new`. Later runs output only changed domains. TTLs and the SOA serial are ignored. For CDN sites whose IPs rotate, add `A` and `AAAA` to *Ignore these record types*. Unchanged domains are still looked up and charged. For watch lists over 50,000 domains, use 1 GB of memory.

**Do I get rows for subdomains like www.example.com?** Yes. The Actor queries exactly what you entered by default. Set *What to look up* to *Root domain* to turn every URL into its registrable domain. DMARC on a subdomain falls back to the parent domain's record, as receivers do.

**Is this legal?** Yes. DNS records are public by design, and the Actor only sends standard DNS queries.

### Use with AI agents / Apify MCP

AI agents (Claude, ChatGPT, Cursor and others) can call this Actor through the [Apify MCP server](https://mcp.apify.com). Add `bulk-dns-lookup` as a tool and ask, for example: *"Check SPF, DMARC and MX for these 200 client domains and list the ones without DMARC enforcement."* The flat fields (`spf`, `spfLookups`, `dmarcPolicy`, `emailProvider`, `issues`) are easy for an LLM to read. You can also call it through the Apify API, or from Make, Zapier or n8n with the Apify integration.

# Actor input Schema

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

Domains to look up, e.g. `example.com`. URLs (`https://www.example.com/page` → `www.example.com`) and email addresses (`jane@example.com` → `example.com`) are accepted. Duplicates are removed automatically.

## `bulkText` (type: `string`):

Paste a long list: one domain per line, comma/semicolon separated, or a whole CSV export (the first cell that looks like a domain, URL or email in each row is used).

## `sourceFileUrl` (type: `string`):

Public URL of a .txt or .csv file with domains (e.g. a Google Sheets 'Publish to web' CSV link).

## `recordTypes` (type: `array`):

DNS record types to query for each domain. PTR = reverse DNS of the A records (same as the 'Reverse DNS' switch).

## `resolver` (type: `string`):

Which DNS server answers the queries. Cloudflare (1.1.1.1) is fast and does not filter. Use 'Custom' to check what a specific DNS server returns (e.g. your company resolver or an authoritative name server).

## `customResolvers` (type: `array`):

Used only when DNS resolver = Custom. IPv4/IPv6 addresses, e.g. `208.67.222.222`.

## `lookupTarget` (type: `string`):

'As entered' queries the exact host name (www.example.com stays www.example.com). 'Root domain' strips subdomains (www.example.com → example.com), useful for lists of URLs.

## `emailSecurity` (type: `boolean`):

Parse SPF (mechanisms, includes, recursive DNS-lookup count with the RFC 7208 limit of 10), DMARC (policy, sp, pct, rua/ruf, alignment, org-domain fallback), MTA-STS, TLS-RPT and BIMI, plus the email provider detected from MX.

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

Probe ~29 common DKIM selectors (google, selector1/2, k1-k3, default, s1/s2, mandrill, amazonses...). DKIM keys cannot be listed, so 'none found' means 'not under common selectors'.

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

Your own selectors to check in addition to the common ones, e.g. `mailo`, `cm`, `scph0922`.

## `reverseDns` (type: `boolean`):

Look up the PTR record of each IPv4 address (max 5 per domain), e.g. to see the hosting provider.

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

Compares each domain with the previous run (stored in a named key-value store in your account) and outputs only domains whose records changed, with a list of added/removed values. Schedule the Actor daily to get DNS change alerts. TTLs and the SOA serial are ignored.

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

Named key-value store that keeps the last known state. Use different names for different watch lists.

## `monitorKey` (type: `string`):

Record key inside the store (lets one store hold several lists).

## `includeNewDomains` (type: `boolean`):

In monitor mode, also output domains that have no previous state (the first run outputs your whole list as the baseline).

## `monitorIgnoreTypes` (type: `array`):

E.g. `A` and `AAAA` for CDN sites whose IPs rotate on every query.

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

Stop after this many unique domains (0 = no limit).

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

How many domains are looked up at the same time.

## `timeoutSecs` (type: `integer`):

Maximum time per DNS query (one retry on timeout).

## Actor input object example

```json
{
  "domains": [
    "apify.com",
    "github.com",
    "bbc.co.uk"
  ],
  "recordTypes": [
    "A",
    "AAAA",
    "MX",
    "NS",
    "TXT",
    "CNAME",
    "SOA",
    "CAA"
  ],
  "resolver": "cloudflare",
  "lookupTarget": "asEntered",
  "emailSecurity": true,
  "checkDkim": true,
  "reverseDns": false,
  "onlyChanges": false,
  "monitorStoreName": "dns-lookup-monitor",
  "monitorKey": "SNAPSHOT",
  "includeNewDomains": true,
  "maxDomains": 0,
  "maxConcurrency": 50,
  "timeoutSecs": 8
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `email` (type: `string`):

No description

## `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",
        "github.com",
        "bbc.co.uk"
    ],
    "recordTypes": [
        "A",
        "AAAA",
        "MX",
        "NS",
        "TXT",
        "CNAME",
        "SOA",
        "CAA"
    ],
    "resolver": "cloudflare"
};

// Run the Actor and wait for it to finish
const run = await client.actor("gazidev/bulk-dns-lookup").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",
        "bbc.co.uk",
    ],
    "recordTypes": [
        "A",
        "AAAA",
        "MX",
        "NS",
        "TXT",
        "CNAME",
        "SOA",
        "CAA",
    ],
    "resolver": "cloudflare",
}

# Run the Actor and wait for it to finish
run = client.actor("gazidev/bulk-dns-lookup").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",
    "bbc.co.uk"
  ],
  "recordTypes": [
    "A",
    "AAAA",
    "MX",
    "NS",
    "TXT",
    "CNAME",
    "SOA",
    "CAA"
  ],
  "resolver": "cloudflare"
}' |
apify call gazidev/bulk-dns-lookup --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,gazidev/bulk-dns-lookup"
        }
    }
}
```

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/D9cxGIj8VxaqYp5BZ/builds/KfRlfSRLpdXTOKWUJ/openapi.json
