# Email Verifier & Validator — Catch-All Resolution (`mu0i/email-verifier`) Actor

Bulk email verification with strict results: ok only on a real existence signal. Resolves many catch-all addresses. Pay only for valid, invalid and disposable.

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

## Pricing

from $0.80 / 1,000 verified email (decisive result)s

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

## Email Verifier & Validator — Catch-All Resolution

**Strict, no false positives.** Verify a list of email addresses in bulk. This verifier is built around one rule: **an address
is marked `ok` only when there is an explicit signal that the mailbox exists**, never because
nothing said it doesn't. When it can't be sure, it says so (`catch_all` or `unknown`, with the
reason) instead of guessing.

That matters because a wrong `ok` costs you the most: you send to a dead mailbox, it bounces, and
your sender reputation takes the hit.

### What you get

- **Strict `ok`.** A mailbox is `ok` only on a hard SMTP accept at a domain that rejects unknown
  addresses, or when the specific mailbox is otherwise confirmed to exist.
- **Many catch-all addresses resolved.** Domains that accept every address can't be verified by
  SMTP alone. We can still confirm many of those mailboxes, so fewer of
  your addresses end up as `catch_all`.
- **One row for every unique address**, including malformed ones (`invalid` / `invalid_syntax`). On the Apify Free plan, the first 1,000 unique addresses per run.
- **Every row explains itself**: `verdict_source` says what decided it, `confidence` how sure it is.
- **You pay only for decisive results** (`ok`, `invalid`, `disposable`). `catch_all`, `unknown` and
  `error` rows are free, and so is an `ok` marked `mailbox_unconfirmed` (see below).

### Use cases

Each of these opens a ready-to-run example with sample addresses. Replace them with your own list,
or link your CSV file or Google Sheet.

- **[Verify an email list before a cold outreach campaign](https://apify.com/mu0i/email-verifier/examples/verify-email-list-before-campaign)**
  — remove invalid and risky addresses before you send, so bounces don't hurt your sender
  reputation.
- **[Check catch-all email addresses for real mailboxes](https://apify.com/mu0i/email-verifier/examples/check-catch-all-emails)**
  — on a domain that accepts every address, many real mailboxes can still be confirmed.
- **[Catch typos and disposable emails in a sign-up list](https://apify.com/mu0i/email-verifier/examples/find-typos-and-disposable-emails)**
  — flag mistyped domains (`gmail.con` → `gmail.com`), throwaway inboxes and domains that can't
  receive mail before they reach your CRM.

### How to use it

1. Paste your addresses into **Email addresses** (one per line), **or** use **File** — upload a CSV
   from your computer, or paste a link to one (Google Sheets, Google Drive, Dropbox). You can use
   both; duplicates are verified once.
2. Click **Start**.
3. Results stream into the dataset while the run is going. Export them as CSV, Excel or JSON.

### Input

| Field | Description |
|---|---|
| `emails` | Addresses to verify, one per line. |
| `fileUrl` | An uploaded CSV/TXT file, or a link to one — a Google Sheet, a Google Drive file or a Dropbox file (max 25 MB, about a million addresses). |
| `emailColumn` | Optional: the file column with the addresses — header name (`Email`), letter (`C`) or number (`3`). Detected automatically when empty. |

Give `emails`, `fileUrl`, or both.

#### Example input

Paste a list:

```json
{
  "emails": [
    "jane@acme.com",
    "sales@gmial.com",
    "not-an-email"
  ]
}
```

Or link a Google Sheet (or a CSV file) and name the column that holds the addresses:

```json
{
  "fileUrl": "https://docs.google.com/spreadsheets/d/<SHEET_ID>/edit",
  "emailColumn": "Email"
}
```

#### Using a file or Google Sheet

- **Google Sheets:** share the sheet as *Anyone with the link → Viewer* and paste the link. The tab
  in the link is used (the first tab if the link doesn't point to one).
- **CSV files:** comma, semicolon, tab and pipe separators all work, with or without a header row.
  Quoted fields and `Name <jane@acme.com>` or `mailto:` cells are handled.
- **The email column** is found by its header (anything containing "email"), or else as the column
  with the most addresses. Set **Email column** to choose it yourself.
- **Rows without an address** in that column (blank, `N/A`…) are skipped and not charged.
- **Excel files:** export them as CSV first, or upload them to Google Sheets and use that link.
- If the file can't be read, the run tells you why (for example "not shared publicly") and verifies
  any addresses you typed. Nothing is charged for a file that couldn't be read.

### Output

One row per unique address:

| Field | Meaning |
|---|---|
| `email` | The address, trimmed and lowercased. |
| `result` | `ok`, `invalid`, `catch_all`, `unknown`, `disposable` or `error` (see below). |
| `resultcode` | 1 ok · 2 catch_all · 3 unknown · 4 error · 5 disposable · 6 invalid. |
| `quality` | `good` (ok), `risky` (catch_all, unknown), `bad` (invalid, disposable), empty for error. |
| `subresult` | The detailed reason, e.g. `no_mailbox`, `dns_no_mx`, `greylisted`, `invalid_syntax`, `mailbox_unconfirmed`. |
| `free` | The domain is a free mailbox provider (gmail.com, yahoo.com…). |
| `role` | A role or team inbox such as `info@` or `sales@`. |
| `didyoumean` | A suggested fix for a likely domain typo (`gmial.com` → `gmail.com`). |
| `error` | Set only when `result` is `error`: `verifier_unavailable` (the service was down — try again later) or `verification_error`. |
| `mx` | The domain's primary mail server. |
| `verdict_source` | The kind of evidence behind the result, for example the mail server's answer (`smtp`) or the domain's DNS records (`dns`). |
| `confidence` | `high`, `medium` or `low`. |

The first nine fields follow the field names other verifiers commonly use, so you can switch
without remapping columns.

#### Example output

Four rows from a real run — a confirmed mailbox, a domain that accepts no mail, a malformed
address, and a typo domain with its suggested fix:

```json
[
  {
    "email": "support@apify.com",
    "result": "ok",
    "resultcode": 1,
    "quality": "good",
    "subresult": "",
    "free": false,
    "role": true,
    "didyoumean": "",
    "error": "",
    "mx": "aspmx.l.google.com",
    "verdict_source": "smtp",
    "confidence": "high"
  },
  {
    "email": "jane.doe@example.com",
    "result": "invalid",
    "resultcode": 6,
    "quality": "bad",
    "subresult": "host_not_accept_incoming_mail",
    "free": false,
    "role": false,
    "didyoumean": "",
    "error": "",
    "mx": "",
    "verdict_source": "dns",
    "confidence": "high"
  },
  {
    "email": "not-an-email",
    "result": "invalid",
    "resultcode": 6,
    "quality": "bad",
    "subresult": "invalid_syntax",
    "free": false,
    "role": false,
    "didyoumean": "",
    "error": "",
    "mx": "",
    "verdict_source": "syntax",
    "confidence": "high"
  },
  {
    "email": "sales@gmial.com",
    "result": "disposable",
    "resultcode": 5,
    "quality": "bad",
    "subresult": "disposable_domain",
    "free": false,
    "role": true,
    "didyoumean": "gmail.com",
    "error": "",
    "mx": "",
    "verdict_source": "heuristic",
    "confidence": "high"
  }
]
```

A run also writes a `SUMMARY` record to its key-value store: counts per result, how many results
were charged, and why the run stopped early if it did.

#### What each result means

| Result | Meaning | Charged |
|---|---|---|
| `ok` | The mailbox exists. Safe to send. | yes |
| `ok` / `mailbox_unconfirmed` | The address belongs to a real account, but its mailbox could not be confirmed, so it may bounce (`confidence` is `medium`). Send with care, or drop it. | no |
| `invalid` | The mailbox or the domain does not exist, or the address is malformed. | yes |
| `disposable` | A throwaway-inbox domain. | yes |
| `catch_all` | The domain accepts every address and this one could not be confirmed. | no |
| `unknown` | Could not be decided (for example the mail server was busy or deferred us). | no |
| `error` | Not verified (see `error`). | no |

### Pricing

Pay per event: you are charged per **decisive** result (`ok`, `invalid`, `disposable`). Rows that
are `catch_all`, `unknown` or `error` are free, so you never pay for an answer that doesn't help you.
An `ok` with subresult `mailbox_unconfirmed` is free as well: we show it, but we don't charge for an
address whose mailbox we couldn't confirm.

| Apify plan | Per decisive result | Per 1,000 |
|---|---|---|
| Free, Bronze | $0.0010 | $1.00 |
| Silver | $0.0009 | $0.90 |
| Gold and above | $0.0008 | $0.80 |

Each run also has a start fee of $0.0001 per GB of memory (minimum one; a default run pays $0.0001).

**Example:** you verify 10,000 addresses on the Bronze plan. 8,000 come back `ok`, `invalid` or
`disposable`, and 2,000 come back `catch_all` or `unknown`. You pay for the 8,000: **$8.00**. The
2,000 risky rows cost nothing.

**Apify Free plan:** each run verifies the first 1,000 unique addresses; the status message says how many were left out. Any paid Apify plan removes the limit.

If you set a maximum charge for the run, the actor stops verifying when that budget is used up.
The remaining addresses still get a row (`unknown` / `max_charge_reached`) and are not charged.

### Reliability

- **Large lists and timeouts.** If the run's timeout is getting close, the actor stops starting new
  work and finishes cleanly. Addresses it didn't reach come back as `unknown` / `run_time_limit`
  (free). **Run the same input again with a longer timeout — you are never charged twice.** For 72
  hours, every address already charged in the stopped run is copied into the new run's results for
  free, and only the rest is verified. The same holds after a run stops at your maximum charge.
- **Service hiccups.** Temporary errors are retried automatically. If the verification service is
  unavailable, the affected rows come back as `error` / `verifier_unavailable` (free) instead of
  the run failing.
- **Busy mail servers.** If a mail server is pushing back, the actor doesn't hammer it. Those rows
  come back as `unknown` / `deferred_server_busy`.
- **Interrupted runs resume.** If Apify moves the run to another server, it continues where it left
  off without re-verifying or re-charging finished batches.

### Other actors by this developer

#### Build a lead list, then verify it here

| Actor | What it does |
|---|---|
| [Skip Trace API — Phone, Email & Address Lookup](https://apify.com/mu0i/skip-tracing-api) | Look up anyone in the US by name, phone, email or address: phones with carrier and line type, emails, addresses. Run the emails it finds through this verifier. |
| [Zillow Property Scraper — full record by zpid](https://apify.com/mu0i/zillow-property-scraper) | The full Zillow record for a zpid or listing URL: price, Zestimate, home facts, price and tax history, schools, agent. |
| [Zillow Search Scraper — listings by city or ZIP](https://apify.com/mu0i/zillow-search-scraper) | Zillow listings for sale, for rent or recently sold by city, ZIP or neighborhood. |
| [TikTok Profile Scraper — followers, likes, bio](https://apify.com/mu0i/tiktok-profile-scraper) | Public profile for any TikTok account: followers, likes, video count, bio, verified status — for creator outreach. |
| [TikTok Shop Scraper — Products, Sellers & Reviews](https://apify.com/mu0i/tiktok-shop-scraper) | Search TikTok Shop, product detail with images and reviews, or a seller's full catalogue. |

#### More from this developer

| Actor | What it does |
|---|---|
| [Hotel Rates, Award Points & OTA Parity](https://apify.com/mu0i/hotel-rates-award-points) | Live cash, member and award-point rates from 11 hotel chains, side by side with the big booking sites. |
| [TikTok Search Scraper](https://apify.com/mu0i/tiktok-search-scraper) | Search TikTok by keyword across videos, accounts, sounds, hashtags, photo posts and places. |
| [TikTok User Posts Scraper](https://apify.com/mu0i/tiktok-user-posts) | Every video an account has posted, with stats and a no-watermark link. |
| [TikTok Comments Scraper](https://apify.com/mu0i/tiktok-comments-scraper) | Every comment on a video, with threaded replies and no duplicates. |
| [TikTok Hashtag Scraper](https://apify.com/mu0i/tiktok-hashtag-scraper) | Every video under a hashtag, with play counts and likes. |
| [TikTok Trending Scraper](https://apify.com/mu0i/tiktok-trending) | Trending videos from TikTok's own Explore feed, with full stats. |
| [TikTok Sound Scraper](https://apify.com/mu0i/tiktok-sound-scraper) | Find sounds by name and the videos that actually use them. |
| [TikTok Transcript Scraper](https://apify.com/mu0i/tiktok-transcripts) | Transcripts and subtitles from TikTok's own caption track: text, VTT and SRT. |
| [TikTok Video Downloader](https://apify.com/mu0i/tiktok-video-downloader) | No-watermark MP4, MP3 audio, covers and subtitles for any public video. |
| [TikTok Live Scraper](https://apify.com/mu0i/tiktok-live-scraper) | Find live rooms by keyword, with playable stream links. |
| [TikTok Places Scraper](https://apify.com/mu0i/tiktok-places-scraper) | TikTok's place directory with each location's reviews and ratings. |
| [TikTok Shop Reviews Scraper](https://apify.com/mu0i/tiktok-shop-reviews-scraper) | Every review on a US TikTok Shop product, to the full archive. |
| [TikTok Shop Category Scraper](https://apify.com/mu0i/tiktok-shop-category-scraper) | Every product in a US TikTok Shop category, subcategories included. |

### FAQ

**Why are some addresses `catch_all`?** Their domain accepts mail for any address, so a mail-server
check can't tell a real mailbox from a made-up one. We try to confirm the address in other ways
first and only report `catch_all` when we can't. Treat these as risky.

**Why `unknown`?** The mail server didn't give a usable answer: it greylisted us, was busy, or
couldn't be reached. We report that honestly rather than guessing `ok`.

**Do you store my email addresses?** The results are stored in your own run's dataset on Apify. The
verification service doesn't keep a list of the addresses you verify. It caches only facts about
domains, such as their mail servers and whether a domain is catch-all. If a run stops early, the
actor keeps a key-value store named `email-verifier-resume` in your Apify account so that a re-run
can skip what you already paid for. It holds the results you were charged for, with every address
replaced by a one-way hash, never the address itself. Results older than 72 hours are ignored and
removed the next time a run saves there, and you can delete the store at any time.

**Do you send any email?** No. Verification stops before any message is sent.

**My Google Sheet link doesn't work.** Open the sheet, click *Share*, set *General access* to
*Anyone with the link* (Viewer), and copy the link again. Private sheets can't be read.

### API

Start a run and fetch the results with the Apify API or the Apify client:

```js
import { ApifyClient } from "apify-client";

const client = new ApifyClient({ token: "<YOUR_APIFY_TOKEN>" });
const run = await client.actor("mu0i/email-verifier").call({
  emails: ["jane@acme.com"],
  // or: fileUrl: "https://docs.google.com/spreadsheets/d/<SHEET_ID>/edit",
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

The same in Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("mu0i/email-verifier").call(run_input={
    "emails": ["jane@acme.com"],
    # or: "fileUrl": "https://docs.google.com/spreadsheets/d/<SHEET_ID>/edit",
})
items = client.dataset(run["defaultDatasetId"]).list_items().items
```

### Integrations

Connect the verifier to the tools you already use through
[Apify integrations](https://docs.apify.com/platform/integrations):

- **Zapier, Make and n8n:** start a verification when new leads arrive, then send the results to
  your CRM or spreadsheet.
- **Webhooks:** get a call when a run finishes and fetch its dataset.
- **Schedules:** re-verify a Google Sheet or CSV link on a regular schedule, for example weekly.

# Actor input Schema

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

Addresses to verify, one per line. Duplicates and blank lines are ignored. Each unique address gets exactly one result row (Free plan: the first 1,000 per run) — including ones that are not well-formed (they come back invalid / invalid_syntax). The two example addresses are ignored when you give a file and leave them unchanged.

## `fileUrl` (type: `string`):

Upload a CSV or TXT file from your computer, or paste a link to one: a Google Sheets link, a Google Drive file link or a Dropbox link. Google files must be shared as "Anyone with the link" (Viewer). For a Google Sheet, the tab in the link is used (the first tab if none). Max 25 MB (about a million addresses). Excel files: export as CSV first. Rows without an address in the email column are skipped and not charged.

## `emailColumn` (type: `string`):

Optional. Which column of the file holds the addresses: its header name (Email), its letter (C) or its number (3). Leave empty to detect it automatically.

## Actor input object example

```json
{
  "emails": [
    "support@apify.com",
    "jane.doe@example.com"
  ]
}
```

# Actor output Schema

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

One row per unique input address.

## `summary` (type: `string`):

Counts per result, how many were charged, and why the run stopped early (if it did).

# 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": [
        "support@apify.com",
        "jane.doe@example.com"
    ]
};

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

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

```

## MCP server setup

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