# Bulk Email Verifier - Validate Email Addresses (`adgrowofficial/email-verifier`) Actor

Verifies email addresses with a real SMTP check against the provider that runs each mailbox — no message is ever sent. Returns valid, invalid, catch-all, disposable or unknown for every address, with the reason behind each verdict. You are only charged for addresses we can actually decide.

- **URL**: https://apify.com/adgrowofficial/email-verifier.md
- **Developed by:** [Efe Berke](https://apify.com/adgrowofficial) (community)
- **Categories:** Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $0.90 / 1,000 verified email addresses

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: $0.90/1k, unknowns free

Verify email addresses in bulk with a real SMTP conversation against the
provider that actually runs each mailbox. Nothing is ever delivered, so nobody
receives a test message.

**You are never charged for an address we could not resolve.**

> 💡 Need leads too? **[Get verified B2B leads at Getle.ad →](https://getle.ad)**

***

### 💸 You only pay for answers

Every verifier hits addresses it cannot resolve. Some mail servers refuse to
answer questions about their recipients, no matter who is asking. The industry
standard is to hand you those rows as "unknown" and bill you for them anyway.

We do not.

On a real 1,000 address B2B list, roughly 150 come back unknown. Everywhere
else those are 150 rows on your invoice. Here they are free, and you pay only
for the addresses we actually resolved.

| | This Actor | Typical verifier |
|---|---|---|
| Unresolvable addresses are free | ✅ Yes | ❌ You pay anyway |
| Catch-all reported honestly | ✅ Yes | ❌ Often sold to you as valid |
| Reason given for every verdict | ✅ Yes | ❌ Just a label |
| Role addresses verified, not auto killed | ✅ Yes | ❌ Marked invalid on sight |
| Your list never touches disk | ✅ Yes | ❌ Varies |

***

### ⚡ What you get

✅ **Bulk email verification** at roughly 23 addresses per second
✅ **Catch-all detection** that probes a domain several times before deciding
✅ **Disposable and burner address detection** against a list refreshed weekly
✅ **Role address flagging** (`info@`, `sales@`) without pretending they are dead
✅ **The reason behind every verdict**, not just a label
✅ **Chain it after any scraper** by pointing at that Actor's dataset

***

### 🛡️ What we guarantee

🔒 **Your list is never written to disk.** Addresses live in memory for the
length of the job and the job is deleted as soon as you collect the results.
Our database records how many addresses were checked, never which ones.

📭 **No mail is ever sent.** The SMTP conversation stops at the point where the
server states whether it would accept the recipient. Nothing lands in anyone's
inbox and nobody knows they were checked.

💸 **No charge for `unknown`.** If we cannot get a trustworthy answer, that row
is free. This is enforced in the billing itself, not a promise in a document.

🎯 **No invented confidence.** We will tell you an address cannot be resolved
rather than guess and let you find out at send time.

***

### 📊 What a real list actually looks like

Measured on 300 addresses from a live B2B prospecting list:

| Result | Share | What to do |
|---|---|---|
| ❌ `invalid` | 41.7% | Remove. These are the bounces you are paying to avoid |
| ⚠️ `catchall` | 25.7% | Your call. The domain accepts everything, so nobody can confirm the mailbox |
| ✅ `valid` | 18.0% | Send |
| ❓ `unknown` | 14.7% | Free. The server would not give a straight answer |

If a verifier promises you 99% certainty on a list like this, ask what it did
with the catch-all quarter.

***

### 🎯 What the statuses mean

**✅ `valid`** The mail server accepted this specific recipient. Safe to send.

**❌ `invalid`** The mailbox does not exist, the domain does not exist, or the
domain accepts no mail at all.

**⚠️ `catchall`** The domain accepts mail for *every* address, including ones we
invented on the spot. The server itself will not tell anyone whether this
particular mailbox exists, so no verifier can resolve it over SMTP. Around a
fifth of business domains are configured this way. Treat them as a calculated
risk, not as verified.

**🗑️ `disposable`** A burner or temporary mail provider.

**❓ `unknown`** No answer we would stand behind. Not charged. **Quality mode**
re-checks these at roughly half the speed.

***

### 🔌 Input

Paste addresses into **Email addresses**, or point **input dataset ID** at
another Actor's output and name the field that holds the address. The second
form is the useful one: run a scraper, then chain this Actor onto its dataset to
strip out everything that would have bounced.

```json
{
  "emails": ["jane.doe@example.com", "info@example.org"],
  "qualityMode": false,
  "skipDuplicates": true
}
```

| Option | What it does |
|---|---|
| `emails` | Addresses to verify |
| `inputDatasetId` | Read addresses from an existing dataset instead |
| `emailField` | Which field of that dataset holds the address |
| `qualityMode` | Re-checks deferred addresses. Slower, recovers some unknowns |
| `skipDuplicates` | Verify, and charge for, each unique address once |

***

### 📤 Output

```json
{
  "email": "jane.doe@example.com",
  "status": "valid",
  "deliverable": true,
  "risky": false,
  "reason": "ok",
  "role": false,
  "billable": true
}
```

| Field | Meaning |
|---|---|
| `email` | The address, normalised to lower case |
| `status` | `valid`, `invalid`, `catchall`, `disposable`, `unknown` |
| `deliverable` | `true` only for `valid`. Safe to send |
| `risky` | `true` for `catchall` |
| `role` | `true` for shared mailboxes like `info@`, still verified normally |
| `reason` | The technical detail, e.g. `smtp_550`, `nxdomain`, `no_mx_record` |
| `billable` | `false` for `unknown`, which is what you were not charged for |

***

### 💰 Pricing

**$0.90 per 1,000 verified addresses**, billed as $0.0009 each.

You are charged for `valid`, `invalid`, `catchall` and `disposable` results.
You are not charged for `unknown`, so an address we could not resolve never
reaches your invoice.

That means a submitted list always costs less than its size suggests. How much
less depends on the list: the harder it is to resolve, the less of it you pay
for.

***

### ❓ Frequently asked questions

**Does this send an email to the addresses I check?**
No. The conversation stops before any message body is transmitted. Nobody
receives anything and nobody is told they were verified.

**Why are some addresses `unknown`?**
Some mail security products, Mimecast most of all, refuse to answer recipient
questions from senders they do not already know, no matter how long you wait.
Others firewall port 25 entirely. Those addresses cannot be resolved over SMTP
by us or by anyone else, which is exactly why we do not charge for them.

**Why can't you tell me whether a catch-all address is real?**
Because the server will not say. It accepts every recipient by design,
including addresses that do not exist. Any tool reporting catch-all addresses as
"valid" is guessing, and you find out at send time.

**Are role addresses like `info@` verified?**
Yes, exactly like any other address. A shared mailbox usually exists and accepts
mail, so reporting it as invalid would be calling a working address dead. You
get the real verdict plus `role: true` and decide for yourself.

**Do you store my list?**
No. Addresses are held in memory for the length of the job and dropped when you
collect the results. Our records store how many addresses were checked, never
which ones.

**Can I verify 100,000 addresses?**
Yes. Large inputs are split automatically and rows are written to the dataset as
they are decided, so an interrupted run still leaves behind everything resolved
up to that point.

**How accurate is it?**
An SMTP check reports what the receiving server says at the moment you ask.
Servers are inconsistent in both directions: some accept every recipient at the
door and bounce later, others refuse a good address because they dislike the
connection. This Actor answers `unknown` rather than guess when the response is
not trustworthy. That is why the unknown bucket is not zero, and why you are not
billed for it.

# Actor input Schema

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

Addresses to verify. Leave empty if you are reading them from a dataset instead.

## `inputDatasetId` (type: `string`):

Read addresses from an existing dataset — handy for chaining after a scraper. Combine with the field name below.

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

Which field of the input dataset holds the address.

## `qualityMode` (type: `boolean`):

Re-checks addresses whose server gave a temporary answer. Roughly halves throughput and recovers a small share of unknowns — worth it when completeness matters more than speed.

## `skipDuplicates` (type: `boolean`):

Verify each unique address once. Duplicates are re-attached to the output afterwards.

## Actor input object example

```json
{
  "emails": [
    "someone@example.com"
  ],
  "emailField": "email",
  "qualityMode": false,
  "skipDuplicates": true
}
```

# Actor output Schema

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

No description

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

No description

# 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": [
        "someone@example.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("adgrowofficial/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": ["someone@example.com"] }

# Run the Actor and wait for it to finish
run = client.actor("adgrowofficial/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": [
    "someone@example.com"
  ]
}' |
apify call adgrowofficial/email-verifier --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,adgrowofficial/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/RH9Uzf0khIYH38iQz/builds/undXKSeZJRZ5pgMJh/openapi.json
