# Bulk Email Verifier - SMTP Validation & Deliverability Check (`smiling_jubilance/smtp-email-verifier`) Actor

Bulk email verifier with SMTP validation and MX record lookup: RCPT probe, catch-all detection, disposable email detection, 8,870-domain blacklist. Built for email deliverability and list cleaning. Inconclusive unknown is never billed.

- **URL**: https://apify.com/smiling\_jubilance/smtp-email-verifier.md
- **Developed by:** [Lake Sky](https://apify.com/smiling_jubilance) (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

from $0.59 / 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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

### What is Bulk Email Verifier?

**Bulk Email Verifier** checks whether an email address can actually receive mail, and it does it at
the **SMTP level** — not with a regex, and not with a lookup table.

For every address it resolves the domain's MX records, opens a connection to the mail server and
issues a real `RCPT TO` probe. That is the same step a mail server performs right before it accepts
a message, which is why it is the only check that can tell you a mailbox truly exists.

Two extra guards make the verdict trustworthy:

- **Catch-all detection.** Many domains accept every address and bounce later. The Actor probes the
  domain with a random 20-character local part as a control. If the control address is accepted too,
  the domain is flagged `accept_all` and you are told the mailbox was **not** confirmed.
- **Disposable detection.** An 8,870-domain blocklist built from three public sources catches
  throwaway addresses before any network probe is made.

You get a **verdict per row** (`safeToSend` is the single field most users branch on), and you only
pay for rows that carry a verdict. Anything the Actor could not decide comes back as `unknown` and
is **free**.

### What can this Actor do?

- ✅ **Mailbox-level confirmation** via syntax + MX + SMTP `RCPT TO`
- ✅ **Catch-all / accept-all** detection with a random-address control probe
- ✅ **Disposable** and **role account** (`sales@`, `support@`, …) detection
- ✅ **Large-provider fallback** — Gmail, Outlook, Yahoo, iCloud, Proton, Zoho, Fastmail, Yandex, QQ
  and 163 block probing on purpose. Instead of returning a useless `unknown`, the Actor returns a
  provider-level verdict at **half the price** of a mailbox-confirmed check
- ✅ **Automatic port-25 fallback** — if the container cannot reach outbound port 25, the Actor
  degrades to MX-only mode and bills the cheaper screening event, because no mailbox-level judgement
  was made
- ✅ **Host-aware concurrency** — per-MX-host throttling so one big provider never stalls the batch
- ✅ Built on the Apify platform: scheduling, webhooks, API access, proxy rotation and integrations
  are all available without extra work

### How to use it

1. Open the Actor's **Input** tab.
2. Paste your addresses into `emails`, or pass a `domainsCsv`-style list of plain strings.
3. Leave the defaults alone for a first run — `hostConcurrency` 12 and a 4-second read timeout are
   tuned for mixed B2B lists.
4. Click **Start**, then download the run dataset as JSON, CSV or Excel.
5. Filter on `safeToSend` and keep the `true` rows.

A minimal input looks like this:

```json
{
  "emails": [
    "info@apify.com",
    "this-address-does-not-exist-9182@gmail.com"
  ]
}
```

### Pricing

This Actor is **pay-per-event**. You are charged per verdict produced — never per input row, and
never per API call.

| Event | When it is charged | Price |
|---|---|---|
| **Email verified** | A mailbox-level decision was reached | **$0.99** per 1,000 ( = $0.00099 each ) |
| **Email verdict (provider-level)** | Address belongs to a large provider that blocks probing | **$0.50** per 1,000 ( = $0.0005 each ) |
| **Email screened (MX-only)** | Container could not reach port 25, so only syntax + MX + disposable + role were checked | **$0.25** per 1,000 ( = $0.00025 each ) |
| **Actor start** | Once per run, per GB of memory | **$0.00005** per GB |

Rows that come back as **`unknown` are never charged** — if the Actor could not decide, you do not
pay. Volume tiers lower the unit price automatically on Apify's BRONZE, SILVER, GOLD, PLATINUM and
DIAMOND plans.

Rough budgeting, assuming every row gets a mailbox-level verdict:

| Addresses in | Cost |
|---|---|
| 1,000 | $0.99 |
| 5,000 | 5 × $0.99 |
| 10,000 | 10 × $0.99 |

Real lists cost less than this ceiling, because `unknown` rows are free and large-provider rows bill
at the lower provider rate.

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `emails` | array | sample addresses | The addresses to verify |
| `heloHostname` | string | `mailprobe.io` | Hostname announced in `HELO` |
| `fromAddress` | string | `probe@mailprobe.io` | Address used in `MAIL FROM` |
| `connectTimeoutMs` | integer | `3500` | Connection timeout |
| `readTimeoutMs` | integer | `4000` | Read timeout for SMTP replies |
| `hostConcurrency` | integer | `12` | Parallel connections per MX host |
| `dnsConcurrency` | integer | `12` | Parallel DNS lookups |
| `skipSmtpForDisposable` | boolean | `true` | Skip probing known disposable domains |
| `maxEmails` | integer | `50000` | Safety cap per run |
| `forceMxOnly` | boolean | `false` | Force MX-only mode |
| `forceSmtp` | boolean | `false` | Force SMTP mode |
| `providerFallback` | boolean | `true` | Enable the large-provider heuristic |

### Output

One dataset item per input email:

```json
{
  "email": "info@apify.com",
  "result": "deliverable",
  "valid": true,
  "safeToSend": true,
  "disposable": false,
  "role": false,
  "catchAll": false,
  "confidence": 100,
  "mailboxConfirmed": true,
  "smtpHost": "aspmx.l.google.com",
  "rcptToCode": 250
}
```

`result` is one of `deliverable`, `undeliverable`, `accept_all`, `unknown`, `mx_only` or
`provider_heuristic`.

### Other Actors you may like

- **[Bulk Tech Stack Detector - BuiltWith/Wappalyzer Alternative](https://apify.com/store)** — the technologies
  behind a domain, with categories, versions and confidence.
- **[Bulk On-Page SEO Audit](https://apify.com/store)** — 26 on-page checks and a 0–100 score.
- **[ATS Job Board Feed Sync - Greenhouse, Lever, Ashby](https://apify.com/store)** — normalize career pages across every major ATS.

### FAQ

**Why not just use a regex?** A regex only tells you the address is *shaped* like an address. Only an
SMTP probe tells you the receiving server would accept it.

**What is a catch-all and why does it matter?** A catch-all domain answers "yes" to every address.
Any verifier that reports those as valid will hand you a list full of addresses that bounce. This
Actor flags them as `accept_all` with `mailboxConfirmed: false`.

**Will you probe Gmail?** Gmail and other large providers deliberately block `RCPT TO` probing. We do
not fake a result — you get a provider-level verdict at roughly half price, clearly marked so you can
decide.

**Does a failed run cost me anything?** No. Billing is per event, and events are only emitted when a
verdict is written.

# Actor input Schema

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

要验证的邮箱地址数组（会自动去重、 trim、去掉尖括号）。也可以用逗号/空格分隔的字符串。

## `heloHostname` (type: `string`):

SMTP HELO 使用的主机名。**必须在同一 IP 上全程固定**——某些服务商（如 Fastmail）会记住上一次的 HELO，换了就回 451。改动此项需要先重新标定 IP 信誉。

## `fromAddress` (type: `string`):

SMTP 会话的发件人地址（不会真的发信，只用于 RCPT TO 探测）。

## `connectTimeoutMs` (type: `integer`):

TCP 25 连接超时。实测 3500 是吞吐与成功率的最优平衡点（不可达主机是墙钟时间的主要来源）。

## `readTimeoutMs` (type: `integer`):

等待 SMTP 应答的超时。

## `hostConcurrency` (type: `integer`):

同时处理的 MX 主机数。

## `dnsConcurrency` (type: `integer`):

并发 MX 解析数。

## `skipSmtpForDisposable` (type: `boolean`):

命中一次性邮箱黑名单（8,870 条，双源交叉）的域名直接出结论、不再做 DNS/SMTP 探测。实测 14 封样本（6 个一次性域名）：吞吐 8,861→11,043 封/小时（+24.6%），且这类地址全部正常计费（此前 2 个因 DNS 失败被判为「未做判断」而白送）。关掉它只会更慢、不会更准——一次性域名的 SMTP 应答对「要不要发」没有增量信息。

## `maxEmails` (type: `integer`):

单次 run 最多处理的地址数（护栏，不是收费档位）。实测 11,943 封/小时，Apify 默认 run 上限会先于本值触发；超出部分不处理并在 SUMMARY.inputTruncated 标注。

## `maxMxHosts` (type: `integer`):

每个域名最多尝试的 MX 主机数。实测 2 即可覆盖绝大多数域名，调低是单项提速的最大杠杆。

## `forceMxOnly` (type: `boolean`):

Skip the SMTP phase even if outbound port 25 is reachable. Cheaper (billed as email-screened) and much faster, but cannot confirm a specific mailbox.

## `forceSmtp` (type: `boolean`):

Run the SMTP phase even if the egress self-test says port 25 is unreachable (results will mostly be unknown and therefore free).

## `egressProbeHosts` (type: `array`):

Hosts used by the startup self-test to decide between SMTP and MX-only mode. Leave empty for the defaults.

## `providerFallback` (type: `boolean`):

Gmail, Outlook, Yahoo, iCloud, Proton, Zoho, Fastmail and other large providers actively block RCPT TO probing — they accept the connection but never reveal whether a specific mailbox exists. This is true even when outbound port 25 works. When SMTP returns no verdict for such a domain, the Actor falls back to a provider-level verdict: result=provider\_heuristic, valid=true, confidence=80, mailboxConfirmed=false. These rows are billed at the reduced 'email-provider-verdict' rate ($0.50/1,000) instead of 'email-verified' ($0.99/1,000). Set to false if you only want mailbox-confirmed verdicts (inconclusive rows then return unknown and are free).

## `egressProbeTimeoutMs` (type: `integer`):

Per-host TCP connect timeout for the startup self-test.

## Actor input object example

```json
{
  "emails": [
    "test@example.com",
    "this-address-does-not-exist-9182@gmail.com",
    "info@apify.com"
  ],
  "heloHostname": "mailprobe.io",
  "fromAddress": "probe@mailprobe.io",
  "connectTimeoutMs": 3500,
  "readTimeoutMs": 4000,
  "hostConcurrency": 12,
  "dnsConcurrency": 12,
  "skipSmtpForDisposable": true,
  "maxEmails": 50000,
  "maxMxHosts": 2,
  "forceMxOnly": false,
  "forceSmtp": false,
  "providerFallback": true,
  "egressProbeTimeoutMs": 5000
}
```

# Actor output Schema

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

Per-email record: email, result, valid, safeToSend, disposable, disposableSuspected, role, catchAll, confidence, mailboxConfirmed, provider, gateway, mailTenant, gatewayCatchAllKnown, advice, reasons, smtpHost, rcptToCode, catchAllProbeCode.

# 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@example.com",
        "this-address-does-not-exist-9182@gmail.com",
        "info@apify.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("smiling_jubilance/smtp-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@example.com",
        "this-address-does-not-exist-9182@gmail.com",
        "info@apify.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("smiling_jubilance/smtp-email-verifier").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": [
    "test@example.com",
    "this-address-does-not-exist-9182@gmail.com",
    "info@apify.com"
  ]
}' |
apify call smiling_jubilance/smtp-email-verifier --silent --output-dataset

```

## MCP server setup

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

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/lqlE1PLsQYsvaUcGg/builds/tf0BmnT1TdG6izpdu/openapi.json
