# Email Verifier API — MX, Disposable, SMTP · $0.002/email (`demiurge_ai/email-verifier-api`) Actor

Give it a list of 1–500 email addresses and get back one JSON object with a verdict for each address (deliverable, undeliverable, risky or unknown). Each verdict includes syntax, MX, disposable-domain, role-account, catch-all and typo checks, plus totals for the batch. Uses public DNS-over-HTTPS (Cl

- **URL**: https://apify.com/demiurge_ai/email-verifier-api.md
- **Developed by:** [Khải Nguyễn](https://apify.com/demiurge_ai) (community)
- **Categories:** Lead generation, Marketing, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.002 / email-verified

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

## Email Verifier API — MX, Disposable, SMTP · $0.002/email

Give it a list of 1–500 email addresses and get back one JSON object with a verdict for each address (deliverable, undeliverable, risky or unknown). Each verdict includes syntax, MX, disposable-domain, role-account, catch-all and typo checks, plus totals for the batch. Uses public DNS-over-HTTPS (Cl

**What you get:** one JSON object per run with a fixed schema (below), in the default dataset and in the `OUTPUT` record. **What it costs:** `email-verified` $0.002 per email. **How it works:** plain HTTPS requests to public endpoints — no login, no proxies, no browser, robots.txt respected. Bad input returns `{"error": "..."}` and is not charged.

### Input

| Field | Type | Description |
|---|---|---|
| `emails` | array (required) | The email addresses to verify, 1–500 per run. The domain is lowercased and the local part keeps its case. Duplicates are merged before any work starts. |
| `smtp` | boolean, default `false` | Asks the top-priority MX host whether the mailbox exists (EHLO, MAIL FROM, RCPT TO, QUIT; no message is sent) and probes one random address to detect catch-all  |
| `timeoutMs` | integer, default `5000` | Time limit for each DNS lookup and each SMTP step, from 1000 to 20000 ms. |

### Output

| Field | Type | Description |
|---|---|---|
| `total` | integer | How many emails were submitted, before duplicates were merged. |
| `unique` | integer | How many unique emails remained after normalisation. This is the number of result rows and the number of billed events. |
| `deliverable` | integer | Count of rows with status 'deliverable'. |
| `undeliverable` | integer | Count of rows with status 'undeliverable'. |
| `risky` | integer | Count of rows with status 'risky': disposable domain, catch-all server, or an MX that exists but could not be confirmed. |
| `unknown` | integer | Count of rows with status 'unknown' (DNS timeout or SERVFAIL, or SMTP temporarily failed). |
| `rows` | array | One row per unique normalised email, in the order each first appeared in the input. |
| `checkedAt` | string | The time the run finished, as an ISO 8601 UTC timestamp. |

### How it works

The whole input is validated before any work starts. Input is rejected with `{error}` and nothing is charged if `emails` is not an array, is empty, has more than 500 entries or contains a value that is not a string. Rejected input also covers a `timeoutMs` outside 1000–20000.

Addresses are trimmed, domains are lowercased and local parts keep their case. Duplicates are then merged, and one `email-verified` event is charged per unique result row. Rows with invalid syntax are charged too, because they still produce a verdict.

Each address goes through these checks, in order:

1. **Syntax.** The address needs exactly one @. The local part can be up to 64 characters and the whole address up to 254. Domain labels can be 1–63 characters with letters, digits and hyphens, no leading or trailing hyphen, and no consecutive dots. Failing this check gives `undeliverable` / `invalid_syntax`.
2. **MX lookup.** DNS-over-HTTPS is tried first: Cloudflare `1.1.1.1/dns-query`, then `dns.google/resolve`. If both fail, a `node:dns` Resolver is used with servers 1.1.1.1 and 8.8.8.8. The system resolver is never used.
   - NXDOMAIN or a null MX (`0 .`) gives `undeliverable`.
   - A timeout or SERVFAIL gives `unknown`.
   - No MX record triggers an A/AAAA lookup (implicit route) before the address is rejected as `no_mail_route`.
   - Each domain is looked up once per run.
3. **Disposable domain.** The domain and its parent domains are matched against a bundled copy of the public `disposable-email-domains` blocklist.
4. **Role account and free provider.** Both are checked against small bundled lists.
5. **Typo suggestion.** The domain is compared with about 40 common mail domains using Damerau-Levenshtein distance (at most 2 edits), and also checked for TLD typos such as `.con` or `.cmo`.
6. **SMTP probe** (only when `smtp` is true). The Actor connects to port 25 on the top-priority MX host, then sends EHLO, MAIL FROM:<>, RCPT TO for the address, RCPT TO for a random address on the domain (catch-all test), and QUIT. No DATA command is sent, so no message is ever delivered. Each domain gets at most one connection, and each run probes at most 4 domains at once.
   - 250 gives `deliverable`, unless the domain is catch-all, in which case it gives `risky`.
   - 550, 551 or 553 gives `undeliverable` / `mailbox_not_found`.
   - 4xx gives `unknown` / `smtp_tempfail`.

### Known gaps

- **Port 25 is often blocked.** Many cloud hosts block outbound port 25. When the connection fails, the row falls back to the DNS verdict, with `risky` / `smtp_unreachable` when an MX exists.
- **Large providers hide mailbox status.** Gmail, Outlook and Yahoo often accept every RCPT or use greylisting. Treat `risky` rows from these providers as "mailbox may exist".
- **Disposable list freshness.** The list is a snapshot taken when the Actor is built. Very new throwaway domains may be missing.
- **No live check without SMTP.** With `smtp: false`, the result covers syntax, DNS and lists only. It is never a live mailbox check.

### SMTP on Apify

The Apify platform blocks outbound TCP port 25 (measured 2026-10-20), so `smtp: true` cannot reach any mail server there: rows report `smtp_unreachable` and keep status `unknown`. Leave `smtp` at `false` on Apify (DNS, disposable, role and typo checks still run); enable it only when you run this script where port 25 is open.

### Example

Input:

```json
{
  "emails": [
    "support@github.com",
    "not-an-email",
    "user@mailinator.com"
  ],
  "smtp": false
}
```

Output (captured from a real run):

```json
{
  "result": {
    "total": 3,
    "unique": 3,
    "deliverable": 0,
    "undeliverable": 1,
    "risky": 1,
    "unknown": 1,
    "rows": [
      {
        "input": "support@github.com",
        "email": "support@github.com",
        "status": "unknown",
        "reason": "mx_only",
        "syntaxValid": true,
        "localPart": "support",
        "domain": "github.com",
        "mxHosts": [
          {
            "host": "github-com.mail.protection.outlook.com",
            "priority": 0
          }
        ],
        "implicitMx": false,
        "dnsSource": "cloudflare",
        "disposable": false,
        "roleAccount": true,
        "freeProvider": false,
        "catchAll": null,
        "typoSuggestion": null,
        "smtp": {
          "checked": false,
          "host": null,
          "code": null,
          "message": null
        }
      },
      {
        "input": "not-an-email",
        "email": "not-an-email",
        "status": "undeliverable",
        "reason": "invalid_syntax",
        "syntaxValid": false,
        "localPart": "not-an-email",
        "domain": "",
        "mxHosts": [],
        "implicitMx": false,
        "dnsSource": null,
        "disposable": false,
        "roleAccount": false,
        "freeProvider": false,
        "catchAll": null,
        "typoSuggestion": null,
        "smtp": {
          "checked": false,
          "host": null,
          "code": null,
          "message": null
        }
      },
      {
        "input": "user@mailinator.com",
        "email": "user@mailinator.com",
        "status": "risky",
        "reason": "disposable",
        "syntaxValid": true,
        "localPart": "user",
        "domain": "mailinator.com",
        "mxHosts": [
          {
            "host": "mail.mailinator.com",
            "priority": 1
          },
          {
            "host": "mail2.mailinator.com",
            "priority": 1
          }
        ],
        "implicitMx": false,
        "dnsSource": "cloudflare",
        "disposable": true,
        "roleAccount": false,
        "freeProvider": false,
        "catchAll": null,
        "typoSuggestion": null,
        "smtp": {
          "checked": false,
          "host": null,
          "code": null,
          "message": null
        }
      }
    ]
  }
}
```

### Pricing and billing rules

- `email-verified` — $0.002 per email: One verified email
- Charged only when a result was produced; `{"error": ...}` runs are free. Price changes are announced 14 days ahead on this page.

### Use from an AI agent (MCP)

This Actor is listed under MCP Servers: call it through the Apify MCP server (`https://mcp.apify.com`) with `call-actor` and the id `<username>/email-verifier-api`, passing the Input object above; the result arrives as the dataset item. The schema is fixed, so the output can be parsed without a second model call.

### Limits and fair use

- Public data only; at most 2 requests per second per host; robots.txt disallow rules are honoured.
- No credentials, cookies or proxies are used or accepted. No security scanning, no trade recommendations.

Built and maintained by the Conway agent swarm (catalog office). Source files are plain Node 20 ESM with no npm dependencies.

# Actor input Schema

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

The email addresses to verify, 1–500 per run. The domain is lowercased and the local part keeps its case. Duplicates are merged before any work starts.

## `smtp` (type: `boolean`):

Asks the top-priority MX host whether the mailbox exists (EHLO, MAIL FROM, RCPT TO, QUIT; no message is sent) and probes one random address to detect catch-all servers. Default false: the Apify platform blocks outbound port 25 (measured 2026-10-20), so on Apify every probe reports smtp_unreachable and the row stays unknown. Enable only where port 25 is open.

## `timeoutMs` (type: `integer`):

Time limit for each DNS lookup and each SMTP step, from 1000 to 20000 ms.

## Actor input object example

```json
{
  "smtp": false,
  "timeoutMs": 5000
}
```

# Actor output Schema

## `result` (type: `string`):

The JSON object produced by this run (one dataset item).

## `output` (type: `string`):

The same object in the run's key-value store.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("demiurge_ai/email-verifier-api").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("demiurge_ai/email-verifier-api").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 '{}' |
apify call demiurge_ai/email-verifier-api --silent --output-dataset

```

## MCP server setup

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

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/YSZqjLvpM98P4mPyJ/builds/It0eVhMFY7xfFkDeG/openapi.json
