# Email Verifier & Validator | Bulk Email List Verification (`eliai/email-validator`) Actor

Verify email addresses in bulk and clean your list before you send. Checks RFC syntax, live MX records and deliverability, flags role accounts and disposable domains — cutting bounces and protecting sender reputation. No mail is ever sent. $0.001 per email, 500 per run.

- **URL**: https://apify.com/eliai/email-validator.md
- **Developed by:** [Broke to Built](https://apify.com/eliai) (community)
- **Categories:** Lead generation, Automation, MCP servers
- **Stats:** 2 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.80 / 1,000 validated emails

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

## Email Address Validator — bulk email verification with MX, role and disposable checks

**Give it an address or a list, get a per-address verdict back — without sending a single
message.** RFC syntax, whether the domain actually accepts mail (MX lookup with an A/AAAA
fallback), whether it is a role inbox like `info@` or `support@`, and whether the domain is a
known disposable/throwaway provider. Up to 500 addresses per run, built to be called from code
and by AI agents as well as clicked.

**$0.001 per email validated — a tenth of a cent.** No subscription, no seat fee, no minimum.
1,000 addresses cost $1.00.

### What problem this solves

A signup list, a scraped lead export, or a CSV from a form always contains addresses that
will bounce: typos, dead domains, and burner inboxes people use to get past a gate. Sending
to them costs deliverability, and finding out by sending is the expensive way to learn.

The usual alternatives are a paid verification SaaS with a monthly seat, or writing your own
regex plus DNS code and maintaining a disposable-domain list forever. This is the check as a
hosted step you can call from a script, a workflow, or an agent. **No mail is ever sent** —
the Actor does DNS lookups and string analysis, nothing else.

### Who uses it

- **Growth and lifecycle teams** cleaning a list before an email send, to cut bounces.
- **Backend developers** validating signups at registration without adding a vendor SDK.
- **Lead-gen and sales ops** scoring a scraped contact export — separating real company
  inboxes from `info@` catch-alls and burner domains.
- **AI agents** handed a contact list that need a machine-readable verdict per address.
- **No-code / automation builders** (Make, n8n, and similar) that can call a URL but cannot
  do an MX lookup.

### Quick start

```json
{
  "email": "support@github.com"
}
```

That is the whole minimum input. For a list:

```json
{
  "emails": ["info@google.com", "test@mailinator.com", "not-an-email"],
  "maxEmails": 50
}
```

#### All input options

| Field | Type | Required | What it does |
|---|---|---|---|
| `email` | string | one of these | A single address to validate |
| `emails` | string\[] | one of these | A list of addresses. A single string separated by newlines, commas or semicolons also works |
| `maxEmails` | number | no | Hard cap on addresses processed this run. Default **50**, allowed range **1–500** |

`email` and `emails` can both be given — they are merged. Duplicates are removed
case-insensitively, original order is kept, and the list is then cut to `maxEmails`. A run
with no usable address fails immediately rather than charging you for nothing.

### What you get back

One dataset item per address. Every output below is copied verbatim from a real run.

**Example 1 — a role inbox at a live domain**

```json
{
  "email": "support@github.com",
  "valid": true,
  "syntaxValid": true,
  "domainHasMx": true,
  "isRole": true,
  "isDisposable": false,
  "reason": "valid-but-role-account",
  "domain": "github.com",
  "deliverableDomain": true,
  "mxVia": "mx",
  "isPlusAddressed": false,
  "isGmailWithDots": false,
  "normalizedEmail": "support@github.com"
}
```

**Example 2 — a plus-addressed Gmail, with the dedupe key computed**

```json
{
  "email": "jane.doe+news@gmail.com",
  "valid": true,
  "reason": "valid",
  "domain": "gmail.com",
  "isRole": false,
  "isDisposable": false,
  "mxVia": "mx",
  "isPlusAddressed": true,
  "isGmailWithDots": true,
  "normalizedEmail": "janedoe@gmail.com"
}
```

`janedoe@gmail.com` is the same mailbox as `jane.doe+news@gmail.com` at Gmail, so
`normalizedEmail` is the field to dedupe a list on.

**Example 3 — the three ways an address fails**

```json
[
  {
    "email": "test@mailinator.com",
    "valid": false, "syntaxValid": true, "domainHasMx": true,
    "isDisposable": true, "reason": "disposable-domain", "domain": "mailinator.com"
  },
  {
    "email": "not-an-email",
    "valid": false, "syntaxValid": false, "domainHasMx": false,
    "isRole": false, "isDisposable": false, "reason": "missing-local-or-domain"
  },
  {
    "email": "hello@thisdomaindoesnotexist12345.io",
    "valid": false, "syntaxValid": true, "domainHasMx": false,
    "deliverableDomain": false, "mxVia": "none",
    "reason": "domain-not-deliverable (no MX/A records)"
  }
]
```

A burner domain, a typo, and a dead domain — three different `reason` values so your code can
treat them differently. The whole batch above cost **$0.005**.

#### Field reference

- `valid` — the headline verdict. True when the domain can receive mail **and** the domain
  is not on the disposable list. Note that a role account is still `valid: true` — it is a
  real inbox, so the judgment call about whether to mail it stays yours.
- `syntaxValid` — passed the syntax rules (254-char total, 64-char local part, dot placement,
  RFC-pragmatic pattern).
- `domainHasMx` — the domain published MX records.
- `deliverableDomain` — MX records, or an A/AAAA record acting as an implicit MX.
- `mxVia` — how it resolved: `mx`, `a`, `aaaa`, or `none`.
- `isRole` — the local part is one of ~30 known role names (`info`, `admin`, `support`,
  `sales`, `noreply`, `billing`, `careers`, `postmaster`, and so on).
- `isDisposable` — the domain is on the built-in throwaway-provider list.
- `reason` — one short machine-readable string explaining the verdict, e.g. `valid`,
  `valid-but-role-account`, `disposable-domain`, `no-mx-but-A-fallback (implicit MX)`,
  `domain-not-deliverable (no MX/A records)`, or a specific syntax failure such as
  `local-part-too-long (>64)`.
- `isPlusAddressed` / `isGmailWithDots` — signals for duplicate detection across sub-addresses.
- `normalizedEmail` — lower-cased, `+tag` stripped, and for `gmail.com` / `googlemail.com`
  dots removed from the local part. Useful as a dedupe key across a list.

Addresses that fail the syntax check return the same shape with `valid: false`,
`syntaxValid: false` and a `reason`, so one bad row in a batch of 500 never kills the rest.

### Call it from code

**curl** — synchronous run, verdicts straight back:

```bash
curl -X POST "https://api.apify.com/v2/acts/eliai~email-validator/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"emails":["jane@example.com","test@mailinator.com"]}'
```

**Python** (`pip install apify-client`):

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("eliai/email-validator").call(
    run_input={"emails": ["jane@example.com", "info@example.com"], "maxEmails": 500}
)
keep = [r["email"] for r in client.dataset(run["defaultDatasetId"]).iterate_items()
        if r["valid"] and not r["isRole"]]
print(keep)
```

**Node.js** (`npm install apify-client`):

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const run = await client.actor('eliai/email-validator').call({ email: 'jane@example.com' });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items[0].valid, items[0].reason);
```

### Use it as an AI agent tool

This Actor is callable over **Apify MCP**, so an agent can check an address mid-task without
you writing an integration. The shape an agent needs:

- **Tool:** this Actor
- **Input:** `{ "emails": ["a@x.com", "b@y.com"] }`
- **Returns:** one verdict object per address, with a `valid` boolean and a `reason` string

The `reason` field is deliberately a short fixed vocabulary rather than prose, so an agent
can branch on it without parsing free text.

### Pricing

Pay per event, one event: `email-validated`.

| Event | What one event covers | Price |
|---|---|---|
| `email-validated` | **One address** given a verdict: syntax, DNS MX/A lookup, role and disposable checks | **$0.001** |

50 addresses cost $0.05. A full 500-address run costs $0.50. A thousand addresses cost $1.00.
There is no start fee and no monthly fee.

You are charged once per address the Actor produces a verdict for, and that includes addresses
that come back invalid — a "this one is junk" answer is the answer you paid for. Addresses
skipped as duplicates or cut off by `maxEmails` are never charged, and an address that hits an
unexpected internal error is written to the dataset but not charged.

Honest comparison: dedicated verification SaaS products (ZeroBounce, NeverBounce, Hunter and
similar) charge roughly $0.004-$0.01 per address and do an **SMTP mailbox probe** this actor
does not do — they can often tell you the specific mailbox exists. If mailbox-level certainty
is what you are buying, buy that instead. This is the cheap DNS-and-syntax layer that removes
typos, dead domains and burner addresses before you pay anyone per-address for the deep check.

### When NOT to use this

- **You need proof that a specific mailbox exists.** There is no SMTP handshake. This verifies
  the *domain* can receive mail, never that `jane@` is a real inbox at it.
- **You need catch-all detection, or to know whether a mailbox is full or deleted.** Not
  detected, at all.
- **Your list is non-English or international.** Role names are ~30 English words, and
  internationalized domains and non-ASCII local parts fail the syntax check outright.
- **You want the disposable list to be current.** It is a fixed built-in list of about 50 known
  providers. Brand-new burner domains will pass as clean.
- **You are validating one address inside your own app on every signup.** A regex plus a DNS
  lookup is a few lines of code and zero latency in your own process. Use this for lists and
  hosted workflows, not for a hot signup path.
- **You are about to delete addresses based only on this.** A DNS hiccup reads as a dead
  domain; re-check failures before destroying data.

### Honest limits

Worth knowing before you run it, so nothing surprises you:

- **No SMTP handshake, so no mailbox-level proof.** This validates the *domain* can receive
  mail; it cannot tell you that `jane@` exists at that domain. Nothing here detects
  catch-all domains, full mailboxes, or an address that was deleted last week. If you need
  mailbox-level certainty, this is not that tool.
- **The disposable-domain list is built in and fixed** (about 50 well-known providers:
  mailinator, guerrillamail, yopmail, 10minutemail, temp-mail, and similar). New burner
  domains appear constantly and will not be caught until the list is updated. Treat
  `isDisposable: false` as "not on the list", not as "definitely not disposable".
- **The role-account list is English and about 30 names.** `ventas@` or `kontakt@` will not
  be flagged.
- **ASCII addresses only.** Internationalized domains (`müller.de`), non-ASCII local parts,
  quoted local parts (`"john doe"@x.com`) and IP-literal domains (`user@[192.168.1.1]`) all
  fail the syntax check. The TLD must be 2–63 ASCII letters.
- **A DNS failure looks like a dead domain.** There is no retry: if the lookup times out or
  the resolver hiccups, that address comes back `mxVia: "none"` and
  `domain-not-deliverable`. On a large run, re-check anything that fails that way before
  deleting it from your list.
- **Lookups run one at a time**, so a 500-address run takes noticeably longer than a
  50-address one. Cap is 500 per run — for bigger lists, split them across runs.
- **`normalizedEmail` is a convenience, not a rule.** RFC-wise, local parts may be
  case-sensitive and only some providers ignore dots or `+tags`. Use it for deduping your
  own list, not for deciding two addresses are the same person.
- **A role inbox counts as valid.** If you want to exclude them, filter on `isRole` yourself.

### FAQ

#### How do I check if an email address is valid without sending an email?

Give this Actor the address. It checks the syntax, then does a DNS MX lookup on the domain
to confirm the domain accepts mail, and flags role and disposable addresses. No message is
ever sent, so nothing lands in anyone's inbox and your sending reputation is untouched.

#### Can I validate a whole list of emails at once?

Yes — pass them in `emails`, up to 500 per run. Each address becomes its own dataset item.
Duplicates are removed case-insensitively before anything is processed or charged.

#### Does it tell me whether the specific mailbox exists?

No. It verifies that the domain can receive mail, not that a particular mailbox does. There
is no SMTP handshake, so a well-formed address at a live domain comes back valid even if
that exact inbox was never created.

#### How does it detect disposable or temporary email addresses?

The domain is matched against a built-in list of about 50 known throwaway providers
(mailinator, guerrillamail, yopmail, 10minutemail and similar). A match sets
`isDisposable: true` and forces `valid: false`. The list is fixed, so brand-new burner
domains can slip through.

#### What is a role account and why is it flagged?

A role account is a shared inbox like `info@`, `support@`, `sales@` or `noreply@` rather
than one person's address. It gets `isRole: true` because those addresses behave differently
in outreach and often should not receive personal or marketing mail. It is still returned as
`valid: true` — filtering them out is your call.

#### What happens if an address has a typo or is not an email at all?

That item comes back with `syntaxValid: false`, `valid: false` and a `reason` naming the
specific failure (for example `local-part-too-long (>64)` or `invalid-dot-placement-in-local`).
The run continues and every other address is still checked.

#### Can an AI agent call this?

Yes — it is exposed through Apify MCP as an agent tool. The output is a fixed set of booleans
plus a short `reason` string, which an agent can branch on directly. See "Use it as an AI
agent tool".

#### How much does it cost to validate 1,000 emails?

$1.00 — it is $0.001 per address validated, with no monthly fee. Because a single run is
capped at 500 addresses, 1,000 means two runs.

#### Will this reduce my bounce rate?

It removes the bounces you can detect without sending: typos, addresses at domains with no
mail server, and known throwaway domains. It cannot remove bounces caused by a mailbox that
does not exist at a live domain — that needs an SMTP probe, which this does not do.

#### Can I use it to check emails at signup, in real time?

You can call it synchronously from your backend, and a single address usually returns in about
a second. But if you control the code path, a local regex plus a DNS MX lookup is faster and
free. This is at its best on lists.

#### Why did a domain I know is real come back as not deliverable?

Almost always a DNS lookup that failed or timed out. There is no retry, so a transient
resolver problem is reported as `mxVia: "none"`. Re-run those addresses before treating the
domain as dead.

### Who made this

[Broke to Built](https://broke2builtai.com) — a company of machines, building things
it gives away. This is one of them; the rest are free too.

### For AI agents

This Actor is built to be called by software, not just by people.

- **Mount it directly as an MCP tool** — no Store search, no ranking, just this one tool:
  `https://mcp.apify.com/?actors=eliai/email-validator`
- **Or call it over HTTP** and get the results in the same request:
  `POST https://api.apify.com/v2/acts/eliai~email-validator/run-sync-get-dataset-items`
- **Pay with x402, without an Apify account.** This Actor is whitelisted for agentic payments, so an agent holding USDC on Base can buy a prepaid token and spend it here. The minimum purchase is $1, the token balance is an absolute spending cap, and it expires 14 days after purchase.
- **Costs are predictable before you call.** Pricing is pay-per-event (see Pricing above), so an agent can budget a run in advance instead of discovering the bill afterwards.
- **Send only the field you mean.** If you pass the bulk field, it is used on its own; the single-value field is a fallback, never merged into your request. You are charged for the items you sent and nothing else.

# Actor input Schema

## `email` (type: `string`):

A single email address to validate (syntax + domain MX).

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

List of email addresses to validate in one run. When you set this, it is used on its own and the single-value field above is ignored, so you are only charged for the items you sent.

## `maxEmails` (type: `integer`):

Hard cap on how many emails to process per run (1-500).

## Actor input object example

```json
{
  "email": "support@github.com",
  "emails": [
    "info@google.com",
    "test@mailinator.com",
    "not-an-email"
  ],
  "maxEmails": 50
}
```

# Actor output Schema

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

Every item this run produced, as JSON.

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

The same items as a spreadsheet-ready CSV.

# 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 = {
    "email": "support@github.com",
    "emails": [
        "info@google.com",
        "test@mailinator.com",
        "not-an-email"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("eliai/email-validator").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 = {
    "email": "support@github.com",
    "emails": [
        "info@google.com",
        "test@mailinator.com",
        "not-an-email",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("eliai/email-validator").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 '{
  "email": "support@github.com",
  "emails": [
    "info@google.com",
    "test@mailinator.com",
    "not-an-email"
  ]
}' |
apify call eliai/email-validator --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,eliai/email-validator"
        }
    }
}

```

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/ij3LHnVocy420Rnph/builds/2MBLgmDdgs0jSE1Mh/openapi.json
