# Bulk Email Verifier: Syntax, MX, Disposable & Role Check (`arthursbuisness/bulk-email-verifier-mx-disposable`) Actor

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

## Pricing

$2.00 / 1,000 verified emails

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 Email Verifier: Syntax, MX, Disposable & Role Check

Clean an email list of up to **10,000 addresses per run** for a fraction of the usual price. Every address gets a syntax check, a live DNS check of its domain (does it exist, does it have MX mail servers, does it explicitly refuse mail), disposable-domain and role-account detection, a free-mail flag, a typo suggestion, a normalized form and a clear **verdict + 0–100 score** you can filter on.

This is a **DNS-level verifier**: it tells you whether the *domain* can receive mail and whether the address is worth sending to. It does **not** open an SMTP connection to the mail server, so it cannot confirm that the specific *mailbox* exists and it does not detect catch-all domains (see Limitations). For most list-cleaning jobs — removing junk, typos, dead domains, disposables and role accounts before an import or campaign — this catches the large majority of problem addresses at ~1/50 of the cost of a mailbox-level verifier.

### What you get

One dataset row per address:

| field | meaning |
|---|---|
| `email` | the input as given |
| `normalized` | cleaned address (`Name <x@y>` and `mailto:` stripped, domain lower-cased) |
| `canonical` | deduplication key: `+tags` removed, Gmail dots removed, googlemail → gmail |
| `local_part`, `domain`, `domain_ascii`, `tld` | parsed parts; internationalized domains converted to punycode |
| `verdict` | `deliverable-domain` · `risky` · `undeliverable` · `valid-syntax` (DNS not checked / lookup failed) |
| `score` | 0–100 confidence that the address is safe to send to |
| `reasons[]` | machine-readable reasons, e.g. `invalid_syntax:missing_or_multiple_at`, `domain_not_found`, `null_mx_domain_refuses_mail`, `no_mx_only_a_record`, `disposable_domain`, `role_account`, `possible_typo`, `free_provider` |
| `syntax_valid` | RFC 5322-style syntax check (length limits, allowed characters, dot rules, valid labels/TLD) |
| `domain_exists` | domain resolves in DNS (not NXDOMAIN) |
| `mx_present`, `mx_records[]` | MX mail servers with priority (sorted) |
| `mx_fallback_a` | no MX but an A/AAAA record exists (implicit MX — rarely accepts mail, marked risky) |
| `null_mx` | domain publishes a *null MX* (`0 .`) — it explicitly refuses email |
| `disposable` | domain (or its parent) is a known throw-away/temporary mail provider |
| `role_account` | generic mailbox such as `info@`, `admin@`, `support@`, `noreply@`, `sales@` (120+ patterns) |
| `free_provider` | consumer webmail (Gmail, Outlook, Yahoo, iCloud, Proton, GMX, Ziggo, Orange … 150+ domains) |
| `suggestion`, `suggested_domain` | likely intended address for typos such as `gmial.com`, `yahoo.con`, `hotmal.com` |
| `error` | only present when both DNS resolvers failed for that domain — such rows are **not billed** |

#### Verdict logic

| verdict | when |
|---|---|
| `undeliverable` | invalid syntax, domain does not exist, domain has null MX, or no MX and no A record |
| `risky` | domain accepts mail but the address is disposable, a role account, a probable typo of a popular provider, or only an A record exists |
| `deliverable-domain` | valid syntax, domain exists, MX present, not disposable/role/typo |
| `valid-syntax` | syntax OK but DNS not checked (`checkDns: false`) or DNS lookup failed |

Score: 20 (syntax) + 20 (domain exists) + 40 (MX) + 10 (not disposable) + 10 (not role), minus penalties for typo suspicion; `free_provider` is informational and does not lower the score.

### Input

| field | default | meaning |
|---|---|---|
| `emails` | — | list of addresses (up to 10,000). Duplicates are skipped by default. |
| `checkDns` | true | resolve domain + MX over DNS-over-HTTPS |
| `dedupe` | true | verify and bill each distinct address once |
| `refreshDisposableList` | true | fetch the latest open-source disposable-domain list (≈8,000 domains) at run start; falls back to the build-time copy or an embedded core list |
| `extraDisposableDomains` | \[] | your own domains to flag as disposable |
| `concurrency` | 10 | parallel DNS lookups (results cached per domain) |
| `timeoutSecs` | 10 | per-lookup timeout |
| `maxItems` | 10000 | cost cap |

Example input:

```json
{ "emails": ["john.doe@gmail.com", "info@shopify.com", "someone@mailinator.com", "user@gmial.com", "not-an-email"] }
```

Example rows (abridged):

```json
{"email":"john.doe@gmail.com","verdict":"deliverable-domain","score":100,"reasons":["free_provider"],"canonical":"johndoe@gmail.com","mx_present":true}
{"email":"info@shopify.com","verdict":"risky","score":80,"reasons":["role_account"],"role_account":true}
{"email":"someone@mailinator.com","verdict":"risky","score":60,"reasons":["disposable_domain"],"disposable":true}
{"email":"user@gmial.com","verdict":"risky","score":15,"reasons":["no_mx_only_a_record","disposable_domain","possible_typo"],"suggestion":"user@gmail.com"}
{"email":"not-an-email","verdict":"undeliverable","score":0,"reasons":["invalid_syntax:missing_or_multiple_at"]}
```

### Pricing

Pay per event: **$0.002 per verified email** ($2 per 1,000; 10,000 addresses ≈ $20). You are charged only for rows that were actually written to the dataset; rows with a DNS `error` are stored but not billed. Comparable mailbox-level verifiers on the Store charge $0.01–$0.10 per email. Typical run: 10,000 addresses in 2–5 minutes at 256 MB.

### Limitations (please read)

- **No mailbox-level SMTP check.** The actor never connects to port 25, so it cannot tell whether `john@company.com` exists as a mailbox, only that `company.com` accepts mail. Unknown mailboxes on valid domains will show as `deliverable-domain`.
- **Catch-all detection is not included** (it requires SMTP).
- Disposable and free-provider detection is list-based; new throw-away domains appear daily. Add your own via `extraDisposableDomains`.
- Typo suggestions are limited to ~50 popular consumer providers and are heuristics: the row keeps its real DNS result, the suggestion is advisory.
- DNS answers come from public resolvers (Google DNS, Cloudflare fallback). Temporary resolver failures produce `verdict: valid-syntax` with `error: dns_lookup_failed` (not billed) — re-run those rows.

No login, no proxies, no third-party API keys. Not affiliated with any email provider.

# Actor input Schema

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

Addresses to verify, one per line (up to 10,000 per run). 'Name <user@domain.com>' and mailto: forms are accepted and cleaned.

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

Look up the domain over DNS-over-HTTPS (Google, Cloudflare fallback): domain existence, MX records, null-MX. Turn off for a syntax/list-only pass.

## `dedupe` (type: `boolean`):

Verify (and bill) each distinct address only once, even if it appears several times in the input.

## `refreshDisposableList` (type: `boolean`):

Download the latest open-source disposable-email-domains blocklist (≈8,000 domains) at the start of the run. A build-time copy plus an embedded core list are always available as fallback.

## `extraDisposableDomains` (type: `array`):

Your own domains to treat as disposable (e.g. competitor or internal test domains).

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

Number of domains resolved in parallel (1–30). Lookups are cached per domain, so 10,000 Gmail addresses cost one lookup.

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

Per-lookup timeout for the DNS-over-HTTPS resolvers.

## `maxItems` (type: `integer`):

Stop after this many addresses (cost control).

## Actor input object example

```json
{
  "emails": [
    "john.doe@gmail.com",
    "info@shopify.com",
    "someone@mailinator.com",
    "user@gmial.com",
    "not-an-email"
  ],
  "checkDns": true,
  "dedupe": true,
  "refreshDisposableList": true,
  "concurrency": 10,
  "timeoutSecs": 10,
  "maxItems": 10000
}
```

# Actor output Schema

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

All items as JSON

## `resultsCsv` (type: `string`):

Same dataset as CSV — open in Excel/Sheets

# 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": [
        "john.doe@gmail.com",
        "info@shopify.com",
        "someone@mailinator.com",
        "user@gmial.com",
        "not-an-email"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("arthursbuisness/bulk-email-verifier-mx-disposable").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": [
        "john.doe@gmail.com",
        "info@shopify.com",
        "someone@mailinator.com",
        "user@gmial.com",
        "not-an-email",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("arthursbuisness/bulk-email-verifier-mx-disposable").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": [
    "john.doe@gmail.com",
    "info@shopify.com",
    "someone@mailinator.com",
    "user@gmial.com",
    "not-an-email"
  ]
}' |
apify call arthursbuisness/bulk-email-verifier-mx-disposable --silent --output-dataset

```

## MCP server setup

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

```

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/7SJkMZglYQzMI5rlb/builds/h0UZn7EGFHAJY30Ec/openapi.json
