# Email List Cleaner & Validator (Dataset, CSV or Sheet) (`nerolabs/email-list-cleaner`) Actor

Cleans an email list from an Apify dataset, a CSV/Excel file or Google Sheet, keeping every original column and adding a verdict per row: format, whether the domain accepts mail, throwaway domains, shared inboxes, typos and duplicates. Does NOT confirm individual mailboxes. Charged per address.

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

## Pricing

from $1.40 / 1,000 addresses

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 List Cleaner & Validator (Dataset, CSV or Sheet)

You scraped or exported a list of leads. Some of those addresses are dead, some are throwaway signups, some are `info@` inboxes that will get you marked as spam, and some are simple typos like `gmial.com`. Send to them and your sender reputation takes the hit, which is what stops your good emails landing.

This Actor checks the list you already have and hands it straight back with a verdict on every row.

### The one thing to know before you start

**This does not confirm that an individual mailbox exists.** That check needs a direct SMTP conversation with the mail server, and Apify's network blocks outbound mail ports, which was tested on their infrastructure before this Actor was built. So it is not available here, and pretending otherwise would be the one genuinely dishonest thing this tool could do.

Every row it returns carries `mailboxChecked: false` so that limit travels with your data, not just this page.

**What it does catch**, which is most of what wrecks a campaign:

| Check | Catches |
|---|---|
| Address format | Broken and malformed addresses that bounce instantly |
| Mail server (MX/A records) | Domains that cannot receive mail from anyone, the biggest single source of hard bounces |
| Throwaway domains | mailinator.com, guerrillamail.com and around 120 others: people who signed up not wanting contact |
| Shared inboxes | `info@`, `sales@`, `support@`, which bounce and draw spam complaints far more on cold lists |
| Likely typos | `gmial.com`, `hotmial.com`, `yahooo.com`, with the correction suggested so you can fix rather than bin the lead |
| Duplicates | The same address again, ignoring case and stray spaces |
| Free providers | Flags gmail/yahoo/outlook, useful to know on a B2B list |

If you need mailbox-level confirmation as well, run this first to strip the obvious junk cheaply, then send the much smaller survivor list to a paid verification service. That is the sensible order anyway, because you pay those services per address.

### What makes it different from the other verifiers

Every other email checker on the Store takes **one input: a typed-in list of addresses.** So you pull the email column out of your spreadsheet, paste it in, get back a bare list of addresses and verdicts, and then have to match it all back to your names, companies and phone numbers yourself.

This one takes your **actual list**: an Apify dataset, a CSV or Excel file, a Google Sheet, or pasted JSON. Every original column comes back untouched, with the verdict columns added alongside. Nothing to re-join.

### Example

In (your scraper's output, or a sheet):

| name | company | email |
|---|---|---|
| Ana Silva | Silva Ltd | ana.silva@apify.com |
| Ben Okafor | Okafor & Co | ben@gmial.com |
| Cara Lind | Lind Studio | info@lindstudio.co.uk |
| Dan Reeves | Reeves Ltd | dan@mailinator.com |
| Frank Ortiz | Reeves Ltd | frank@thisdomaindoesnotexist12345.com |

Out (real output from the default example):

| name | company | email | emailStatus | emailReason | didYouMean |
|---|---|---|---|---|---|
| Ana Silva | Silva Ltd | ana.silva@apify.com | valid | ok | |
| Ben Okafor | Okafor & Co | ben@gmial.com | risky | possible\_typo | gmail.com |
| Cara Lind | Lind Studio | info@lindstudio.co.uk | risky | role\_address | |
| Dan Reeves | Reeves Ltd | dan@mailinator.com | invalid | disposable\_domain | |
| Frank Ortiz | Reeves Ltd | frank@thisdomaindoesnotexist12345.com | invalid | no\_mail\_server | |

Your `name` and `company` columns are still there, still lined up.

### The four verdicts

- **valid**: passed every check that ran. Not a delivery guarantee, see above.
- **risky**: send if you want, but know why. Shared inbox, likely typo, or a domain whose lookup did not complete.
- **invalid**: do not send. Broken format, a domain that accepts no mail, or a throwaway provider.
- **duplicate**: you already have this address earlier in the list.

Alongside the verdict you get the individual flags (`hasMailServer`, `isDisposable`, `isRoleAddress`, `isFreeProvider`, `didYouMean`, `syntaxValid`) and `emailAllReasons` with everything that applied, so you can set your own bar rather than taking ours.

### Pricing

| Event | Price |
|---|---|
| Address checked | $0.002 |
| File export (CSV or Excel) | $0.01 per file |
| Webhook delivery | $0.02, only on a confirmed 2xx |
| Actor start | $0.00005 per run |

Bronze 10% off, Silver 20%, Gold 30%, applied automatically.

**In practice:** 1,000 addresses is $2. A list of 10,000 is $20. A nightly clean of 200 new leads is about $12 a month.

Everything that gets a verdict is charged, including rows you filter out of the output with **Rows to keep** and including duplicates, because each one still had to be checked to know that. Domain lookups are cached per domain, so a 5,000-row list covering 300 domains does 300 lookups, not 5,000, which is why this is fast and cheap to run.

### Tips

- **Leave "Email field" empty** unless it guesses wrong. It looks for a sensibly named column first, then falls back to whichever column actually contains addresses.
- **Use "Rows to keep"** set to *problems* for a review list of just the junk, or *valid* to get a clean send list directly.
- **`didYouMean` is worth acting on.** A typo is a real lead you would otherwise throw away. Note that some typo domains, `hotmial.com` for one, really do run mail servers and will happily swallow your email, which is precisely why the flag matters.
- **A domain lookup that times out comes back risky, never invalid.** A slow DNS server is not evidence of a bad address.
- **Chain it**: [Dataset Cleaner & Exporter](https://apify.com/nerolabs/dataset-cleaner-exporter) to tidy the raw scrape, this to check the addresses, then [Dataset Filter & Transform](https://apify.com/nerolabs/dataset-filter-transform) to keep only `emailStatus = valid`.

### FAQ

**Does a "valid" verdict mean the email will definitely arrive?**
No, and nothing that says otherwise is being straight with you. It means the address is properly formed, the domain can receive mail, and it is not a throwaway, a shared inbox or an obvious typo. Whether that specific person still works there is something no tool can tell you without sending.

**Why can't you check the mailbox like the other tools?**
Apify blocks outbound connections on the mail ports. That was tested directly on their servers, on ports 25 and 587, against Gmail, Outlook and Google Workspace, before this Actor was designed. Tools that do offer the check are calling a paid third-party service behind the scenes, which is usually why they cost far more per address.

**Will it change my data?**
No. Your original columns are returned exactly as they came in. The normalized lowercase address is added as a separate `emailNormalized` column rather than overwriting yours.

**How current is the throwaway-domain list?**
It covers roughly 120 of the common providers and is bundled with the Actor rather than fetched at run time, so a run can never fail because an external list was down. It is not exhaustive, and new throwaway domains appear constantly. If you hit one it misses, say so on the Issues tab and it will get added.

**Can I use it on a list I bought?**
You can, and the domain and format checks will strip a lot of it. Be aware that bought lists fail mostly at the mailbox level, which is the one thing this cannot check, so pair it with a paid service for that step.

If this saved you a bounced campaign or a damaged sender reputation, a review on the Store page genuinely helps.

### The rest of the toolkit

- [Dataset Cleaner & Exporter](https://apify.com/nerolabs/dataset-cleaner-exporter): dedupe, flatten nested JSON, clean phone numbers and URLs, export.
- [Dataset Filter & Transform](https://apify.com/nerolabs/dataset-filter-transform): keep the rows you want and reshape the fields.
- [Dataset Join & Merge](https://apify.com/nerolabs/dataset-join-merge): VLOOKUP-style joins across datasets, files or sheets.
- [Dataset Aggregate, Group By & Pivot](https://apify.com/nerolabs/dataset-aggregate-pivot): counts, sums and pivot tables per group.
- [Dataset Diff & Change Detector](https://apify.com/nerolabs/dataset-diff-detector): what changed since last time.
- [Dataset AI Enrich](https://apify.com/nerolabs/dataset-ai-enrich): LLM-generated columns, no API key needed.
- [Dataset Charts & Report](https://apify.com/nerolabs/dataset-charts-report): chart images and a PDF report.
- [Dataset to Postgres, Supabase & MySQL](https://apify.com/nerolabs/dataset-to-database): write the rows into a database table.
- [Dataset to REST API](https://apify.com/nerolabs/dataset-to-rest-api): send every row to any API as its own request.
- [Actor Pipeline Runner](https://apify.com/nerolabs/actor-pipeline-runner): run that whole chain in one call.

### For AI agents

Pay per event, agent-payable through x402 and MCP, limited permissions, no standby. Inputs: `datasetId` (resource picker), `fileUrl` (CSV, TSV, Excel, JSON, JSON Lines or Google Sheet) or inline `data` for the rows; `emailField` (auto-detected when omitted); `checkMailServer`, `detectDisposable`, `detectRole`, `detectTypos`, `markDuplicates` to control the checks; `keep` to filter the output; `exportFormats`, `outputDatasetName`, `webhookUrl` for delivery. Returns every input row unchanged plus `emailStatus` (valid, risky, invalid, duplicate), `emailReason`, `emailAllReasons`, `hasMailServer`, `mailServer`, `isDisposable`, `isRoleAddress`, `isFreeProvider`, `didYouMean` and `mailboxChecked` (always false), plus a `CLEAN_SUMMARY` record. Charged $0.002 per address given a verdict, including rows filtered from the output. SMTP mailbox verification is NOT performed and is not available on this platform; do not present a valid verdict as a delivery guarantee.

# Actor input Schema

## `datasetId` (type: `string`):

Pick an existing Apify dataset, for example the output of any scraper run. Use this OR 'File URL' OR 'Data (inline)'. Declaring it this way is what lets this Actor run with limited permissions: it may read the dataset you point at, and nothing else on your account.

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

A public link to a CSV or TSV file, an Excel .xlsx file, a JSON array or JSON Lines file, or a Google Sheet (paste the normal sheet link with sharing set to 'Anyone with the link can view'). Format is detected automatically. Up to 100 MB.

## `data` (type: `array`):

Paste rows directly instead, for a quick one-off check.

## `fileFormat` (type: `string`):

Only needed if automatic detection gets the file URL's format wrong.

## `emailField` (type: `string`):

Which column holds the address, for example 'email' or 'contact\_email'. Leave empty and it is detected automatically, first by column name and then by looking at which column actually contains addresses.

## `checkMailServer` (type: `boolean`):

Looks up the domain's MX records (falling back to an A record, which mail servers also accept). A domain with neither cannot receive mail from anyone, which is the single biggest source of hard bounces. Distinct domains are looked up once and cached, so a 5,000-row list with 300 domains costs 300 lookups, not 5,000.

## `detectDisposable` (type: `boolean`):

Marks known temporary mailbox providers (mailinator.com, guerrillamail.com, 10minutemail.com and around 120 others) as invalid. These are people who signed up without wanting to be contacted.

## `detectRole` (type: `boolean`):

Marks info@, sales@, support@, admin@ and similar as risky rather than invalid. They often work, but on a cold list they bounce and generate spam complaints far more than a named person does.

## `detectTypos` (type: `boolean`):

Spots near misses of popular domains (gmial.com, hotmial.co.uk, yahooo.com) and puts the suggestion in a 'didYouMean' column so you can correct rather than discard the row.

## `markDuplicates` (type: `boolean`):

The first time an address appears it is checked normally; later repeats are marked 'duplicate'. Matching ignores case and surrounding spaces.

## `keep` (type: `string`):

Filters the output. Every row is still checked and charged either way, so this is about what you get back, not about cost.

## `exportFormats` (type: `array`):

Optionally write the results as a real downloadable CSV and/or Excel file, on top of the dataset.

## `outputDatasetName` (type: `string`):

Optional. Every run's results are also appended to a dataset of this name, so a scheduled job builds one growing clean list. Not charged again.

## `dnsConcurrency` (type: `integer`):

How many domains to look up in parallel. The default is fine for almost everything.

## `dnsTimeoutSecs` (type: `integer`):

A domain whose lookup times out is reported as risky, never as invalid, because a slow DNS server is not evidence of a bad address.

## `maxItems` (type: `integer`):

Stop after this many rows, a cost guard while you are testing. There is a hard ceiling of 200,000 per run regardless.

## `webhookUrl` (type: `string`):

Optional. If set, the run summary (counts and download links, never the addresses themselves) is POSTed here as JSON when the run finishes, so a scheduled clean-up can report into Slack, Zapier, Make, n8n or your own API. Only charged on a confirmed 2xx.

## Actor input object example

```json
{
  "data": [
    {
      "name": "Ana Silva",
      "company": "Silva Ltd",
      "email": "ana.silva@apify.com"
    },
    {
      "name": "Ben Okafor",
      "company": "Okafor & Co",
      "email": "ben@gmial.com"
    },
    {
      "name": "Cara Lind",
      "company": "Lind Studio",
      "email": "info@lindstudio.co.uk"
    },
    {
      "name": "Dan Reeves",
      "company": "Reeves Ltd",
      "email": "dan@mailinator.com"
    },
    {
      "name": "Eve Mbeki",
      "company": "Mbeki Group",
      "email": "eve mbeki@@example"
    },
    {
      "name": "Frank Ortiz",
      "company": "Ortiz SA",
      "email": "frank@thisdomaindoesnotexistatall12345.com"
    },
    {
      "name": "Ana Silva (again)",
      "company": "Silva Ltd",
      "email": "ANA.SILVA@apify.com"
    }
  ],
  "fileFormat": "auto",
  "checkMailServer": true,
  "detectDisposable": true,
  "detectRole": true,
  "detectTypos": true,
  "markDuplicates": true,
  "keep": "all",
  "exportFormats": [],
  "dnsConcurrency": 20,
  "dnsTimeoutSecs": 10
}
```

# Actor output Schema

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

Every original row with its verdict, reasons and per-check flags added.

## `cleanSummary` (type: `string`):

Counts by verdict and reason, plus an explicit note on what was and was not checked.

# 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 = {
    "data": [
        {
            "name": "Ana Silva",
            "company": "Silva Ltd",
            "email": "ana.silva@apify.com"
        },
        {
            "name": "Ben Okafor",
            "company": "Okafor & Co",
            "email": "ben@gmial.com"
        },
        {
            "name": "Cara Lind",
            "company": "Lind Studio",
            "email": "info@lindstudio.co.uk"
        },
        {
            "name": "Dan Reeves",
            "company": "Reeves Ltd",
            "email": "dan@mailinator.com"
        },
        {
            "name": "Eve Mbeki",
            "company": "Mbeki Group",
            "email": "eve mbeki@@example"
        },
        {
            "name": "Frank Ortiz",
            "company": "Ortiz SA",
            "email": "frank@thisdomaindoesnotexistatall12345.com"
        },
        {
            "name": "Ana Silva (again)",
            "company": "Silva Ltd",
            "email": "ANA.SILVA@apify.com"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("nerolabs/email-list-cleaner").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 = { "data": [
        {
            "name": "Ana Silva",
            "company": "Silva Ltd",
            "email": "ana.silva@apify.com",
        },
        {
            "name": "Ben Okafor",
            "company": "Okafor & Co",
            "email": "ben@gmial.com",
        },
        {
            "name": "Cara Lind",
            "company": "Lind Studio",
            "email": "info@lindstudio.co.uk",
        },
        {
            "name": "Dan Reeves",
            "company": "Reeves Ltd",
            "email": "dan@mailinator.com",
        },
        {
            "name": "Eve Mbeki",
            "company": "Mbeki Group",
            "email": "eve mbeki@@example",
        },
        {
            "name": "Frank Ortiz",
            "company": "Ortiz SA",
            "email": "frank@thisdomaindoesnotexistatall12345.com",
        },
        {
            "name": "Ana Silva (again)",
            "company": "Silva Ltd",
            "email": "ANA.SILVA@apify.com",
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("nerolabs/email-list-cleaner").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 '{
  "data": [
    {
      "name": "Ana Silva",
      "company": "Silva Ltd",
      "email": "ana.silva@apify.com"
    },
    {
      "name": "Ben Okafor",
      "company": "Okafor & Co",
      "email": "ben@gmial.com"
    },
    {
      "name": "Cara Lind",
      "company": "Lind Studio",
      "email": "info@lindstudio.co.uk"
    },
    {
      "name": "Dan Reeves",
      "company": "Reeves Ltd",
      "email": "dan@mailinator.com"
    },
    {
      "name": "Eve Mbeki",
      "company": "Mbeki Group",
      "email": "eve mbeki@@example"
    },
    {
      "name": "Frank Ortiz",
      "company": "Ortiz SA",
      "email": "frank@thisdomaindoesnotexistatall12345.com"
    },
    {
      "name": "Ana Silva (again)",
      "company": "Silva Ltd",
      "email": "ANA.SILVA@apify.com"
    }
  ]
}' |
apify call nerolabs/email-list-cleaner --silent --output-dataset

```

## MCP server setup

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

```

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/0CDbELZxl7DXMo3f6/builds/gjhww9LJGWzG3l07r/openapi.json
