# Email Verifier & Validator - Bulk SMTP & Catch-All Checker (`memo23/email-verifier`) Actor

Bulk email verification with a real SMTP mailbox check: we verify each address by asking its mail server if that exact inbox exists. A catch-all checker flags domains that accept anything. Plus syntax and MX validation, disposable, role and free-provider flags and a valid/risky/invalid verdict.

- **URL**: https://apify.com/memo23/email-verifier.md
- **Developed by:** [Muhamed Didovic](https://apify.com/memo23) (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 $1.00 / 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.

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 & Validator ✅

**Bulk email verification that checks the mailbox, not just the domain.** Give it your list of email addresses; get back one row per address with a clear `valid` / `risky` / `invalid` verdict, plus the signals behind it: syntax, MX (does the domain accept mail), disposable / role-based / free-provider flags, and a real SMTP mailbox check with a catch-all checker that tells you whether the exact inbox exists. No proxy, no API key, no login. Export to JSON, CSV or Excel.

![How the Email Verifier works](https://raw.githubusercontent.com/muhamed-didovic/muhamed-didovic.github.io/main/assets/how-it-works-email-verifier.png)

### ✨ Why use this verifier

- **One clear verdict per email** - `valid`, `risky`, or `invalid` - with every underlying signal kept on the row so you can set your own threshold.
- **Real mailbox checks.** Apify blocks outbound SMTP (port 25), so a mailbox check dialed straight from the platform never reaches the mail server. This one asks the recipient's mail server from our own mail-check server, so `mailboxConfirmed: true` means the server accepted that exact mailbox.
- **It never lies.** When a mail server can't be reached or won't answer, the verdict falls back to the DNS/MX result. A genuinely valid address is never downgraded just because a check didn't get through.
- **Catch-all checker.** A random, certainly-nonexistent mailbox is probed alongside the real one, so "accepted" on a catch-all domain is flagged rather than trusted.
- **Disposable, role-based and free-provider flags** - spot `mailinator.com`, `info@`/`sales@`, and `gmail.com`/`outlook.com` addresses that skew outreach.
- **Bulk and fast.** MX cached per domain, checked in parallel - thousands of addresses per run. Turn `smtp` off for a pure-DNS pass.
- **Paste or list.** Feed a structured list, or paste a whole blob (comma / space / newline separated); duplicates are removed.
- **No setup.** No proxy, no third-party API key, no account.

### 🎯 Use cases

| You are… | Use it to… |
|---|---|
| **Running cold outreach** | Scrub a list before sending, so bounces don't wreck your sender reputation. |
| **Cleaning a CRM** | Flag dead, disposable and role-based addresses across your contact base. |
| **Capturing sign-ups** | Reject junk and typo domains at import time. |
| **Buying/merging lists** | Grade a purchased list before you trust it. |
| **Pairing with an email finder** | Verify addresses a finder returned before they reach a campaign. |

### 📥 Supported inputs

Provide addresses as a **list** (`emails`) and/or a **pasted blob** (`emailsText`, comma / space / newline separated). Both are merged and de-duplicated. To check a list that lives in a spreadsheet or CSV file, copy just the email column into the paste box. The SMTP mailbox check is on by default; set `smtp: false` to skip it.

### 🔄 How it works

1. **Normalise** - the list and the blob are merged, split, lowercased and de-duplicated.
2. **Check** - each address is run through syntax, MX (DNS), and disposable / role / free classification, then the domain's mail server is asked whether the mailbox exists, and a random address is tested to catch catch-all domains.
3. **Emit** - one flat row per address with all signals and a single verdict.

### 🔍 What an SMTP mailbox check catches that an MX-only check misses

An MX-only check is a DNS lookup: it confirms the domain exists and has a mail server, but it never asks that server about the address itself, so any name at a working domain passes. SMTP email verification goes one step further: it asks the domain's mail server whether it would accept mail for that exact address (`RCPT TO`), then hangs up before anything is sent.

| Situation | MX-only check | MX + SMTP mailbox check (this actor) |
|---|---|---|
| Domain doesn't exist | `invalid` | `invalid` |
| Typo in the name part (`jonh@` instead of `john@`) | passes | `invalid` when the server answers "no such user" |
| Mailbox deleted after someone left the company | passes | `invalid` when the server answers "no such user" |
| Catch-all domain that accepts any address | passes | flagged `catchAll`, verdict `risky` |
| Proof that the exact mailbox exists | not possible | `mailboxConfirmed: true` |

The "no such user" addresses are the hard bounces that hurt sender reputation, and an MX-only pass lets them through. When a server won't give a straight answer (greylisting, rate limits, a block on our checking server), the row keeps `accepted: null` and the verdict falls back to the MX result, so the SMTP check never turns a good address bad by guessing.

### ⚙️ Input parameters

| Field | Type | Default | Description |
|---|---|---|---|
| `emails` | array | - | Addresses to verify (entries may themselves be comma/space/newline separated). |
| `emailsText` | string | - | Paste a blob of addresses; merged with `emails`. |
| `smtp` | boolean | `true` | SMTP mailbox + catch-all check. Set `false` for a DNS-only pass. |
| `smtpTimeoutMs` | integer | 7000 | SMTP dialog timeout per address (ms). |
| `maxItems` | integer | 1000 | Max verified rows per run. Free-tier capped at 100. |
| `maxConcurrency` | integer | 10 | Addresses verified in parallel. |

#### Example - quick list

```json
{ "emails": ["jane@stripe.com", "info@acme.com", "test@mailinator.com"] }
```

#### Example - paste a blob, DNS-only

```json
{ "emailsText": "a@x.com, b@y.com\nc@z.com", "smtp": false }
```

### 📊 Output overview

One row per input address. The `verdict` is `valid` when syntax and MX are good and the address isn't disposable; `risky` when it's role-based, a free provider, or on a catch-all domain (deliverable, but not individually confirmable); `invalid` when syntax fails, the domain has no mail server, the domain is disposable, or SMTP cleanly rejects that exact mailbox (even on a domain that also accepted a random address). A domain with no MX record, only an A record, is `risky` unless its server answers the SMTP check. The `reason` field says what decided each verdict. `mailboxConfirmed` is `true` only when the mail server accepted that exact mailbox and rejected a random one. The raw signals stay on the row so you can re-grade with your own rules.

### 📦 Output sample

```json
{
  "email": "jane@stripe.com",
  "syntaxValid": true,
  "domain": "stripe.com",
  "mxFound": true,
  "isDisposable": false,
  "isRoleBased": false,
  "isFreeProvider": false,
  "smtp": { "checked": true, "reachable": true, "accepted": true, "catchAll": false, "code": 250, "detail": "accepted" },
  "mailboxConfirmed": true,
  "verdict": "valid",
  "reason": "mailbox-confirmed"
}
```

With `smtp: false` the `smtp` object is left out and `mailboxConfirmed` is always `false`.

### 🗂 Key output fields

| Field | Meaning |
|---|---|
| `verdict` | `valid` / `risky` / `invalid` - the headline call. |
| `reason` | What decided the verdict: `mailbox-confirmed`, `mailbox-rejected`, `catch-all`, `smtp-inconclusive`, `no-mx-record`, `no-mail-server`, `role-based`, `free-provider`, `disposable-domain`, `invalid-syntax`, `dns-only`. |
| `mailboxConfirmed` | The mail server accepted this exact mailbox and the domain is not catch-all. The strongest signal on the row. |
| `syntaxValid` | Passes RFC-ish address syntax. |
| `domain` / `mxFound` | The domain, and whether it has an MX record. `false` for a domain with only an A record (reason `no-mx-record`). |
| `isDisposable` | Throwaway domain (mailinator, guerrillamail, …). |
| `isRoleBased` | Role mailbox (`info@`, `sales@`, `support@`, …), not a person. |
| `isFreeProvider` | Free webmail (gmail, outlook, yahoo, …). |
| `smtp.accepted` | *(smtp mode)* Mailbox accepted (`true`), cleanly rejected (`false`), or unclear (`null`). |
| `smtp.catchAll` | *(smtp mode)* Domain accepts any mailbox, so acceptance is meaningless. |
| `smtp.detail` | *(smtp mode)* Short reason: `accepted`, `rejected 550`, `catch-all`, or `inconclusive: …` when the server gave no answer about the mailbox (e.g. `inconclusive: the mail server blocked the check (SMTP 550)`). |

### ❓ FAQ

**Is this an email validator or an email verifier?**
Both. Email validation usually means checking the syntax and the domain; email verification goes further and asks the mail server about the mailbox itself. This actor runs both in one pass and keeps each result on the row.

**Does it send an email to the address?**
No. The SMTP conversation stops after the recipient check (`RCPT TO`) and closes with `QUIT` before any message is sent, so nothing lands in anyone's inbox.

**Do I need a proxy or API key?**
No. DNS checks need nothing, and the SMTP check runs on our own mail-check server, so you don't need port 25 or a third-party verifier key.

**How reliable is the SMTP mailbox check?**
Gmail, Google Workspace, Microsoft 365 and most company mail servers answer honestly, so a real mailbox comes back accepted and a made-up one rejected. Consumer outlook.com / hotmail.com sometimes accept made-up addresses, so they can come back `invalid` but never `mailboxConfirmed`. Two cases stay inconclusive: catch-all domains, which accept every address (flagged as `catchAll`), and servers that greylist, rate-limit or block the check (`accepted: null`, reason `smtp-inconclusive`; Yahoo and AOL mailboxes currently land here). In those cases the verdict falls back to the DNS/MX result rather than guessing, so it under-claims instead of over-claiming.

**What's the difference between `risky` and `invalid`?**
`invalid` addresses should not be mailed (bad syntax, no mail server, disposable, or a clean rejection). `risky` addresses are deliverable but not individually confirmable (role mailboxes, free providers, catch-all domains, domains with no MX record) - you decide whether to keep them.

**Can it find emails, not just verify them?**
This actor verifies. To discover addresses, pair it with an email-finder and feed the results here.

**How many can I verify at once?**
Thousands per run. A DNS-only pass (`smtp: false`) is the fastest; the SMTP check adds roughly 0.2-3 seconds per address, run in parallel.

**Can I check a CSV file or a spreadsheet?**
Yes. Copy the email column into the paste box (`emailsText`), or send the addresses as an array through the API. Every word in the paste box is treated as an address, so paste only the email column: names or company columns would come back as `invalid` rows.

**Can I verify a single email address?**
Yes. Put one address in `emails` and the run returns one row for it.

### 💬 Support

Found a bug or need an extra signal? Open an issue on the actor's **Issues** tab and we'll take a look.

### 🔎 Explore more

See the [full memo23 actor catalog →](https://apify.com/memo23)

***

### ⚠️ Disclaimer

This Actor performs standard email deliverability checks (DNS/MX lookups and an SMTP conversation that ends before any message is sent) against addresses you supply. It does not send email, does not store your lists, and accesses no private data. You are responsible for having a lawful basis to process the addresses you submit and for complying with applicable data-protection and anti-spam law (GDPR, CAN-SPAM, etc.).

***

### SEO Keywords

email verifier, email verification, verify email address, bulk email verifier, email validation, email checker, mx record check, smtp verification, catch-all detection, disposable email detector, role-based email filter, email list cleaning, bounce reduction, email deliverability, verify email api, apify email verifier, email validator, clean email list, csv email verification, cold outreach list cleaning, smtp email verification, bulk email verification, catch-all email checker

# Actor input Schema

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

Email addresses to verify, e.g. \["jane@stripe.com", "info@acme.com"].

## `emailsText` (type: `string`):

Paste many addresses at once - comma, space or newline separated. Merged with the list above.

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

Ask the mail server whether each mailbox exists, plus a catch-all test. Sets <code>mailboxConfirmed</code> and turns cleanly rejected addresses <code>invalid</code>. Turn off for a faster DNS-only pass.

## `smtpTimeoutMs` (type: `integer`):

How long to wait for the SMTP dialog before giving up on one address.

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

Hard cap on verified rows per run. Each verified email is one paid result. Default 1000. Free-tier users are capped at 100.

## `maxConcurrency` (type: `integer`):

How many addresses to verify at once. 10 is a good default.

## Actor input object example

```json
{
  "emails": [
    "jane.doe@stripe.com",
    "info@acme.com",
    "test@mailinator.com"
  ],
  "smtp": true,
  "smtpTimeoutMs": 7000,
  "maxItems": 1000,
  "maxConcurrency": 10
}
```

# Actor output Schema

## `results` (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": [
        "jane.doe@stripe.com",
        "info@acme.com",
        "test@mailinator.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("memo23/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": [
        "jane.doe@stripe.com",
        "info@acme.com",
        "test@mailinator.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("memo23/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": [
    "jane.doe@stripe.com",
    "info@acme.com",
    "test@mailinator.com"
  ]
}' |
apify call memo23/email-verifier --silent --output-dataset

```

## MCP server setup

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