# Bulk Email Validator: Syntax, MX, Disposable, Role & Typo Check (`latamdatatools/email-validator-bulk`) Actor

Clean email lists without sending a single email: syntax check, typo suggestions (gmial.com to gmail.com), MX records, disposable domains, role accounts, free providers, SPF and DMARC. Valid / risky / invalid with a reason. No SMTP probing.

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

## Pricing

from $1.00 / 1,000 email 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

## Bulk Email Validator: Syntax, MX, Disposable, Role & Typo Check

Clean an email list **without sending a single email**. Paste emails, point it at a CSV / Google Sheet, or feed it the dataset of another Actor, and get one row per email with:

- **Syntax check** (RFC 5321/5322, the parts real mail systems accept) with a plain-English error: `Two dots in a row before @`, `Domain has no extension (e.g. .com)`, `Invalid character "(" before @`...
- **Cleanup**: spaces removed, domain lower-cased, `"Name <email>"`, `mailto:` links, trailing dots and `gmail,com` fixed; international domains converted to punycode.
- **Typo suggestions** for common providers: `gmial.com` → `gmail.com`, `hotmail.con` → `hotmail.com`, `yahoo.com.arg` → `yahoo.com.ar`, `gmailcom` → `gmail.com`.
- **MX records** of the domain (with the RFC 5321 fallback to the A/AAAA record when there is no MX), **null MX** detection (domains that declare they accept no mail, like `example.com`) and the **mail provider** (Google, Microsoft, Yahoo, Zoho, Proofpoint...).
- **Disposable / temporary domains** (mailinator, yopmail, 10minutemail... about 10,000 domains, subdomains included).
- **Role accounts**: `info@`, `sales@`, `support@`, `noreply@`, `contacto@`, `ventas@`... (shared mailboxes, not a person).
- **Free providers**: Gmail, Outlook/Hotmail, Yahoo, iCloud, AOL, Proton, GMX, Yandex, Mail.ru, regional ISPs...
- **SPF and DMARC** of the domain (policy `none` / `quarantine` / `reject`, with the RFC 7489 fallback to the organizational domain).
- **Duplicates** (case-insensitive, after cleanup): flagged or removed.
- A final **status `valid` / `risky` / `invalid`** with the **reason** in one sentence.

### What does Bulk Email Validator do?

Bulk Email Validator cleans email lists without sending a single email: syntax check, typo suggestions (gmial.com to gmail.com), MX records, disposable / temporary email domains, role accounts (info@, sales@), free providers, SPF and DMARC, with a valid / risky / invalid verdict and a reason. It does not do SMTP mailbox probing.

### How to use Bulk Email Validator

1. Click **Try for free** on this page (a free Apify account is enough to start).
2. Fill in the input (see the examples below) or keep the prefilled example.
3. Click **Start** and wait for the run to finish.
4. Download the results from the **Output** tab as JSON, CSV, Excel or HTML, or read them with the Apify API.
5. Optional: create a **Schedule** to run it daily or weekly, and connect a webhook or an integration (Make, Zapier, n8n, Slack).

### What it does NOT do (on purpose)

**No SMTP check.** This Actor never connects to port 25, never opens a session with the recipient's mail server and never sends an email. It only uses public DNS (DNS over HTTPS through Cloudflare and Google).

That means it **does not verify that the mailbox exists**. `valid` means "well-formed, not disposable, not a likely typo, and the domain has a working mail server setup". A made-up address at a real company (`nobody-here-123@microsoft.com`) is `valid` here. SMTP "mailbox probing" is slow, often blocked from cloud servers, gets your IP blacklisted, and many large providers (Gmail, Outlook, Yahoo) accept every address during the probe anyway (catch-all behaviour), so its answers are unreliable. If you need mailbox-level verification, use this Actor first to remove the obvious junk cheaply, then send only the remaining `valid` rows to a paid SMTP verifier.

### Use cases

- **Clean lists before a campaign**: remove invalid and disposable addresses and fix typos before you import into Mailchimp, Brevo, HubSpot or Instantly. Fewer hard bounces protect your sender reputation.
- **Validate sign-up and lead forms**: run your form exports daily, catch `@gmial.com` typos and throwaway inboxes, and reach the person through another channel with the suggested address.
- **CRM hygiene**: periodic checks of the contacts table: domains that stopped receiving mail, role accounts mixed with people, duplicates with different casing.
- **Lead scraping pipelines**: chain it after a contact or Google Maps scraper (dataset input) and keep only the rows worth sending to.
- **B2B segmentation**: free provider vs company domain, mail provider (Google Workspace vs Microsoft 365) and DMARC policy per domain.

### Input

| Field | What it does |
|---|---|
| **Email addresses** | One per line. Messy values are fine. A line with several emails separated by `;` or `,` gives one row per email. |
| **Dataset** | The output of another Actor run. Use **Email field** (`email`, `contact.email`, `emails.0`...) to say where the email is. Lists in that field give one row per email. |
| **CSV file URL** | Public CSV with a header row (`,` `;` tab or `|` separated, up to 50 MB). Google Sheets share links work if the sheet is "Anyone with the link". |
| **Fields to keep from the source** | Copy columns like `name`, `company`, `id` into the output (`source`) to join the results back. |
| **Check MX** / **Check SPF and DMARC** | Turn off for a syntax-only run (no network at all) or to skip the SPF/DMARC lookups. |
| **Duplicates** | Flag (`isDuplicate`, `duplicateOf`) or remove (not charged). |
| **Include invalid / risky emails** | Turn off to get only the rows you can send to (filtered rows are not charged). |
| **Domains in parallel**, **DNS timeout**, **Max emails** | Tuning. Defaults work for most lists. |

#### Example input

```json
{
  "csvUrl": "https://docs.google.com/spreadsheets/d/your-sheet-id/edit#gid=0",
  "emailField": "Email",
  "keepFields": ["Name", "Company"],
  "duplicates": "skip",
  "includeInvalid": false
}
```

### Output (one row per email)

```json
{
  "position": 2,
  "input": " Someone@GMIAL.com ",
  "email": "Someone@gmial.com",
  "status": "risky",
  "reason": "Possible typo in the domain, did you mean gmail.com?",
  "reasons": ["Possible typo in the domain, did you mean gmail.com?", "No MX record: mail would go to the A record (fallback), often not set up for email"],
  "isValidSyntax": true,
  "syntaxError": null,
  "localPart": "Someone",
  "domain": "gmial.com",
  "domainUnicode": null,
  "suggestion": "Someone@gmail.com",
  "isDisposable": false,
  "isRoleAccount": false,
  "isFreeProvider": false,
  "domainExists": true,
  "hasMx": false,
  "mailServerSource": "a-fallback",
  "mxRecords": [],
  "mailProvider": null,
  "hasSpf": false,
  "spfRecord": null,
  "hasDmarc": false,
  "dmarcPolicy": null,
  "dmarcRecord": null,
  "dmarcFoundAt": null,
  "isDuplicate": false,
  "duplicateOf": null,
  "dnsError": null,
  "source": null
}
```

Every row has exactly the same keys, so CSV and Excel exports have stable columns. The dataset has three views: **Validated emails**, **Domain and email security** and **Problems**. The `RUN_SUMMARY` record in the key-value store has the totals (valid, risky, invalid, disposable, role, free provider, typos, duplicates), the top domains and reasons, DNS statistics and speed.

| `status` | Meaning |
|---|---|
| `valid` | Good syntax, the domain exists and has MX records, not disposable, not a role account, no likely typo. The mailbox itself is **not** verified (see above) |
| `risky` | Deliverable in principle but worth a second look: likely typo, disposable domain, role account, no MX record (mail would go to the A record), quoted or non-ASCII part before the @ |
| `invalid` | Bad syntax, the domain does not exist, the domain publishes a null MX (accepts no mail), the MX points to localhost/an IP, or there is no mail server at all |
| `unknown` | The DNS lookup failed after retries (timeouts). **Not charged**; run those again later |

`reason` is the most important finding; `reasons` lists all of them.

### Pricing

Pay per event: **USD 1 per 1,000 emails** (USD 0.001 per email in the results). Not charged: duplicates with "Remove duplicates", rows you filter out with the include options, and `unknown` rows (DNS failure). The run respects your *max charge per run*. Platform usage is included.

### Speed and limits

- DNS results are **cached per domain** during the run: 50,000 emails at `gmail.com` cost one lookup. Lists with many different domains are limited by DNS (tens to a couple of hundred new domains per second, 20 in parallel by default); emails on domains already seen in the run are near-instant.
- Default memory 256 MB is enough for large lists. Up to 1,000,000 emails per run (`maxEmails`).
- The disposable domain list is the open-source [disposable-email-domains](https://github.com/disposable-email-domains/disposable-email-domains) list (CC0), bundled with the Actor and refreshed with new builds. No list can catch every new throwaway domain.
- If no row is produced (empty input, wrong field name, wrong dataset ID, CSV not reachable), the run is marked as **failed** with a clear message, never a silent empty success.

### Privacy

The Actor only reads the domain part of each email from DNS. The emails are never sent to any third-party verification API and never contacted. Nothing is stored outside your run's own dataset and key-value store.

### Why use Bulk Email Validator

- **No emails sent, no SMTP connections.** It never talks to the recipients' mail servers, so it is fast and cannot get your IP flagged.
- **Catches the common problems in a list:** bad syntax, typos with a suggested fix (gmial.com to gmail.com), domains without MX, about 10,000 disposable domains, role accounts (info@, sales@), free providers, and SPF / DMARC of the domain.
- **Clear result per row:** `valid`, `risky`, `invalid` or `unknown`, with a main `reason` and all reasons.
- **Cheap first pass:** `unknown` rows (DNS failure), removed duplicates and rows you filter out are not charged.

### Cost examples

Prices at the current pay-per-event rates (see Pricing). You can set a maximum charge per run in the run options.

| Job | Approximate cost |
|---|---|
| 1,000 emails | about $1 |
| 10,000 emails | about $10 |
| 100,000 emails | about $100 |

### Use it from your code, an integration or an AI agent

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

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("latamdatatools/email-validator-bulk").call(run_input={
    "emails": [
        "support@apify.com",
        "someone@gmial.com",
        "test@mailinator.com",
        "info@example.com"
    ]
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)
```

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

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('latamdatatools/email-validator-bulk').call({
  "emails": [
    "support@apify.com",
    "someone@gmial.com",
    "test@mailinator.com",
    "info@example.com"
  ]
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

**No code:**

- **Schedules:** run it daily or weekly from Apify Console (Schedules), with the same input saved as a Task.
- **Integrations:** send results to Google Sheets, Slack, email, Make, Zapier, n8n or Airbyte from the Integrations tab, or call your own webhook when a run finishes.
- **AI agents:** add it as a tool in Claude, Cursor, VS Code and other MCP clients through the Apify MCP server (`https://mcp.apify.com`), so the agent can run Bulk Email Validator and read the results.

### FAQ

**Why is an email I know is fake marked `valid`?** Because the Actor does not ask the mail server whether the mailbox exists (no SMTP probe). It checks everything that can be known from the address and public DNS.

**Why is `info@company.com` risky?** Role accounts usually go to several people or a ticket system; many email platforms restrict sending marketing email to them. If they are fine for your use case (B2B outreach to generic inboxes), filter on `isRoleAccount` yourself.

**Why is a company domain with no MX `risky` and not `invalid`?** RFC 5321 says mail can fall back to the domain's A record, so delivery is technically possible, but in practice most such domains do not run a mail server.

**Does it detect catch-all domains?** Not reliably: that needs an SMTP probe. `mailProvider` tells you who hosts the mail (Google and Microsoft do not accept everything; some corporate gateways do).

**Should I use this or an SMTP email verifier?** Use this first to remove the obvious bad rows cheaply and safely. If you also need to know whether each mailbox exists, send only the `valid` rows to an SMTP verifier afterwards, so you pay for fewer checks.

Questions or a missing check? Open an issue in the Issues tab.

# Actor input Schema

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

One email per line. Messy values are cleaned: spaces, upper-case domains, "Name <email>", mailto: links and trailing dots. A line with several emails separated by ; or , gives one row per email.

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

Optional: validate the emails stored in an Apify dataset, for example the output of a lead or contact scraper. Pick the dataset or paste its ID. Use "Email field" to say which field holds the email.

## `csvUrl` (type: `string`):

Optional: a public URL of a CSV file with a header row (comma, semicolon, tab or | separated, up to 50 MB). Google Sheets links work too if the sheet is shared as "Anyone with the link".

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

Name of the field / CSV column that holds the email, e.g. email, Email, contact.email (dot notation for nested fields). A field with a list of emails, or emails separated by ; gives one row per email.

## `keepFields` (type: `array`):

Optional: copy these fields of each dataset record / CSV row into the output (under "source"), e.g. name, company, id, so you can join the results back.

## `checkDns` (type: `boolean`):

Look up the MX records of each domain (falling back to A/AAAA). Turn off for a syntax-only check. Lookups are cached per domain, so thousands of emails on the same domain cost one lookup.

## `checkSpfDmarc` (type: `boolean`):

Read the domain's SPF record and DMARC policy (two more DNS lookups per domain). Useful to see how seriously a domain handles email.

## `duplicates` (type: `string`):

Two inputs are duplicates when they are the same email after cleaning (case-insensitive). Flag them (isDuplicate = true, duplicateOf = first input) or leave them out of the results (not charged).

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

When off, invalid emails are left out of the results (and not charged). They are still counted in RUN_SUMMARY.

## `includeRisky` (type: `boolean`):

When off, risky emails (disposable, role accounts, likely typos, no MX record) are left out of the results (and not charged).

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

How many new domains are looked up at the same time.

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

Per DNS query. Domains whose lookup fails get status "unknown" and are not charged.

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

Upper limit of emails to process in one run. 0 = no limit (up to 1,000,000). Your max charge per run is respected too.

## Actor input object example

```json
{
  "emails": [
    "support@apify.com",
    "someone@gmial.com",
    "test@mailinator.com",
    "info@example.com",
    "john..doe@company.com",
    "hello@this-domain-does-not-exist-12345.com"
  ],
  "emailField": "email",
  "checkDns": true,
  "checkSpfDmarc": true,
  "duplicates": "flag",
  "includeInvalid": true,
  "includeRisky": true,
  "maxConcurrency": 20,
  "dnsTimeoutSecs": 8,
  "maxEmails": 0
}
```

# Actor output Schema

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

No description

## `runSummary` (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": [
        "support@apify.com",
        "someone@gmial.com",
        "test@mailinator.com",
        "info@example.com",
        "john..doe@company.com",
        "hello@this-domain-does-not-exist-12345.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("latamdatatools/email-validator-bulk").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": [
        "support@apify.com",
        "someone@gmial.com",
        "test@mailinator.com",
        "info@example.com",
        "john..doe@company.com",
        "hello@this-domain-does-not-exist-12345.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("latamdatatools/email-validator-bulk").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": [
    "support@apify.com",
    "someone@gmial.com",
    "test@mailinator.com",
    "info@example.com",
    "john..doe@company.com",
    "hello@this-domain-does-not-exist-12345.com"
  ]
}' |
apify call latamdatatools/email-validator-bulk --silent --output-dataset

```

## MCP server setup

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

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/nqpjtr5gVOw1KNfby/builds/QlcYkX315TrBjIbyt/openapi.json
