# Bulk Email Verifier & Validator - MX, Disposable, No API Key (`flash_scraper/email-verifier`) Actor

Email deliverability checker and bulk verifier. No API key, never sends email. Checks syntax, MX records, 7,900+ disposable domains, role accounts and free webmail; detects Google Workspace/Microsoft 365, flags typos, scores 0-100 (A-F). Clean email lists before a cold campaign. CSV, JSON, Excel.

- **URL**: https://apify.com/flash\_scraper/email-verifier.md
- **Developed by:** [Flash Scrape](https://apify.com/flash_scraper) (community)
- **Categories:** Lead generation, Automation, Developer tools
- **Stats:** 6 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $3.00 / 1,000 results

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?

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 & Validator - MX, Disposable, No API Key

Bulk email verifier and email list cleaning tool for cold-email senders, lead-gen agencies and CRM owners. Every address comes back with a **deliverable / risky / undeliverable** status, a reason code, a **0–100 deliverability score** and an **A–F grade** — **no API key, no subscription, and it never sends a single email**. **$3 per 1,000 verified emails**; a run that verifies nothing bills nothing.

Copy-paste input to start:

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

Only `emails` is required — an array, one address per line, or a single comma/space/newline-separated string.

### What does it check?

For every email address in your list, the actor runs a fast, read-only verification pipeline:

- **Syntax validation** — RFC-style format check (local part, domain shape, and the RFC 5321 length limits: local part ≤ 64, whole address ≤ 254, each domain label ≤ 63 characters)
- **MX record lookup** — resolves the domain's mail servers over DNS-over-HTTPS (Google and Cloudflare resolvers), so it works from any network without port-25 access. A lookup that does not complete is retried once within the run before the address is marked `mx_lookup_failed` (time permitting - if the run's timeout arrives first, the status message says how many were not retried)
- **Disposable domain detection** — a bundled blocklist of **7,900+ temporary-inbox domains** (mailinator, yopmail, guerrillamail, temp-mail and thousands more)
- **Role account detection** — flags generic mailboxes like `info@`, `sales@`, `support@`, `noreply@`, `admin@` that convert poorly and bounce often
- **Typo detection** — `jane@gmial.com` gets `did_you_mean: "jane@gmail.com"` (a common-provider typo map plus a single-edit-distance check against gmail, outlook, yahoo, icloud and other major domains)
- **Mailbox provider detection** — identifies Google Workspace, Microsoft 365, Zoho Mail, Proofpoint, Fastmail and others from the MX hosts
- **Free webmail detection** — labels gmail, outlook, yahoo, icloud, proton and other consumer providers so you can separate B2B from personal addresses
- **Normalization + deduping** — trims, lowercases, and (optionally) drops case-insensitive duplicates before verifying

Verification runs in parallel (configurable concurrency) with per-domain MX caching and one in-flight lookup per domain, so large lists that share domains finish especially fast — 1,000 addresses on 50 domains cost 50 lookups, not 1,000. Rows are delivered to the dataset in batches of up to 500 while the run is still verifying, so a run that hits its timeout keeps everything verified so far.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `emails` | array | *(required)* | List of email addresses to verify. You can paste them one per line, or pass an array. Also accepts a single comma/space/newline-separated string. |
| `concurrency` | integer | `20` | How many emails to verify in parallel (1–100). |
| `dedupe` | boolean | `true` | Drop duplicate addresses (case-insensitive) before verifying. |
| `includeInvalid` | boolean | `true` | Keep malformed addresses in the results (flagged undeliverable) instead of dropping them. |

### Output columns

One dataset row per verified address. Export to CSV, JSON, or Excel, or pull results via the Apify API.

| Column | What it holds |
|---|---|
| `email` | The address exactly as you provided it |
| `normalized` | Trimmed, lowercased form that was verified |
| `status` | `deliverable`, `risky`, or `undeliverable` |
| `score` / `deliverability_score` | 0–100 deliverability score (`score` is the same value, kept for back-compat) |
| `grade` | Letter grade: A ≥ 85, B 70–84, C 50–69, D 20–49, F < 20 |
| `business_class` | `good` / `risky` / `bad` — outreach-value view of the same result |
| `technical_state` | `deliverable` / `undeliverable` / `unknown` (unknown = the DNS lookup itself failed) |
| `reason` | Machine code: `ok`, `role_address`, `mx_lookup_failed`, `no_mx_record`, `disposable_domain`, `invalid_syntax` |
| `syntax_valid` | `true` if the address passed the syntax gate |
| `domain` | The part after `@` |
| `is_free` | Consumer webmail (gmail, outlook, yahoo, ...) |
| `is_role` | Shared alias (`info@`, `sales@`, ...) |
| `is_disposable` | Domain is on the throwaway blocklist |
| `mx_found` | Domain has working mail servers |
| `mx_host` | Highest-priority MX hostname (`null` when no MX resolved) |
| `provider` | Mailbox provider from the MX hosts: `Google Workspace`, `Microsoft 365`, `Proofpoint`, `Mimecast`, `Zoho Mail`, `Fastmail`, `GoDaddy`, or `Other / self-hosted` when the MX hosts are not recognized; `null` only when no MX resolved |
| `did_you_mean` | Typo-corrected suggestion (`null` unless the domain looks like a typo of a major provider) |

A real row:

```json
{
  "email": "john@apify.com",
  "normalized": "john@apify.com",
  "status": "deliverable",
  "score": 95,
  "deliverability_score": 95,
  "grade": "A",
  "business_class": "good",
  "technical_state": "deliverable",
  "reason": "ok",
  "syntax_valid": true,
  "domain": "apify.com",
  "is_free": false,
  "is_role": false,
  "is_disposable": false,
  "mx_found": true,
  "mx_host": "aspmx.l.google.com",
  "provider": "Google Workspace",
  "did_you_mean": null
}
```

### How the status, score and grade are decided

Status and score map to a fixed, transparent rubric:

| status | reason | score | grade | meaning |
|---|---|---|---|---|
| `deliverable` | `ok` | 95 on Google Workspace / Microsoft 365, else 85 | A | Valid syntax, domain has working mail servers, no risk flags |
| `deliverable` | `ok` + `did_you_mean` set | capped at 60 | C | The domain resolves but looks like a typo of a major provider — double-check before sending |
| `risky` | `role_address` | 60 | C | Generic mailbox (`info@`, `sales@`, ...) — deliverable but low-value for outreach |
| `risky` | `mx_lookup_failed` | 20 | D | The DNS lookup did not complete even after the run's one retry (or the run's timeout arrived before the retry could start — the status message says which) — **unknown**, not a confirmed failure. Re-run these rather than discarding them. The status message tells you how many rows ended this way. If the resolvers never answered for *any* domain in the run, these rows are held back instead of delivered (see below) and you are not charged for them. |
| `undeliverable` | `no_mx_record` | 15 | F | Domain has no mail servers — mail cannot be delivered |
| `undeliverable` | `disposable_domain` | 5 | F | Temporary/throwaway domain |
| `undeliverable` | `invalid_syntax` | 0 | F | Malformed address |

#### Example output

Real rows from a run of the verification engine (2026-08-23):

| email | status | score | grade | reason | did\_you\_mean |
|---|---|---|---|---|---|
| john@apify.com | deliverable | 95 | A | ok | |
| info@stripe.com | risky | 60 | C | role\_address | |
| jane@gmial.com | undeliverable | 5 | F | disposable\_domain | jane@gmail.com |
| test@mailinator.com | undeliverable | 5 | F | disposable\_domain | |
| invalid@@bademail | undeliverable | 0 | F | invalid\_syntax | |

(`gmial.com` is both on the disposable blocklist *and* an obvious gmail typo, so it gets flagged twice over.)

### Pricing — what does it cost to verify 1,000 emails?

**$3 per 1,000 verified emails** (Apify pay-per-event: one `result` event per row in the dataset). No subscription, no minimums, and no charge for runs that deliver nothing. A **free Apify plan is enough to try it** — the plan's monthly platform credit covers cleaning a real list before you pay anything. Note that every row pushed to the dataset is a charged result, including rows kept by `includeInvalid`.

### What you get back when a run cannot finish cleanly

The status message on the run always names what happened, in plain words, and a run that delivered nothing ends with "You were not charged.":

- **Run timeout** — the actor reads the run's own timeout and stops starting new addresses about 30 seconds before it; everything verified so far is already in the dataset (delivered in batches of ≤ 500 rows) and billed. The message says how many addresses were left unverified so you can re-run just those, or raise the run's timeout.
- **DNS resolvers down** — a lookup that does not complete is retried once. If some lookups still fail, those rows are delivered as `risky / mx_lookup_failed` and the message tells you how many. If the resolvers never answered for *any* domain in the whole run, the unverified rows are **held back, not delivered and not charged** — "risky" is never used as a euphemism for "unchecked"; the rows that need no DNS (malformed, disposable) are still delivered.
- **Charge limit** — with a `maxTotalChargeUsd` on the run, the actor asks the platform how many rows still fit *before* each batch and delivers exactly the rows you are billed for, never more. The message says how many were left out.
- **Nothing to verify** — an empty `emails` list, or a list where every address is malformed while `includeInvalid` is off, exits SUCCEEDED with a message telling you what to change.

### Every run comes with a report

Every run that delivers at least one row also writes a `REPORT` record to the run's key-value store (Output tab → `REPORT`, or follow the "Report:" link in the status message). It is a single self-contained HTML page — the verdict split, reason codes, mailbox providers, the first 100 rows and the same notes the status message carries — safe to forward or screenshot for a client. The dataset remains the source of truth; the report is a view of exactly the rows that were delivered and billed.

### Run summary for API and scheduled callers

Every run — including zero-row runs — writes a `RUN_SUMMARY` record (key-value store) with the counts an integration needs: `supplied`, `unique`, `duplicates_removed`, `verified`, `delivered`, `charged`, `by_status`, `by_reason`, `malformed_dropped`, `dns_unknown_delivered`, `dns_withheld`, `dns_retried`, `skipped_timeout`, `left_out_budget`, `cause` (`null` on a normal run, otherwise the same reason the status message names), `report_url` and `status_message`. `delivered` and `charged` are equal on a normal run; if they differ, the run log carries a charge warning or MISMATCH line - please report that run. `dns_retried` counts the lookups the retry pass actually started (the run's timeout can cut the retry short; the status message then says how many rows were not retried).

### Verifying at volume? (agencies, lead-gen teams)

This is built for bulk: paste 50,000 addresses and it verifies the lot in one run, priced per result
at a fraction of the mainstream verifiers (many charge $8-$100 per 1,000 at their entry tier; the big SaaS
tools charge $30-80 per 1,000). No subscription, no monthly minimum, no API key - and a run that
verifies nothing bills nothing. If you maintain client lists and re-verify monthly, this is the
cheapest way on the platform to do it. Open an issue if you need a custom volume arrangement.

### Use cases

- **Cold outreach list cleaning** — run every scraped or purchased list through the verifier and keep only `deliverable` rows; bounces above ~2–3% get your domain flagged by Google and Microsoft.
- **Sales prospecting pipelines** — chain it after a lead scraper so every contact you hand to sales already has a verified, scored email.
- **Lead-gen agencies** — deliver client lists with a `status` and `score` column attached; verified lists command higher prices than raw scrapes.
- **CRM hygiene** — export your CRM's email column quarterly, verify it, and archive contacts whose domains no longer accept mail.
- **Form-signup filtering** — batch-check new signups and quarantine disposable domains before they hit your product analytics or drip campaigns.

### FAQ

#### How can I verify an email address without sending an email?

This is exactly that: the actor confirms an address's syntax is valid and its domain runs real mail servers using **DNS lookups only**. Nothing is ever sent, no mailbox is ever contacted, and the address owner never knows a check happened — safe to run on scraped or purchased lists.

#### Can it check an email list for bounces before I send?

That's the core use case. Run the list, keep `status = "deliverable"` rows, and you remove the addresses that are guaranteed to hard-bounce (dead domains, throwaway inboxes, malformed entries) before your ESP ever sees them. Bounces above ~2–3% get your sending domain flagged by Google and Microsoft, so cleaning first protects the domain itself.

#### Does it do a full SMTP handshake with the mail server?

No — and the README won't pretend otherwise. This verifier checks **syntax and MX/DNS only** (plus disposable/role/free classification). It confirms the *domain* accepts mail, not that the specific *mailbox* exists. That's why a clean pass caps at a score of 95, not 100. The upside: it's read-only, sends nothing, needs no proxies or port 25, and never risks your IP reputation. For high-stakes sends, treat `deliverable` as "safe to include", and let your email tool's soft-bounce handling catch the remaining mailbox-level misses.

#### Can a `deliverable` email still bounce?

Yes, occasionally. A domain can have valid MX records while the individual mailbox is full, disabled, or never existed (catch-all domains are the usual culprit). MX-level verification typically removes the large majority of bounces from a scraped list, but it can't guarantee zero.

#### Why are role accounts marked `risky` rather than `undeliverable`?

`info@` and `sales@` mailboxes usually work — they're flagged because they're shared inboxes with low reply rates, and some ESPs penalize campaigns heavy on role addresses. Filter them out with the `is_role` column if your tooling dislikes them, or keep them for local-business outreach where they're often the only address available.

#### How big a list can I verify in one run?

There's no hard input cap. Verification is parallel (up to `concurrency: 100`) and MX lookups are cached per domain, so lists that share domains verify especially fast. Rows reach the dataset in batches of up to 500 while the run is still working, and the actor watches the run's own timeout: it stops taking new addresses shortly before the platform would kill the run, delivers and bills what it verified, and tells you in the status message how many addresses were left unverified. For very large jobs, splitting into runs of a few tens of thousands (or raising the run's timeout) keeps datasets easy to export.

#### What happens to malformed addresses?

With `includeInvalid: true` (the default) they stay in the output flagged `undeliverable / invalid_syntax` with a score of 0 — useful for auditing what was wrong with a source list. Set it to `false` to drop them silently. Note that every row pushed to the dataset is a charged result.

#### Does it need proxies or an API key?

No. MX resolution goes over DNS-over-HTTPS to Google and Cloudflare, which works from any IP, including datacenter IPs, with nothing to configure.

### What this actor does NOT do

- **No SMTP / mailbox-level verification** — it cannot confirm an individual inbox exists or detect catch-all domains (see the FAQ above for why that trade-off is deliberate)
- **It never sends email** — no test messages, no pings, nothing lands in anyone's inbox
- **It doesn't find or guess addresses** — for that, use [Email Pattern Finder](https://apify.com/flash_scraper/email-pattern-finder) and verify its guesses here
- **It doesn't validate phone numbers** — the phone column of the same list goes to [Phone Number Validator](https://apify.com/flash_scraper/phone-number-validator)

### Related actors

- [Phone Number Validator](https://apify.com/flash_scraper/phone-number-validator) — the phone counterpart from the same publisher: validate and E.164-format the phone column of the same lead list, offline
- [Email Pattern Finder](https://apify.com/flash_scraper/email-pattern-finder) — discover a company's email format when you have names but no addresses, then verify the guesses here
- [Local Business Leads](https://apify.com/flash_scraper/local-business-leads) — scrape local business leads with MX-verified emails, a natural upstream source for this verifier

Found a bug or need a feature? Open an issue on the actor's **Issues tab** — typical response within 1 business day.

### Related Flash Scrape actors

- [Multi Job Board Scraper](https://apify.com/flash_scraper/multi-jobboard-scraper) — LinkedIn, Indeed, Glassdoor + 9 more boards, deduplicated, with only-new job alerts
- [Local Business Leads Scraper](https://apify.com/flash_scraper/local-business-leads) — any category, any city, verified emails, lead scores
- [Remote Jobs Aggregator](https://apify.com/flash_scraper/remote-job-aggregator) — 10 keyless remote boards in one deduplicated feed
- [All Flash Scrape actors](https://apify.com/flash_scraper) — same house rules everywhere: pay per delivered row, honest status messages, only-new monitoring, webhook alerts, and a run report on every run.

***

### More from Flash Scrape

Same publisher, same rules: no API keys, pay per row, filters run before billing.

- [Multi Job Board Scraper](https://apify.com/flash_scraper/multi-jobboard-scraper) - LinkedIn, Indeed, Glassdoor and 9 more boards in one deduplicated table
- [Local Business Leads Scraper](https://apify.com/flash_scraper/local-business-leads) - local businesses in any category and city with MX-verified emails, phones and socials
- [Remote Jobs Aggregator](https://apify.com/flash_scraper/remote-job-aggregator) - 10 remote job boards in one feed, only-new-jobs monitoring
- [Creator Leads Scraper](https://apify.com/flash_scraper/creator-leads-scraper) - YouTube, TikTok and Instagram creator emails in one run

# Actor input Schema

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

List of email addresses to verify. You can paste them one per line, or pass an array. Also accepts a single comma/space/newline-separated string.

## `dedupe` (type: `boolean`):

Drop duplicate addresses (case-insensitive) before verifying.

## `includeInvalid` (type: `boolean`):

Keep malformed addresses in the results, flagged undeliverable / invalid\_syntax with score 0, instead of dropping them. Kept rows are charged like any other row; dropped rows are not.

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

How many addresses to verify in parallel (1-100). Addresses on the same domain share one cached MX lookup, so higher values mainly help lists with many distinct domains.

## Actor input object example

```json
{
  "emails": [
    "john@apify.com",
    "info@stripe.com",
    "noreply@gmail.com",
    "test@mailinator.com"
  ],
  "dedupe": true,
  "includeInvalid": true,
  "concurrency": 20
}
```

# 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": [
        "john@apify.com",
        "info@stripe.com",
        "noreply@gmail.com",
        "test@mailinator.com"
    ]
};

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

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

```

## MCP server setup

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