# Bulk Email Verifier & Validator — $0.80/1k (`ragsbit/email-verifier`) Actor

Email validation in bulk for $0.80 per 1,000: syntax, MX/DNS, disposable, role-account and typo checks (gmai.com → gmail.com). No SMTP probing.

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

## Pricing

from $0.80 / 1,000 checked emails

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

### Bulk Email Verifier: clean your email list without sending a single email

**Bulk Email Verifier** checks every address on your email list for **syntax errors, duplicates, missing mail servers (MX records), disposable domains, role accounts, typos in popular domains and free email providers**, suggests the fix for typos such as `gmai.com`, and tells you which addresses are **valid, risky, invalid or unknown**. Paste a list or send it through the API, and get a clean table you can download as CSV, Excel or JSON.

It is built for anyone about to send to a list: cold outreach, newsletters, CRM clean-ups. A bad list costs you twice, once in bounces and again in sender reputation; most sending platforms ask you to keep hard bounces under 2%. Checks run on public DNS only: **no SMTP probing, no test emails, no mail server is ever contacted.** Free mailbox providers worldwide are recognized out of the box: Gmail, Outlook, Yahoo, iCloud, Proton, GMX, Yandex, QQ, Naver and more than 100 others.

**Got your list from a lead scraper?** Leads from Google Maps, LinkedIn or Apollo-style scrapers often include role inboxes (info@, sales@), dead domains and throwaway addresses. Export the email column, paste it into **Pasted email list**, and send only to what comes back `valid`. At **$0.80 per 1,000 addresses**, checking a 5,000-lead list costs $4.

**Already pay for an SMTP verifier such as NeverBounce or ZeroBounce?** Use this as a cheap first pass: it catches malformed addresses, typos such as gmai.com or hotmial.com, dead domains, disposable and role inboxes at $0.80 per 1,000, so you pay SMTP prices only for the addresses that are left.

The easiest way to try it: open the Actor, keep the example addresses in the input, click **Start**, and look at the **Output** tab. To automate it, connect it to Zapier, Make or n8n, for example to check new rows in a Google Sheet.

### What does the email verifier check?

| Check | How it works | Result field |
| --- | --- | --- |
| Syntax and normalization | Practical RFC 5321/5322 rules: valid characters, no double dots, length limits, valid domain labels. Trims whitespace and invisible copy-paste characters, lowercases the domain, converts international domains (IDN) to punycode, accepts `Name <email>` | `normalized`, `reasons` |
| Duplicates | Case-insensitive match after normalization. With **Remove duplicates** on, repeats are dropped and not charged | `reasons` |
| Mail server (MX) | DNS lookup of the domain's MX records. Without MX records the domain's A/AAAA record is used as the implicit mail host (RFC 5321). A null MX (RFC 7505) means the domain accepts no mail | `mxFound` |
| Disposable domain | Matches the domain and its parent domains against a dated open-source list of more than 10,000 throwaway email services (mailinator.com, guerrillamail.com, yopmail.com...) | `disposable` |
| Role account | Flags mailboxes that belong to a function rather than a person: admin@, info@, sales@, support@, noreply@, no-reply@, webmaster@, cs@, recruit@... | `roleAccount` |
| Domain typo | Flags domains one typing slip away from a popular mailbox domain (gmai.com, hotmial.com, yahooo.com, gmail.con) and suggests the likely address, for example `mike@gmail.com` for `mike@gmai.com`. The address itself is never changed | `didYouMean` |
| Free email provider | Flags consumer mailbox providers: gmail.com, outlook.com, hotmail.com, yahoo.com, icloud.com, proton.me, gmx.de, yandex.ru, qq.com, naver.com... | `freeProvider` |

#### What it does not do

- **No SMTP verification.** The Actor never connects to a mail server, never sends an email and never asks a server whether a mailbox exists. That keeps your IP and domain reputation safe, but it also means it cannot confirm that a specific mailbox exists, detect catch-all domains or spot spam traps.
- It does not find or scrape email addresses. It only checks the list you provide.

### How to verify an email list

1. Click **Try for free** (or **Start**) on this page and sign in to Apify.
2. Add your addresses: type them into **Email addresses**, or paste a whole column from Excel, Google Sheets or your CRM into **Pasted email list** (one per line, or separated by commas or semicolons).
3. Keep the checks you need switched on. All of them are on by default.
4. Click **Start**. Each domain is looked up only once per run, so lists with many addresses on the same providers finish quickly.
5. Open the **Output** tab and filter by `status`, or export the results as CSV, Excel, JSON or XML. You can also run the Actor through the Apify API, on a schedule, or from integrations such as Make, Zapier and n8n.

### Input

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `emails` | array of strings | (empty) | One address per item. |
| `emailsText` | string | (empty) | Pasted text: one address per line, or comma/semicolon separated. Combined with `emails`. |
| `checkMx` | boolean | `true` | Look up MX records (with the A/AAAA fallback). |
| `checkDisposable` | boolean | `true` | Flag disposable domains. |
| `checkRoleAccount` | boolean | `true` | Flag role accounts. |
| `checkTypos` | boolean | `true` | Flag likely typos of popular mailbox domains and suggest the fix in `didYouMean`. |
| `dedupe` | boolean | `true` | Keep only the first occurrence of each address. |
| `maxConcurrency` | integer | `50` | Parallel DNS lookups (1 to 200). Each domain is looked up once per run. |

Example input:

```json
{
    "emails": [
        "Jane.Doe@Gmail.com",
        "sales@outlook.com",
        "temp-inbox@mailinator.com",
        "john@example.com",
        "user@münchen.de",
        "mike@gmai.com",
        "not-an-email"
    ],
    "emailsText": "Lee <lee@yahoo.com>; info@icloud.com\njane.doe@gmail.com",
    "checkMx": true,
    "checkDisposable": true,
    "checkRoleAccount": true,
    "checkTypos": true,
    "dedupe": true
}
```

### Output

The Actor writes **one item per checked address** to the run's dataset, in input order:

```json
[
    {
        "email": "Jane.Doe@Gmail.com",
        "normalized": "Jane.Doe@gmail.com",
        "status": "valid",
        "reasons": [],
        "didYouMean": null,
        "domain": "gmail.com",
        "mxFound": true,
        "disposable": false,
        "roleAccount": false,
        "freeProvider": true
    },
    {
        "email": "sales@outlook.com",
        "normalized": "sales@outlook.com",
        "status": "risky",
        "reasons": ["role_account"],
        "didYouMean": null,
        "domain": "outlook.com",
        "mxFound": true,
        "disposable": false,
        "roleAccount": true,
        "freeProvider": true
    },
    {
        "email": "temp-inbox@mailinator.com",
        "normalized": "temp-inbox@mailinator.com",
        "status": "risky",
        "reasons": ["disposable_domain"],
        "didYouMean": null,
        "domain": "mailinator.com",
        "mxFound": true,
        "disposable": true,
        "roleAccount": false,
        "freeProvider": false
    },
    {
        "email": "john@example.com",
        "normalized": "john@example.com",
        "status": "invalid",
        "reasons": ["null_mx"],
        "didYouMean": null,
        "domain": "example.com",
        "mxFound": false,
        "disposable": false,
        "roleAccount": false,
        "freeProvider": false
    },
    {
        "email": "user@münchen.de",
        "normalized": "user@xn--mnchen-3ya.de",
        "status": "risky",
        "reasons": ["implicit_mx"],
        "didYouMean": null,
        "domain": "xn--mnchen-3ya.de",
        "mxFound": true,
        "disposable": false,
        "roleAccount": false,
        "freeProvider": false
    },
    {
        "email": "mike@gmai.com",
        "normalized": "mike@gmai.com",
        "status": "risky",
        "reasons": ["possible_typo"],
        "didYouMean": "mike@gmail.com",
        "domain": "gmai.com",
        "mxFound": true,
        "disposable": false,
        "roleAccount": false,
        "freeProvider": false
    },
    {
        "email": "not-an-email",
        "normalized": null,
        "status": "invalid",
        "reasons": ["syntax_no_at_sign"],
        "didYouMean": null,
        "domain": null,
        "mxFound": null,
        "disposable": null,
        "roleAccount": null,
        "freeProvider": null
    }
]
```

| Field | Meaning |
| --- | --- |
| `email` | The entry as you supplied it (surrounding whitespace removed). |
| `normalized` | Cleaned address: local part as written, domain lowercased and in ASCII (punycode). `null` when the syntax is invalid. |
| `status` | `valid`, `invalid`, `risky` or `unknown` (rules below). |
| `reasons` | Codes that explain the status. Empty when nothing was found. |
| `didYouMean` | The address with the likely intended domain when the domain looks like a typo of a popular mailbox domain (`mike@gmail.com` for `mike@gmai.com`). `null` when no typo was found, the check is off or the syntax is invalid. |
| `domain` | Domain of the address in ASCII form. |
| `mxFound` | `true` if the domain can receive mail, `false` if it cannot, `null` if not checked or DNS did not answer. |
| `disposable`, `roleAccount`, `freeProvider` | `true` or `false`; `null` when the check is off or the syntax is invalid. |

#### Status rules

The first matching rule wins:

| Status | When | What to do |
| --- | --- | --- |
| `invalid` | Malformed address (`syntax_*`), the domain does not exist (`domain_not_found`), has a null MX (`null_mx`) or has no mail host (`no_mail_host`) | Remove it |
| `risky` | Disposable domain (`disposable_domain`), role account (`role_account`), likely domain typo (`possible_typo`), or no MX records so mail relies on the A/AAAA fallback (`implicit_mx`) | Review it, fix typos with `didYouMean`, or leave it out of cold campaigns |
| `unknown` | Nothing wrong was found, but DNS timed out or failed (`dns_timeout`, `dns_error`) | Check it again later |
| `valid` | Every enabled check passed | Keep it |

`duplicate` appears only when **Remove duplicates** is off; it marks a repeated address and does not change the status.

#### Reason codes

| Code | Meaning |
| --- | --- |
| `syntax_no_at_sign` | There is no @. |
| `syntax_invalid_local_part` | The part before @ is empty or has spaces, quotes, a second @, or leading, trailing or double dots. |
| `syntax_non_ascii_local_part` | The part before @ uses non-ASCII characters (SMTPUTF8), which most mail systems reject. |
| `syntax_local_part_too_long` | The part before @ is longer than 64 characters. |
| `syntax_invalid_domain` | The domain is malformed: one label only, invalid characters, an IP address, or a trailing dot. |
| `syntax_too_long` | The whole address is longer than 254 characters. |
| `domain_not_found` | The domain does not exist in DNS. |
| `null_mx` | The domain publishes a null MX: it accepts no email. |
| `no_mail_host` | The domain exists but has no MX and no A/AAAA record. |
| `implicit_mx` | No MX records; mail would go to the domain's own A/AAAA host. |
| `disposable_domain` | The domain belongs to a disposable email service. |
| `role_account` | The mailbox is a role or team address. |
| `possible_typo` | The domain is one typing slip away from a popular mailbox domain; `didYouMean` holds the likely address. |
| `dns_timeout`, `dns_error` | DNS did not answer in time, or failed. |
| `duplicate` | The address appeared earlier in the input. |

### How much does email verification cost?

This Actor uses **pay-per-event** pricing: **$0.80 per 1,000 checked addresses** ($0.0008 per address), plus Apify's standard Actor start fee of $0.00005 per run. There is no subscription and no charge for platform usage on top.

| List size | Price |
| --- | --- |
| 1,000 addresses | $0.80 |
| 10,000 addresses | $8.00 |
| 100,000 addresses | $80.00 |

- Every address in the output is billed as one `email-checked` event, including invalid ones: finding bad addresses is the point of the check.
- Duplicates removed by **Remove duplicates** and blank lines are not charged.
- Set a maximum cost per run (in the run options or through the API) to cap your spending. The Actor checks only as many addresses as the limit covers, then stops.
- $1 checks 1,250 addresses, so a test run with the example input costs less than one cent.

### Privacy and data handling

- **Only domain names are sent to DNS.** The part before the @ never leaves the run, and no third-party verification API is used.
- **Nothing is stored beyond the run's default dataset.** Apify keeps the run input and the results under your account, subject to your data retention settings; you can delete them at any time. The Actor writes no other records and keeps no copy of your list.
- Run logs contain counts only, never email addresses.
- You decide which addresses to check. Make sure you have a lawful basis to process them, for example under GDPR.

### Limits

- A `valid` result means the address is well formed and its domain accepts mail. Without SMTP probing, the Actor cannot tell whether the individual mailbox exists or whether the domain is a catch-all.
- Disposable services appear every day. The bundled list is a dated snapshot and is refreshed with new releases of the Actor.
- The free-provider and role-account lists are curated, not exhaustive. Role detection matches the whole local part (with any `+tag` removed), so `info@` is flagged but `info.kim@` is not.
- The typo check compares domains with about 40 popular mailbox domains (Gmail, Yahoo, Outlook, Hotmail, iCloud, AOL, GMX, Yandex, QQ and others). A misspelled company domain is caught only when it does not exist or accepts no mail. A rare real domain one keystroke away from a popular one (for example hmail.com) is marked `risky` with a suggestion; nothing is ever changed or removed for you.
- Quoted local parts (`"john doe"@example.com`), IP-address domains and non-ASCII local parts are reported as invalid because almost no mail system accepts them in practice. Gmail dot and plus variants are not merged.
- A run with the default 256 MB of memory comfortably handles about 100,000 addresses. For bigger lists, raise the run memory (up to 1 GB) or split the list into several runs.

### FAQ

#### Does this tool send emails or connect to mail servers?

No. It only performs DNS lookups for domain names. No SMTP connection is opened and no email is sent, so the recipients' mail servers never see your list.

#### Why is a real-looking address marked as risky?

Risky addresses can usually receive mail, but sending to them is not recommended: disposable inboxes disappear after a short time, role accounts such as info@ or sales@ are usually shared by several people rather than read by one contact, domains without MX records often bounce, and a domain that looks like a typo of a popular provider (gmai.com) is usually not the address your contact meant. Check `reasons` to see which rule applied.

#### Does it catch typos like gmai.com?

Yes. Domains one typing slip away from a popular mailbox domain, such as gmai.com, hotmial.com, yahooo.com or gmail.con, get the reason `possible_typo` and a suggested address in `didYouMean` (mike@gmai.com gives mike@gmail.com). The Actor never changes the address itself: fix it, confirm it with the contact, or leave it out.

#### What does unknown mean?

The address passed every other check, but DNS for its domain timed out or returned an error, so the mail-server check could not finish. Run those addresses again later.

#### Does it recognize free email providers outside the US?

Yes. Every domain's MX records are checked the same way, and the free-provider list covers the big international webmail services and consumer ISPs: for example gmx.de and web.de in Germany, orange.fr and laposte.net in France, yandex.ru and mail.ru, qq.com and 163.com, naver.com and daum.net, rediffmail.com and bigpond.com.

#### How do I check the emails from a lead scraper run?

Open the scraper's dataset, export it as CSV, copy the email column and paste it into **Pasted email list**. Through the API, read the emails from the scraper's dataset and pass them as the `emails` array. Duplicates are removed before checking and are not charged.

#### Can I use it through the API or in my automation?

Yes. Start the Actor with the Apify API or a client library, pass the input JSON shown above, and read the results from the run's dataset. You can also schedule runs or connect the Actor to Make, Zapier, n8n and the other integrations available on Apify.

#### Which disposable email list is used?

The [disposable-email-domains](https://github.com/disposable-email-domains/disposable-email-domains) project, dedicated to the public domain under [CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/). The Actor bundles a snapshot taken on 2026-10-31 (10,083 domains) and never downloads anything while it runs.

### Credits

Disposable domain data: [disposable-email-domains](https://github.com/disposable-email-domains/disposable-email-domains), CC0 1.0 Universal. Thank you to its maintainers and contributors.

### Support

Bulk Email Verifier is built and maintained by Ragsbit. Found a problem or need a feature? Open an issue on the **Issues** tab of this Actor and we will reply as soon as we can.

# Actor input Schema

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

One address per item. Addresses copied from an email client together with a display name are accepted too. Combined with the pasted list below.

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

Paste a list copied from a spreadsheet, CRM export or email client: one address per line, or separated by commas or semicolons. Combined with the addresses above.

## `checkMx` (type: `boolean`):

Look up the domain's MX records over DNS. Without MX records the domain's A/AAAA record is used as the implicit mail host (RFC 5321); a null MX means the domain accepts no mail. Only the domain name is sent to DNS.

## `checkDisposable` (type: `boolean`):

Flag temporary or throwaway email services (for example mailinator.com) using a bundled, dated snapshot of an open-source disposable-domain list.

## `checkRoleAccount` (type: `boolean`):

Flag mailboxes that belong to a function rather than a person, such as info@, sales@, support@, admin@ or noreply@.

## `checkTypos` (type: `boolean`):

Flag addresses whose domain looks like a typing slip of a popular mailbox domain, such as gmai.com, hotmial.com or gmail.con, and put the likely address in didYouMean. The address itself is never changed.

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

Keep only the first occurrence of each address (case-insensitive, after normalization). Removed duplicates are not checked and not charged. When off, every entry is returned and repeats are marked with the reason "duplicate".

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

How many domains are looked up at the same time. Results are cached per domain, so each domain is looked up only once per run.

## Actor input object example

```json
{
  "emails": [
    "jane.doe@gmail.com",
    "sales@outlook.com",
    "tester@mailinator.com",
    "john@example.com",
    "mike@gmai.com",
    "not-an-email"
  ],
  "emailsText": "jane.doe@gmail.com\nsales@outlook.com; info@yahoo.com",
  "checkMx": true,
  "checkDisposable": true,
  "checkRoleAccount": true,
  "checkTypos": true,
  "dedupe": true,
  "maxConcurrency": 50
}
```

# Actor output Schema

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

One item per checked email address with status, reasons and check results.

# 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@gmail.com",
        "sales@outlook.com",
        "tester@mailinator.com",
        "john@example.com",
        "mike@gmai.com",
        "not-an-email"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("ragsbit/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@gmail.com",
        "sales@outlook.com",
        "tester@mailinator.com",
        "john@example.com",
        "mike@gmai.com",
        "not-an-email",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("ragsbit/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@gmail.com",
    "sales@outlook.com",
    "tester@mailinator.com",
    "john@example.com",
    "mike@gmai.com",
    "not-an-email"
  ]
}' |
apify call ragsbit/email-verifier --silent --output-dataset

```

## MCP server setup

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