# Email Verifier — Bulk SMTP & MX Validation, Catch-all (`bovi/email-verifier`) Actor

Verify email deliverability without sending mail. Syntax, MX lookup, SMTP probe, catch-all detection, disposable & role-account flags. Score 0-100 per email.

- **URL**: https://apify.com/bovi/email-verifier.md
- **Developed by:** [Vitalii Bondarev](https://apify.com/bovi) (community)
- **Categories:** Lead generation, Business
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.87 / 1,000 email-verifications

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#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

## Email Verifier — Bulk SMTP & MX Validation, Catch-all Detection

Verify email deliverability in bulk without ever sending a single message.
Feed it a list of email addresses and get back a score, status, and detailed flags
for each one — whether you're cleaning a B2B lead list, validating a signup form,
or scrubbing a cold-outreach database before you burn your sender reputation.

### What it does

The actor runs each email through six verification layers in sequence:

#### Layer 1 — Syntax

Validates the address against a strict RFC-compliant regex. Catches typos, missing
`@` signs, double-dots, overly long local-parts, and other malformed addresses before
any network round-trip. Invalid syntax → `undeliverable` immediately.

#### Layer 2 — Disposable domain detection

Checks the email's domain against a bundled blocklist of ~300 known temporary/throwaway
mail providers: Mailinator, YOPmail, Guerrilla Mail, 10MinuteMail, Trashmail, and hundreds
more. Disposable emails are flagged with `is_disposable: true`. They may still be
syntactically valid and even have MX records — you decide whether to reject them.

#### Layer 3 — Role account detection

Identifies generic role-based addresses that are unlikely to belong to an individual:
`info@`, `support@`, `admin@`, `noreply@`, `sales@`, `webmaster@`, `postmaster@`,
`abuse@`, and ~40 more patterns. Role accounts are flagged `is_role: true`. They deliver
mail (often to a shared inbox), but bounce rates and engagement are typically low —
useful signal for lead scoring.

#### Layer 4 — MX lookup (DNS)

Queries the domain's DNS for MX records via dnspython. If no MX record exists, falls back
to an A-record check (some small domains skip MX). No MX + no A record → domain can't
receive mail → `undeliverable`. The highest-priority MX host is returned in `mx_host`.

#### Layer 5 — SMTP probe (no mail sent)

Connects to the top MX host on port 25 and runs:

```
HELO verify.example.com
MAIL FROM: <verify@verify.example.com>
RCPT TO: <the-email-being-checked>
```

The connection is closed immediately after reading the response code — no `DATA` command,
no message ever delivered. Response codes:

- `250` → mail server accepts the address → `deliverable`
- `5xx` → permanent rejection → `undeliverable`
- `4xx` → temporary failure / greylisting → `risky`

#### Layer 6 — Catch-all detection

After a `250` response, the actor sends a second RCPT to a randomly-generated
non-existent address on the same domain (e.g. `xk7m2q9r4j@domain.com`). If that
is also accepted, the domain is a catch-all — it accepts mail for any local-part
regardless of whether the mailbox exists. Catch-all addresses get `is_catch_all: true`
and `status: risky` because we can't confirm the specific mailbox exists.

***

### Output fields

| Field | Type | Description |
|---|---|---|
| `email` | string | Normalized (lowercased) email address |
| `status` | string | `deliverable` / `undeliverable` / `risky` / `unknown` |
| `score` | integer | Deliverability confidence 0–100 |
| `syntax_valid` | boolean | Passes RFC syntax check |
| `is_disposable` | boolean | Known throwaway/temp-mail domain |
| `is_role` | boolean | Generic role address (info@, support@, …) |
| `mx_found` | boolean | Domain has a working mail server |
| `mx_host` | string | Highest-priority MX hostname |
| `smtp_code` | integer | Raw SMTP response code (null if unreachable) |
| `smtp_message` | string | Raw SMTP response message |
| `is_catch_all` | boolean | Domain accepts mail for any address (null = unknown) |
| `checked_at` | string | ISO 8601 UTC timestamp |

#### Status values

- **deliverable** — syntax OK, MX found, SMTP accepted the address, domain is not catch-all
- **undeliverable** — syntax invalid, no MX record, or SMTP permanently rejected the address
- **risky** — domain is catch-all (can't confirm the specific mailbox) OR SMTP returned 4xx (greylisting)
- **unknown** — SMTP unreachable (port-25 blocked or timeout) and domain is not a known big provider

#### Score (0–100)

| Score | Meaning |
|---|---|
| 100 | SMTP 250 + not catch-all → high confidence deliverable |
| 80 | Known provider (Gmail/Outlook/…), MX found, port-25 unreachable → heuristic |
| 60 | MX found, SMTP unreachable, unknown domain → can't confirm |
| 35–40 | Catch-all or greylisting → deliverable but risky |
| 0 | Syntax fail, no MX, or hard SMTP rejection |

***

### Port-25 limitation (important)

**Many cloud providers (AWS, GCP, Azure) block outbound TCP port 25 by default.**
Apify's cloud infrastructure may or may not have port-25 open depending on the
instance type and region.

When port-25 is blocked, the actor handles it gracefully:

1. The SMTP probe returns `smtp_code: null` and marks the connection as unreachable
2. For well-known providers (Gmail, Outlook, Yahoo, iCloud, Proton, etc.) the actor
   falls back to a heuristic: if MX exists, status = `deliverable` at score 80
3. For all other domains: status = `unknown` at score 60
4. You can use `skipSmtp: true` to skip SMTP entirely and run MX-only mode

The syntax, disposable, role, and MX layers work correctly regardless of port-25 access.
On Apify infrastructure, port-25 availability may vary — test with `skipSmtp: false`
first; if you see many `unknown` results, the port is likely blocked on that instance.

***

### Accuracy notes

- **B2B domains**: SMTP probes work well; most corporate mail servers respond reliably
- **Gmail / Microsoft 365**: Block SMTP probes from cloud IPs. The heuristic gives score 80
- **Catch-all domains**: Common in B2B (many companies accept all mail). `risky` is correct —
  the address format is valid but the mailbox may not exist
- **Greylisting**: Some servers return `451` on first contact and accept on retry.
  The actor marks these `risky` (score 35); re-verify later or send anyway and watch bounce rates
- **False negatives**: SMTP probing is not 100% reliable. Servers that accept `250` may still
  bounce silently later. Combined with catch-all detection this is rare but possible
- **Rate limits**: Running at high concurrency against one domain may trigger rate-limiting.
  The default concurrency of 10 is conservative; reduce if you see many `risky` 4xx responses

***

### Pricing

**Pay-per-result**: charged once per verified email (`email-verification` event).
You are only charged for emails that complete the verification pipeline — not for errors.

***

### Example input

```json
{
  "emails": [
    "cto@openai.com",
    "noreply@github.com",
    "throwaway@mailinator.com",
    "test@nonexistent-xyz-domain.com",
    "info@stripe.com"
  ],
  "concurrency": 10,
  "smtpTimeoutSecs": 10,
  "skipSmtp": false
}
```

Also accepts objects with an `email` key — useful when piping from a lead-gen actor:

```json
{
  "emails": [
    {"email": "cto@openai.com", "name": "Sam Altman"},
    {"email": "info@stripe.com", "company": "Stripe"}
  ]
}
```

# Actor input Schema

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

List of email addresses to verify. Each entry is either a plain email string (e.g. 'user@example.com') or an object with an 'email' key. Max 10,000 per run.

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

Number of emails to verify in parallel. Higher = faster but may trigger rate-limits on target mail servers. Recommended: 5–20.

## `smtpTimeoutSecs` (type: `integer`):

Timeout for each SMTP connection attempt. Increase if many unknown results on slow mail servers.

## `skipSmtp` (type: `boolean`):

Skip the SMTP verification step and rely on MX lookup only. Faster but less accurate — use when running from a network that blocks outbound port 25.

## Actor input object example

```json
{
  "emails": [
    "test@gmail.com",
    "info@example.com",
    "user@mailinator.com"
  ],
  "concurrency": 10,
  "smtpTimeoutSecs": 10,
  "skipSmtp": false
}
```

# Actor output Schema

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

Dataset containing Email Verifier records (email, status, score, syntax\_valid, is\_disposable, is\_role, mx\_found, mx\_host, smtp\_code, is\_catch\_all, checked\_at).

# 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": [
        "test@gmail.com",
        "info@example.com",
        "user@mailinator.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("bovi/email-verifier").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": [
        "test@gmail.com",
        "info@example.com",
        "user@mailinator.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("bovi/email-verifier").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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": [
    "test@gmail.com",
    "info@example.com",
    "user@mailinator.com"
  ]
}' |
apify call bovi/email-verifier --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=bovi/email-verifier",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/Zbaas8lOx7qQ1Nazc/builds/SWUJ3y6ejNnakdkXC/openapi.json
