# Bulk Email Validator (Syntax, MX, Disposable, Typo) (`rasareka/email-validator`) Actor

Bulk email validator & list cleaner. Verify email lists you own: syntax check, MX/DNS lookup, 10k+ disposable domains, role accounts, free providers and typo fixes (gmial.com → gmail.com). Score 0-100 + valid/risky/invalid verdict to cut bounce rate. No SMTP probing.

- **URL**: https://apify.com/rasareka/email-validator.md
- **Developed by:** [Rasa Reka](https://apify.com/rasareka) (community)
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 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, Typo)

Clean your email list before you hit send. This bulk email validator checks every address for **valid syntax, working MX/DNS records, disposable (temporary) domains, role accounts, free providers and domain typos** (gmial.com → gmail.com), then gives each one a **0–100 score and a valid / risky / invalid verdict**. Up to 100,000 emails per run.

> **This Actor verifies email lists you already own.** It does not find, scrape, guess or harvest email addresses. You bring the list (your CRM export, newsletter signups, form submissions); the Actor tells you which addresses are safe to mail.

### What you get

- **Syntax check**: practical RFC 5322 rules (what real mail providers accept), length limits, valid domain labels. Cleans `Jane Doe <jane@x.com>` and `mailto:` forms automatically.
- **MX / DNS lookup**: confirms the domain exists and can receive mail (MX records, with an A/AAAA fallback, null-MX detection). Top 3 MX hosts are returned.
- **Disposable email detection**: 10,000+ known temporary / throwaway domains (mailinator, yopmail, 10minutemail, guerrillamail...), including their subdomains.
- **Role account detection**: info@, support@, sales@, noreply@, admin@ and 40+ more shared inboxes.
- **Free provider flag**: gmail.com, outlook.com, yahoo.com, icloud.com, proton.me and other consumer mailboxes, so you can separate B2B from B2C leads.
- **Typo suggestions**: gmial.com → gmail.com, hotmial.com → hotmail.com, yaho.com → yahoo.com, company.con → company.com.
- **Score 0–100, verdict and reasons**: one clear verdict per email plus machine-readable reasons (`domain_not_found`, `disposable_domain`, `possible_typo`...).
- **Free SUMMARY report**: counts per verdict, number of disposable / role / typo addresses, and the top 20 domains in your list.

### Why this Actor

| | This Actor | Regex-only checks | SMTP "ping" verifiers |
|---|---|---|---|
| Syntax check | Yes | Yes | Yes |
| Domain exists + MX records | Yes | No | Yes |
| Disposable domains (10k+) | Yes | No | Usually |
| Role / free provider flags | Yes | No | Sometimes |
| Typo suggestions | Yes | No | Sometimes |
| Mailbox existence | No (honest, see Limits) | No | Claimed, often unreliable |
| Sends traffic to the recipient's mail server | No | No | Yes |
| Speed | ~2,000 emails in about 5 s in our local test | Instant | Slow |

### Use cases

- **CRM and list cleaning before campaigns**: remove dead domains, typos and throwaway addresses before a newsletter or cold-email send.
- **Reduce bounce rate**: hard bounces hurt your sender reputation. Drop `invalid` addresses and review `risky` ones.
- **Signup form QA**: check the addresses collected by your forms, spot disposable signups and fixable typos (offer "Did you mean jane@gmail.com?").
- **Lead list hygiene**: split B2B (company domain) from B2C (free provider) leads, flag shared role inboxes.

### Input

Use any combination of the three sources. Everything is merged, deduplicated and capped at 100,000 emails per run.

| Field | Type | Description |
|---|---|---|
| `emails` | array | List of email addresses. |
| `emailsText` | string | Pasted text: one email per line, or separated by commas/semicolons. |
| `datasetId` | string | Optional: validate a column of an existing Apify dataset (e.g. another Actor's output). |
| `emailField` | string | Field that holds the email in that dataset. Default `email`. |
| `checkMx` | boolean | Look up MX/DNS records. Default `true`. Turn off for syntax-only checks. |
| `suggestTypos` | boolean | Suggest domain typo fixes. Default `true`. |
| `dedupe` | boolean | Check each address once (case-insensitive). Default `true`. |

```json
{
  "emails": ["john.doe@gmail.com", "jane@gmial.com", "test@mailinator.com", "info@apify.com", "not-an-email"],
  "emailsText": "lee@aol.com, kim@abc.com\nceo@company.con",
  "checkMx": true,
  "suggestTypos": true,
  "dedupe": true
}
```

If you start the Actor with no emails at all, it validates a 5-email sample so you can see the output format.

### Output

One dataset item per email checked. Real output from a test run:

```json
[
  {
    "email": "john.doe@gmail.com",
    "normalized": "john.doe@gmail.com",
    "isValidSyntax": true,
    "localPart": "john.doe",
    "domain": "gmail.com",
    "hasMx": true,
    "mxRecords": ["gmail-smtp-in.l.google.com", "alt1.gmail-smtp-in.l.google.com", "alt2.gmail-smtp-in.l.google.com"],
    "isDisposable": false,
    "isRoleAccount": false,
    "isFreeProvider": true,
    "typoSuggestion": null,
    "score": 100,
    "verdict": "valid",
    "reasons": [],
    "checkedAt": "2026-11-07T02:39:55.593Z"
  },
  {
    "email": "alice@yaho.com",
    "normalized": "alice@yaho.com",
    "isValidSyntax": true,
    "localPart": "alice",
    "domain": "yaho.com",
    "hasMx": false,
    "mxRecords": [],
    "isDisposable": false,
    "isRoleAccount": false,
    "isFreeProvider": false,
    "typoSuggestion": "alice@yahoo.com",
    "score": 45,
    "verdict": "risky",
    "reasons": ["no_mx_a_record_fallback", "possible_typo"],
    "checkedAt": "2026-11-07T02:42:04.910Z"
  },
  {
    "email": "test@mailinator.com",
    "normalized": "test@mailinator.com",
    "isValidSyntax": true,
    "localPart": "test",
    "domain": "mailinator.com",
    "hasMx": true,
    "mxRecords": ["mail.mailinator.com", "mail2.mailinator.com"],
    "isDisposable": true,
    "isRoleAccount": false,
    "isFreeProvider": false,
    "typoSuggestion": null,
    "score": 40,
    "verdict": "risky",
    "reasons": ["disposable_domain"],
    "checkedAt": "2026-11-07T02:39:55.597Z"
  },
  {
    "email": "info@apify.com",
    "normalized": "info@apify.com",
    "isValidSyntax": true,
    "localPart": "info",
    "domain": "apify.com",
    "hasMx": true,
    "mxRecords": ["aspmx.l.google.com", "alt2.aspmx.l.google.com", "alt1.aspmx.l.google.com"],
    "isDisposable": false,
    "isRoleAccount": true,
    "isFreeProvider": false,
    "typoSuggestion": null,
    "score": 85,
    "verdict": "valid",
    "reasons": ["role_account"],
    "checkedAt": "2026-11-07T02:39:55.598Z"
  },
  {
    "email": "someone@thisdomaindoesnotexist-zzqx9.com",
    "normalized": "someone@thisdomaindoesnotexist-zzqx9.com",
    "isValidSyntax": true,
    "localPart": "someone",
    "domain": "thisdomaindoesnotexist-zzqx9.com",
    "hasMx": false,
    "mxRecords": [],
    "isDisposable": false,
    "isRoleAccount": false,
    "isFreeProvider": false,
    "typoSuggestion": null,
    "score": 5,
    "verdict": "invalid",
    "reasons": ["domain_not_found"],
    "checkedAt": "2026-11-07T02:39:55.598Z"
  },
  {
    "email": "not-an-email",
    "normalized": null,
    "isValidSyntax": false,
    "localPart": null,
    "domain": null,
    "hasMx": null,
    "mxRecords": [],
    "isDisposable": false,
    "isRoleAccount": false,
    "isFreeProvider": false,
    "typoSuggestion": null,
    "score": 0,
    "verdict": "invalid",
    "reasons": ["missing_at_or_part"],
    "checkedAt": "2026-11-07T02:39:55.615Z"
  }
]
```

#### How the verdict works

Start at 100, then: domain cannot receive mail → 5; MX missing but A record present → −25; disposable → −60; possible typo → −30; role account → −15; DNS lookup failed → −20.
**valid** = score 80+ and the domain accepts mail. **risky** = 40–79. **invalid** = below 40, bad syntax, or a domain that cannot receive mail.

#### SUMMARY record (key-value store, free)

```json
{
  "total": 33,
  "verdicts": { "valid": 15, "risky": 5, "invalid": 13 },
  "disposable": 6,
  "roleAccounts": 5,
  "freeProvider": 8,
  "typoSuggestions": 5,
  "badSyntax": 6,
  "topDomains": [{ "domain": "gmail.com", "count": 3 }, { "domain": "outlook.com", "count": 1 }, { "domain": "yahoo.com", "count": 1 }],
  "uniqueDomains": 28,
  "dns": { "system": 27, "doh": 0, "failed": 0 },
  "stoppedForBudget": false,
  "failed": 0,
  "durationSec": 0.6
}
```

Download results as JSON, CSV, Excel or via the API. Filter on `verdict` to get your clean list.

### Pricing

Pay per result: you pay only for emails checked and saved to the dataset. An invalid email is still a result (knowing it is invalid is the point). Duplicates removed by `dedupe` are free, and internal errors are free (they go to the `FAILED` record, not the dataset). Set a **maximum cost per run** and the Actor stops cleanly when it is reached.

### Limits (please read)

- **Mailbox existence is not checked.** There is no SMTP probing: port 25 is blocked on most cloud platforms, and "pinging" mailboxes is spammy and unreliable (many servers accept everything or block verifiers). **"valid" means the address is well-formed and its domain accepts mail**, not that the specific mailbox exists. A small share of `valid` addresses may still bounce.
- Catch-all domains, full mailboxes and deactivated accounts cannot be detected without sending mail.
- The disposable list has 10,000+ domains but new throwaway services appear daily, so some will be missed.
- Typo suggestions cover popular consumer providers and clear TLD typos (`.con`, `.cmo`, `.ner`...). They are suggestions only; the Actor never changes your data.
- 100,000 emails per run. Split larger lists across runs.
- DNS uses the system resolver with Google Public DNS-over-HTTPS as a fallback. If both fail for a domain, the email gets `dns_lookup_failed` and a `risky` verdict instead of a wrong `invalid`.

### FAQ

**Do I need an account or API key?** Only an Apify account. No third-party API keys.

**Does this Actor find emails?** No. It only checks addresses you supply. It is a list-cleaning tool, not a lead or email finder.

**Is it legal?** The Actor performs public DNS lookups on domains and never contacts the mailbox. You are responsible for having a lawful basis for the email data you process (for example consent under GDPR or the PDPA) and for following anti-spam laws such as CAN-SPAM when you send.

**Do you store my list?** Results stay in your own Apify storage. The Actor does not send your emails anywhere except the domain names to DNS resolvers.

**Why is a gmail typo like gmial.com also marked disposable?** Some typo domains are registered by throwaway mail services and appear on the public disposable list. Either way, the fix is the `typoSuggestion`.

# Actor input Schema

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

Email addresses you own or collected with consent (one per row). "Name <email>" and "mailto:" forms are cleaned automatically. Up to 100,000 per run across all inputs.

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

Paste a list: one email per line, or separated by commas or semicolons. Combined with the list above.

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

Validate a column of an existing Apify dataset you have access to, e.g. the output of another Actor run.

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

Name of the field that holds the email in that dataset.

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

Look up the domain's MX records (A/AAAA fallback) to confirm the domain can receive mail. Turn off for a syntax-only check.

## `suggestTypos` (type: `boolean`):

Suggest corrections for common domain typos, e.g. gmial.com → gmail.com, hotmial.com → hotmail.com, company.con → company.com.

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

Check each address only once (case-insensitive). You are not charged for removed duplicates.

## Actor input object example

```json
{
  "emails": [
    "john.doe@gmail.com",
    "jane@gmial.com",
    "test@mailinator.com",
    "info@apify.com",
    "not-an-email"
  ],
  "emailField": "email",
  "checkMx": true,
  "suggestTypos": true,
  "dedupe": true
}
```

# Actor output Schema

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

No description

## `summary` (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": [
        "john.doe@gmail.com",
        "jane@gmial.com",
        "test@mailinator.com",
        "info@apify.com",
        "not-an-email"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("rasareka/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 = { "emails": [
        "john.doe@gmail.com",
        "jane@gmial.com",
        "test@mailinator.com",
        "info@apify.com",
        "not-an-email",
    ] }

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

```

## MCP server setup

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