# Bulk Email Verifier: Syntax, Domain, MX & Role Checks (`pavel.tashev/email-verifier`) Actor

Clean an email list before you import it. Checks syntax, whether the domain exists and accepts mail (MX), disposable providers and role addresses, and says plainly what it could not determine.

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

## Pricing

from $10.00 / 1,000 email address checkeds

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

### What does Bulk Email Verifier do?

**Clean an email list before you import it.** Paste your addresses and get back, for each one, whether it is
well formed, whether the domain exists and accepts mail, whether it is a throwaway address, and whether it is a
role mailbox like `info@` rather than a person — with a stable reason code for every verdict.

It runs on the Apify platform, so you get an API, scheduling, and integrations with Make, Zapier and n8n.

### What it checks, and what it deliberately does not claim

| Check | What it tells you |
|---|---|
| **Syntax** | The address is shaped like something a mail system will route — not merely RFC-legal |
| **Domain exists** | The domain resolves at all |
| **MX records** | The domain actually accepts mail, and which servers handle it |
| **Disposable provider** | Throwaway mailboxes (mailinator, guerrillamail, yopmail and similar) |
| **Role address** | `info@`, `sales@`, `noreply@` and friends — real, but they behave differently in bulk |
| **Free provider** | Consumer mailboxes, useful to know on a B2B list |
| **SPF and DMARC** | Whether the domain publishes a sender policy, and how strict DMARC is |
| **Domain age** | How long ago the domain was registered (RDAP) — a week-old domain on a mailing list is a pattern |

Every address also gets a **0–100 score**. Every deduction maps to a reason code that is also in the row, so
the number can be explained rather than trusted: a role address costs 25, a disposable domain 70, a missing
SPF record 10. No opaque model, no score you cannot audit.

**SPF, DMARC and domain age are checked once per domain, not once per address** — a list of 5,000 addresses
across 300 domains costs 300 lookups.

**It does not perform an SMTP handshake, so it never claims a specific mailbox exists.** Apify blocks outbound
port 25, which makes that check impossible from here — and rather than quietly imply otherwise, every result
carries `mailboxChecked: false` and `deliverable` is defined as *"the domain accepts mail and the address is
well formed."* Catch-all detection needs the same handshake and is likewise not offered.

If you need mailbox-level confirmation, this Actor is the wrong tool and says so. For the far more common job —
removing the typos, dead domains, throwaways and role accounts that make up most of the junk in a real list —
it is exactly the right one.

### Three ways to give it the list

- **Paste addresses** into *Email addresses* for a quick check.
- **Point it at a file** — *Read addresses from a file* takes links to CSV, JSON or plain text. Every
  address-shaped value is used, so a CSV export with headers and quoting works with no reformatting. One
  consequence worth knowing: run it over a *raw email message* and it will also pick up Message-ID headers,
  which are address-shaped by design. Supply a list or a CSV for clean results.
- **Check domains instead** — *Domains to check* answers "can this domain receive mail at all?" with MX, SPF,
  DMARC and age, before you have a single address for it.

### The per-domain summary

`VERIFICATION_REPORT` groups every result by domain, **worst first**, because a list with 400 addresses at one
dead domain is one problem and not four hundred. Each domain shows how many addresses it holds, the spread of
statuses, its average score, and only the reasons that are genuinely about *the domain* — a role address is a
fact about one mailbox, so it never appears against the domain everybody else shares.

### Status values

- **deliverable** — the domain accepts mail and the address is well formed.
- **risky** — it can receive mail, but you may not want it: disposable domain, role address, or a domain that
  accepts mail only through the RFC's implicit-MX rule.
- **invalid** — it cannot receive mail: bad syntax, or the domain does not exist and has no MX.
- **unknown** — DNS did not answer. **Try again rather than deleting the address**; this is never charged.
- **duplicate** — already in your list. Reported once, charged once.

### Your list stays yours

The addresses are your data and you remain the controller of them. We act only as a processor: results go to
your run's dataset, nothing is retained by us beyond it, no address is looked up anywhere except its own
domain's DNS, and nothing is ever enriched, appended or combined into any list of ours.

### How much does it cost?

Charged per address actually checked. **Duplicates, syntax rejections and DNS failures are never charged** —
a result we could not produce is not a result you should pay for. Domains are looked up once per run and
cached, so a list of 5,000 addresses across 300 domains costs 300 lookups, not 5,000.

### Tips

- Send the whole list in one run: the domain cache is per run, so splitting it costs more.
- Treat **risky** as a decision, not a rejection. `info@` addresses are often exactly who you meant to write to.
- Re-run **unknown** rows later. They mean our DNS query failed, not that the address is bad.

### FAQ

**Why no mailbox verification?** Outbound port 25 is blocked on the Apify platform. Claiming a mailbox exists
without the handshake would be a guess, and this Actor does not guess.

**Is the disposable-domain list complete?** No, and no such list is. There are thousands of these domains and
they change weekly. This catches the common ones; `disposable-domain` being absent is not proof of the opposite.

# Actor input Schema

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

The addresses to check, one per line. This is your list: we check it and return the results to your dataset. Nothing is stored, enriched or added to any other list. Leave empty if you are supplying a file or a list of domains instead.

## `fileUrls` (type: `array`):

Links to CSV, JSON or plain-text files holding the addresses. Every address-looking value in the file is used, so a CSV export with headers and quoting works without reformatting.

## `domains` (type: `array`):

Check whether a domain can receive mail at all, without an address — MX, SPF, DMARC and age.

## `deduplicate` (type: `boolean`):

Repeated addresses are reported once and charged once.

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

Domains are looked up once each and cached for the run, so a big list of few domains is fast.

## `checkDomainPolicy` (type: `boolean`):

Reads the domain’s own sender policy — whether it publishes SPF and DMARC, and how strict DMARC is. Costs no extra request per address: it is one lookup per domain.

## `checkDomainAge` (type: `boolean`):

Looks up when the domain was registered (RDAP). A domain registered days ago and already on a list is the classic throwaway-sender pattern. Off by default: it adds one request per domain.

## Actor input object example

```json
{
  "emails": [
    "jane.doe@apify.com",
    "info@python.org",
    "nobody@mailinator.com",
    "not-an-email",
    "someone@this-domain-does-not-exist-9x8v7q.com"
  ],
  "deduplicate": true,
  "concurrency": 20,
  "checkDomainPolicy": true,
  "checkDomainAge": false
}
```

# Actor output Schema

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

No description

## `verificationReport` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "emails": [
        "jane.doe@apify.com",
        "info@python.org",
        "nobody@mailinator.com",
        "not-an-email",
        "someone@this-domain-does-not-exist-9x8v7q.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("pavel.tashev/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@apify.com",
        "info@python.org",
        "nobody@mailinator.com",
        "not-an-email",
        "someone@this-domain-does-not-exist-9x8v7q.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("pavel.tashev/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@apify.com",
    "info@python.org",
    "nobody@mailinator.com",
    "not-an-email",
    "someone@this-domain-does-not-exist-9x8v7q.com"
  ]
}' |
apify call pavel.tashev/email-verifier --silent --output-dataset

```

## MCP server setup

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