# Bulk Email Verifier & MX Record Checker (DNS, $0.70/1K) (`wintersworks/bulk-email-verifier-dns`) Actor

Email validator and bulk verifier: syntax, MX record lookup, disposable and role detection, typo fixes, 0-100 score. DNS only, no SMTP probing. $0.70 per 1,000 emails.

- **URL**: https://apify.com/wintersworks/bulk-email-verifier-dns.md
- **Developed by:** [Winters Works](https://apify.com/wintersworks) (community)
- **Categories:** Lead generation, Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.70 / 1,000 email verifieds

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

## Bulk Email Verifier (DNS/MX, no SMTP ping)

Email validator and bulk email verifier for cleaning your own lists fast and cheaply. Paste emails, point to a CSV/text URL or an Apify dataset, and get one flat record per address with a **status** (valid / invalid / risky / unknown), a **0-100 score** and a plain-English **reason**.

**Keywords:** email validator, bulk email verifier, email validation API, email list cleaner, MX record checker, disposable email detector, bounce reduction, typo fix. A low-cost alternative to per-seat email validation tools for list hygiene.

It checks syntax, whether the domain exists, MX records, disposable domains, role-based addresses, free providers and common typos. It uses DNS only. **It never connects to mail servers or sends RCPT probes**, so it is safe, fast and cheap, and it is aimed at people cleaning lists they already own (signup forms, newsletter exports, CRM).

### What each check does

| Check | Detail |
|---|---|
| Syntax and normalization | RFC 5322 dot-atom rules, length limits, internationalized domains converted to punycode, domain lowercased, whitespace and `mailto:`/`Name <a@b.com>` wrappers stripped |
| Domain and MX | MX lookup with RFC 5321 implicit-MX fallback to A/AAAA; detects null MX (RFC 7505) and non-existent domains |
| Disposable | Matched against the open-source [disposable-email-domains](https://github.com/disposable-email-domains/disposable-email-domains) list (100k+ domains, wildcard subdomains included) |
| Role-based | info@, admin@, sales@, support@, noreply@ and ~50 more |
| Free provider | Gmail, Yahoo, Outlook, iCloud, Proton and thousands of others |
| Typo suggestion | gmial.com to gmail.com, hotmial.com to hotmail.com, `.con` to `.com`, etc. |
| SPF / DMARC (optional) | Whether the domain publishes SPF and DMARC records |

### Statuses

- **valid**: syntax OK and the domain accepts mail (score 90-100; a missing MX record lowers the score to 90).
- **risky**: probably deliverable but a problem is flagged: disposable domain, role-based address, or likely typo.
- **invalid**: bad syntax, domain does not exist, null MX, or no mail records at all.
- **unknown**: DNS lookup failed or timed out, or DNS checking was turned off.

### Output example

```json
{
  "input": "bob@gmial.com",
  "email": "bob@gmial.com",
  "status": "risky",
  "score": 35,
  "reason": "Possible typo, did you mean bob@gmail.com?",
  "suggestion": "bob@gmail.com",
  "domain": "gmial.com",
  "domainExists": true,
  "hasMx": true,
  "mxRecords": ["mx.example.net"],
  "isDisposable": false,
  "isRoleBased": false,
  "isFreeProvider": false,
  "hasSpf": null,
  "hasDmarc": null
}
```

### Output fields

| Field | Meaning |
|---|---|
| `input` | The string you submitted, unchanged |
| `email` | Normalized address (domain lowercased, punycode), or null if the syntax is invalid |
| `status` | valid, risky, invalid or unknown (see Statuses) |
| `score` | 0-100, higher is cleaner |
| `reason` | Plain-English explanation of the status |
| `riskType` | low, medium, high or unknown (mapping below) |
| `syntaxValid` | true if the address parses. `isValidSyntax` holds the same value and is kept for older integrations |
| `hasPlusTag` | true if the local part has a plus tag, like `jane+news@example.com` |
| `verifiedAt` | ISO 8601 UTC timestamp of the check |
| `localPart`, `domain` | The two halves of the address |
| `domainExists`, `hasMx`, `nullMx`, `mxRecords` | DNS results (null when DNS was off or failed) |
| `isDisposable`, `isRoleBased`, `isFreeProvider` | Flags from the optional checks |
| `suggestion` | Corrected address when a typo is likely |
| `hasSpf`, `hasDmarc` | Only filled when `checkSpfDmarc` is on |

**How `riskType` is set.** It is derived only from the checks above, in this order:

- **high**: invalid syntax, domain does not exist, null MX or no mail records, or a disposable domain.
- **unknown**: the DNS lookup failed, or DNS checking is turned off.
- **medium**: role-based address, likely typo, no MX record (mail would fall back to the A/AAAA record), or neither SPF nor DMARC published (when that check is on).
- **low**: valid syntax, the domain accepts mail, and none of the flags above.

`riskType` is a summary of domain-level signals. It says nothing about whether the individual mailbox exists.

### Input

```json
{
  "emails": ["jane@company.com", "info@acme.io", "bob@gmial.com"],
  "sourceUrl": "https://example.com/my-export.csv",
  "checkSpfDmarc": true,
  "maxResults": 5000
}
```

You can combine `emails`, `datasetId` (with `emailField`) and `sourceUrl`. Duplicates are removed (case-insensitive) and are never charged. An empty-input run uses a small sample set so you can see the output format in seconds.

### Use cases

- Remove dead and typo'd addresses from a signup list before importing to your ESP, lowering bounce rates.
- Block or flag disposable signups in a CRM export.
- Separate personal from role-based addresses for B2B outreach to your own contacts.
- Pre-screen your own contact list before spending on an SMTP-level verifier.
- Chain after another Actor: pass its dataset ID and the email field name.

### What DNS-only verification can and cannot tell you

Honest version: this Actor tells you whether an address **could** receive mail, not whether the specific mailbox exists.

- It **can** catch: malformed addresses, dead or misspelled domains, domains that accept no mail, throwaway domains, shared inboxes and common typos. In typical signup lists this removes a large share of hard bounces.
- It **cannot** catch: `nobody123@gmail.com` where the domain is real but the mailbox is not, full mailboxes, catch-all behaviour, or spam traps. Only an SMTP handshake can sometimes answer that, and many providers (including Gmail and Microsoft) hide the answer anyway. We deliberately do not do SMTP probing: it is blocked on Apify (port 25), unreliable, and abusive at scale.
- A "valid" result means "no problem found at the domain level". Treat it as list hygiene, not a delivery guarantee.

### DNS layer vs SMTP verifiers

This Actor is a DNS layer, not an SMTP verifier.

- **What we check:** syntax, domain and MX records, disposable domains, role-based addresses, free providers, typos, and optionally SPF and DMARC.
- **What we do not check:** whether a specific mailbox exists, and whether a domain is catch-all. Both need an SMTP conversation with the recipient's mail server, which we never start.
- **When to use us:** first-pass cleaning of big lists at a low price, before you send, or before you pay for an SMTP verifier on the addresses that are left.
- **No SMTP probing** means nothing here touches the recipient's mail server. Only public DNS is queried.

### FAQ

**Is this allowed?** It only performs public DNS lookups and local list matching. Use it on lists you have the right to process.

**Do you store my emails?** No. Emails are processed only inside your run and written to your own run dataset. We do not store, sell or reuse them. Delete your dataset any time.

**How fast is it?** About 20 emails verified in parallel by default (up to 100), with DNS cached per domain, so a list with repeated domains finishes in seconds. 200 emails take about a second.

**How much does it cost?** Pay per event: $0.70 per 1,000 verified emails, with no charge for duplicates or for rows where the DNS lookup failed. See the Pricing table.

**Why is a Gmail address "valid" when the mailbox may not exist?** See the section above: DNS cannot see individual mailboxes.

**Is the disposable list up to date?** It is bundled at build time from the open list and refreshed when the Actor is rebuilt.

**Are the results estimates from a model?** No. They come from deterministic rule-based checks and DNS lookups.

Built by Winters Works. Feedback and feature requests are welcome via the Issues tab.

Disposable domain data: disposable-email-domains (CC0/MIT). Free provider data: free-email-domains (MIT).

### Pricing

Pay per event: $0.70 per 1,000 emails, flat, no seats and no subscription.

| Emails | Cost |
|---|---|
| 1,000 | $0.70 |
| 10,000 | $7 |
| 100,000 | $70 |
| 1,000,000 | $700 |

Duplicate addresses (case-insensitive) are dropped before verification and are never charged. Rows where the DNS lookup failed or timed out (status `unknown`) are returned so you can retry them, but they are not charged. You're never charged for failed or empty lookups, and the run's status message tells you why any input came back short (duplicates, DNS failures, maxResults). Apify's free plan includes $5 of monthly platform credit, which covers roughly 7,000 emails; platform compute usage also draws from that credit, so treat it as an estimate. Set a maximum cost per run and the Actor stops cleanly when it is reached.

### Is it legal? (public data only)

Yes, in the sense that it only reads public or official data: public DNS records only (no mailbox contact). It does not log in, bypass paywalls or access private data. You remain responsible for how you use the results, including privacy, copyright and marketing laws. Not affiliated with or endorsed by the data sources.

### Quick start and cost

Run it with the prefilled input to see sample output in seconds; the default run costs only a few cents at most. Then adjust the input and schedule it.

### Integrations and AI agents (MCP)

**Chain from a scraper.** Pass the dataset ID of any other Actor run in `datasetId`, and the field that holds the address in `emailField` (default `email`). If that field is missing, the first value containing an @ is used.

**Apify platform integrations.** Like every Actor on Apify, this one can be started from webhooks, Make, n8n and Zapier, or through the Apify API and the official JavaScript and Python clients. These are Apify platform features, not separate integrations built by us.

**MCP for AI agents.** Apify's hosted MCP server at https://mcp.apify.com can expose this Actor as a tool. Use this URL:

`https://mcp.apify.com?actors=wintersworks/bulk-email-verifier-dns`

Example config for Claude Desktop, Cursor or Claude Code (an MCP client that supports remote servers with headers):

```json
{
  "mcpServers": {
    "email-verifier": {
      "url": "https://mcp.apify.com?actors=wintersworks/bulk-email-verifier-dns",
      "headers": { "Authorization": "Bearer YOUR_APIFY_TOKEN" }
    }
  }
}
```

Two prompts to try:

- "Check these 200 addresses with the email verifier and give me only the ones with riskType low."
- "Verify the emails in dataset abc123 and list every disposable or role-based address."

**Python**

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("wintersworks/bulk-email-verifier-dns").call(
    run_input={"emails": ["jane@company.com", "info@acme.io"]}
)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["email"], item["status"], item["riskType"])
```

**JavaScript**

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const run = await client.actor('wintersworks/bulk-email-verifier-dns').call({
    emails: ['jane@company.com', 'info@acme.io'],
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map((i) => [i.email, i.status, i.riskType]));
```

Results land in the run's dataset (JSON, CSV, Excel).

### About

Built and maintained by Winters Works. Questions or feature requests: open an issue on the Actor page or email wintersworks.shop@gmail.com.

### More from Winters Works

- [Greenhouse, Lever & Ashby Jobs API](https://apify.com/wintersworks/ats-job-board-feeds): every open role from company careers boards on 5 ATS platforms, normalized into one dataset.
- [iOS App Store Reviews Scraper](https://apify.com/wintersworks/appstore-reviews-scraper): pull App Store reviews and ratings for any app, with date, rating and keyword filters.
- [Website Tech Stack & SEO Audit](https://apify.com/wintersworks/website-tech-stack-seo-audit): see what a list of sites runs on, plus SEO basics, security headers, SSL expiry and a 0-100 score.
- [ClinicalTrials.gov Scraper & Monitor](https://apify.com/wintersworks/clinical-trials-search-monitor): search trials by condition, drug, sponsor or location, and track what's new or updated between runs.

# Actor input Schema

## `emails` (type: `array`):

Emails to verify, one per line. Use this for your own lists (signup forms, CRM exports).

## `datasetId` (type: `string`):

ID of an Apify dataset. Reads the field named below, or the first value containing an @.

## `emailField` (type: `string`):

Field name that holds the email in the dataset.

## `sourceUrl` (type: `string`):

Public URL of a CSV or text file. Every email-like string in it is extracted.

## `checkDns` (type: `boolean`):

Looks up MX (falling back to A/AAAA) and detects null MX. Turn off for syntax-only checks.

## `checkDisposable` (type: `boolean`):

Matches against a bundled open list of disposable/temporary email domains.

## `checkRole` (type: `boolean`):

Flags info@, admin@, sales@, support@, noreply@ and similar.

## `checkFreeProvider` (type: `boolean`):

Flags Gmail, Yahoo, Outlook and other free mailbox providers.

## `checkTypos` (type: `boolean`):

Suggests corrections such as gmial.com to gmail.com.

## `checkSpfDmarc` (type: `boolean`):

Adds hasSpf and hasDmarc for each domain (two extra DNS lookups per domain).

## `maxResults` (type: `integer`):

Cap on emails processed (duplicates are skipped and never charged).

## `concurrency` (type: `integer`):

Parallel verifications.

## `dnsTimeoutSecs` (type: `integer`):

Per-query DNS timeout.

## Actor input object example

```json
{
  "emails": [
    "jane.doe@gmail.com",
    "info@example.com",
    "bob@gmial.com",
    "test@mailinator.com",
    "someone@this-domain-does-not-exist-xyz123.com",
    "not-an-email",
    "support@github.com"
  ],
  "emailField": "email",
  "checkDns": true,
  "checkDisposable": true,
  "checkRole": true,
  "checkFreeProvider": true,
  "checkTypos": true,
  "checkSpfDmarc": false,
  "maxResults": 1000,
  "concurrency": 20,
  "dnsTimeoutSecs": 5
}
```

# Actor output Schema

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

All result items from the run's default dataset. Each item includes status, score, riskType (low/medium/high/unknown), syntaxValid, hasPlusTag and verifiedAt.

# 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 = {
    "emails": [
        "jane.doe@gmail.com",
        "info@example.com",
        "bob@gmial.com",
        "test@mailinator.com",
        "someone@this-domain-does-not-exist-xyz123.com",
        "not-an-email",
        "support@github.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("wintersworks/bulk-email-verifier-dns").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 = { "emails": [
        "jane.doe@gmail.com",
        "info@example.com",
        "bob@gmial.com",
        "test@mailinator.com",
        "someone@this-domain-does-not-exist-xyz123.com",
        "not-an-email",
        "support@github.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("wintersworks/bulk-email-verifier-dns").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 '{
  "emails": [
    "jane.doe@gmail.com",
    "info@example.com",
    "bob@gmial.com",
    "test@mailinator.com",
    "someone@this-domain-does-not-exist-xyz123.com",
    "not-an-email",
    "support@github.com"
  ]
}' |
apify call wintersworks/bulk-email-verifier-dns --silent --output-dataset

```

## MCP server setup

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

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/MZAFQayGh3RYK8FII/builds/Psa62ALi87sw5k1OX/openapi.json
