# Bulk DNS Lookup, SPF DMARC DKIM Email Check (`thoob/dns-bulk-lookup`) Actor

Resolves A, AAAA, MX, NS, TXT, CNAME, and SOA records for many domains at once over DNS-over-HTTPS (Cloudflare, with Google fallback), and parses SPF, DMARC, and optional DKIM. Deterministic and fast. Billed only per domain that returns DNS data.

- **URL**: https://apify.com/thoob/dns-bulk-lookup.md
- **Developed by:** [Pono Data](https://apify.com/thoob) (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

$3.00 / 1,000 resolved domain or ips

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

## Bulk DNS Lookup (DNS-over-HTTPS)

Resolve DNS for a list of domains or IPs in one run, with a source URL on every
row you can open and check. Public DNS-over-HTTPS resolvers (Cloudflare by
default, Google as automatic fallback). One clean row per input.

### What you get for each domain

- Records: A, AAAA, MX, NS, TXT, CNAME, SOA, and CAA.
- Email authentication: SPF, DMARC with its policy, and DKIM for any selectors you supply.
- TXT verification tokens pulled out on their own (Google, Microsoft, Facebook, Stripe, DocuSign, and more), so domain-ownership signals are easy to read.
- An email-sending posture (strong, partial, mail-no-auth, or no-mail) read from the records found, including the RFC 7505 null-MX "this domain sends no mail" case.

Give it an IP address instead of a domain and it returns the reverse (PTR) hostname.

### Why this one

- Every value carries a `sourceUrl`: the exact DoH query that returned it. Open it and check. Nothing is guessed; records that are absent come back null, not invented.
- You pay only for an input that actually returns data. Non-existent or empty domains go to a free `rejected` dataset at no charge.
- No subscription. Run one domain or a hundred thousand; the price is per resolved domain either way.
- CAA records, the verification tokens, and the email posture are included, not a separate paid check.

### Input

- **Domains**: one per line. A full URL is reduced to its host. A bare IPv4 or IPv6 address runs a reverse (PTR) lookup.
- **Record types**: any of A, AAAA, MX, NS, TXT, CNAME, SOA, CAA.
- **Primary resolver**: Cloudflare or Google; the other is the automatic fallback.
- **Parse SPF and DMARC**: extract the email-auth records.
- **DKIM selectors**: optional; DKIM needs a selector to be discoverable.
- **Max delivered domains**: cap on billed rows (0 means no cap).

### Output

One row per input: `queryType` (forward or reverse), the record fields, `CAA`,
`txtVerifications`, `spf`, `dmarc`, `dmarcPolicy`, `dkim`, `emailPosture` with its
`hasMX` / `hasSPF` / `spfHardFail` / `hasDMARC` / `dmarcEnforced` booleans, `PTR`
for IP inputs, `resolver`, `dnsStatus`, and provenance (`sourceUrl`,
`retrievedAt`, `confidence`, `dataSource`).

### How it works

DNS-over-HTTPS is a public, standard protocol (RFC 8484). The actor reads only
what the resolver returns; absent records are null. A domain that returns no
records for any requested type (for example NXDOMAIN) is written to the free
`rejected` dataset and is not billed. Inputs are resolved concurrently, so a batch
of thousands stays fast.

### Billing

Pay per domain or IP that returns data. Empty, non-existent, and sanctioned inputs
cost nothing.

### Coverage

Global. Targets in any country are processed. The one exclusion is jurisdictions
under US sanctions (Cuba, Iran, North Korea, Syria, Russia, Belarus, Venezuela,
Myanmar, matched by country-code TLD), which are written to the free `rejected`
dataset and never billed.

### Opt out

A domain owner can ask us to skip their domain at https://ponodata.com/opt-out .
Suppressed domains are returned by nothing and never billed.

### Sample output

A real run resolving common domains (one row per domain, records abbreviated):

| domain | A | MX (top) | SPF | Email posture |
| --- | --- | --- | --- | --- |
| cloudflare.com | 104.16.133.229 | 5 mxa-canary.global.inbou… | v=spf1 ip4:199.15.212.0/22 ip4:17… | strong |
| google.com | 142.251.167.139 | 10 smtp.google.com. | v=spf1 include:\_spf.google.com ~a… | strong |
| github.com | 140.82.113.4 | 0 github-com.mail.protect… | v=spf1 ip4:192.30.252.0/22 includ… | strong |
| stripe.com | 198.202.176.41 | 10 aspmx.l.google.com. | v=spf1 ip4:198.2.180.60/32 ip4:13… | strong |

Every row carries a `sourceUrl` you can open to verify, for example `https://cloudflare-dns.com/dns-query?name=cloudflare.com&type=A`.

### Use cases

- Audit email deliverability across a portfolio: pull SPF, DMARC, and the email posture in one run to find domains that can be spoofed or that fail authentication.
- Vet a list of domains before outreach or onboarding: MX presence and the RFC 7505 null-MX flag tell you which domains actually accept mail.
- Confirm domain-ownership signals at scale: the TXT verification tokens (Google, Microsoft, Stripe, DocuSign, and more) are pulled onto their own field.
- Map infrastructure for a security review: A, AAAA, NS, CNAME, and reverse PTR for many hosts at once, each with a source URL you can re-query.

### FAQ

- Does it guess records that are missing? No. An absent record is null, and a domain that returns nothing goes to the free rejected dataset, unbilled.
- Can I check email authentication without requesting every record type? Yes. Turn on SPF and DMARC parsing and the actor fetches the TXT records the posture needs.
- What does the email posture mean? It reads the records found and reports strong, partial, mail-no-auth, or no-mail, including the null-MX "sends no mail" case.
- How am I billed? Per domain or IP that returns data; empty, non-existent, and sanctioned inputs cost nothing.

### See also

More clean, pay-only-for-results data tools from Pono Data:

- [Domain WHOIS via RDAP](https://apify.com/thoob/rdap-domain-lookup) - registration data, structured from RDAP
- [Bulk Email Validator](https://apify.com/thoob/email-validator) - syntax, MX, disposable, and role flags
- [Sitemap Extractor](https://apify.com/thoob/sitemap-extractor) - every URL from any sitemap

Full catalog: https://apify.com/thoob

# Actor input Schema

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

Domains or hostnames to resolve, one per line. A full URL is reduced to its host automatically. A bare IPv4 or IPv6 address runs a reverse (PTR) lookup instead.

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

Which record types to resolve for domains. Supported: A, AAAA, MX, NS, TXT, CNAME, SOA, CAA. An IP input always returns PTR.

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

Primary DNS-over-HTTPS resolver. The other is used as an automatic fallback on error.

## `parseEmailAuth` (type: `boolean`):

Extract the SPF record (from TXT) and the DMARC record and policy (from \_dmarc TXT).

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

DKIM cannot be discovered without a selector. Supply selectors (for example google, k1, selector1) to fetch selector.\_domainkey.<domain> TXT. Leave empty to skip DKIM.

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

Cap on delivered, billed rows. 0 means no cap. The platform spend cap is honored regardless.

## Actor input object example

```json
{
  "domains": [
    "cloudflare.com",
    "google.com",
    "8.8.8.8"
  ],
  "recordTypes": [
    "A",
    "AAAA",
    "MX",
    "NS",
    "TXT",
    "SOA",
    "CAA"
  ],
  "resolver": "cloudflare",
  "parseEmailAuth": true,
  "dkimSelectors": [],
  "maxDomains": 0
}
```

# Actor output Schema

## `records` (type: `string`):

One row per domain (forward) or IP (reverse), with records, verification tokens, and a derived email posture.

# 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": [
        "cloudflare.com",
        "google.com",
        "8.8.8.8"
    ],
    "recordTypes": [
        "A",
        "AAAA",
        "MX",
        "NS",
        "TXT",
        "SOA",
        "CAA"
    ],
    "dkimSelectors": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("thoob/dns-bulk-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": [
        "cloudflare.com",
        "google.com",
        "8.8.8.8",
    ],
    "recordTypes": [
        "A",
        "AAAA",
        "MX",
        "NS",
        "TXT",
        "SOA",
        "CAA",
    ],
    "dkimSelectors": [],
}

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,thoob/dns-bulk-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/7ALJfB3rABKBWXn56/builds/NX7JqninILFvWqqyO/openapi.json
