# Email Validator: Bulk Email Verification & MX (`maliksaid/email-validator`) Actor

Bulk email validator for email verification and list cleaning. Checks syntax, MX records, disposable domains, role addresses, typos like gmial.com, SPF and DMARC. Gives each email a valid, risky or invalid verdict. Uses DNS only. Paste emails, link a CSV file or read another Actor's dataset.

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

## Pricing

from $0.24 / 1,000 email checks

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?

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

## Email Validator: Bulk Email Verifier without SMTP

Clean an email list before you send. Each address gets a verdict (`valid`, `risky`, `invalid` or `unknown`), a 0 to 100 deliverability score and send advice.

- **Never touches the mailbox.** DNS checks only, so mail servers never block it and your sender reputation stays safe.
- **Ready files.** `CLEAN_LIST.csv` to import into your sending tool, `REMOVE_LIST.txt` to suppress.
- **$0.30 per 1,000 addresses** on the free plan, less on paid plans, plus a small start fee per run.

One output row, shortened:

```json
{
  "email": "jane.doe@gmial.com",
  "verdict": "risky",
  "score": 0,
  "sendAdvice": "remove",
  "reasons": ["Disposable email domain", "Possible typo, did you mean jane.doe@gmail.com"],
  "suggestion": "jane.doe@gmail.com",
  "isDisposable": true,
  "mxHosts": []
}
```

The full row is in [Output example](#output-example).

### How it works

This email validator checks a list of email addresses and gives each one a verdict: `valid`, `risky`, `invalid` or `unknown`. It checks syntax, the domain's MX records, disposable domains, role addresses, common typos, and SPF and DMARC on the domain.

It uses DNS only. It never connects to a mail server and never sends an email. So it is fast, it is never blocked by mail providers, and it does not touch your sender reputation. The DNS-only design is also why it costs so little per address.

Use it to clean a mailing list before a campaign, to check sign-up forms in bulk, or as a cheap pre-filter before a paid SMTP check.

### Email validation checks

| Check | How it works | Fields |
|---|---|---|
| Syntax | Practical RFC 5322 rules: one @, local part up to 64 characters, no leading, trailing or double dots, a host name with a letter TLD. International domains are converted to ASCII | `syntaxValid`, `syntaxError` |
| Domain and MX | MX lookup. With no MX record, falls back to the A and AAAA records (RFC 5321). Detects null MX (RFC 7505) and domains that do not exist | `domainExists`, `mxHosts`, `nullMx`, `mailHostSource` |
| Disposable domain | The domain and its parent domains are matched against the disposable-email-domains list (about 9,900 domains) | `isDisposable` |
| Role address | Local parts such as info, sales, support, admin and noreply | `isRoleAccount` |
| Free provider | Large free mailbox providers such as gmail.com, outlook.com and yahoo.com | `isFreeProvider` |
| Typo | Close misspellings of common providers, such as gmial.com, hotmial.com, yahoo.con or gmail.co | `suggestion` |
| SPF and DMARC | TXT records on the domain and on `_dmarc.<domain>` | `hasSpf`, `hasDmarc`, `dmarcPolicy` |
| Duplicates | Repeats of an earlier address in your list | `isDuplicate` |
| Deliverability score | 100 minus fixed points for each risk found (table below). Invalid addresses score 0 | `score`, `sendAdvice` |
| Plus tag | Addresses such as jane+news@example.com, and the address without the tag | `hasPlusTag`, `baseEmail` |
| Mail provider | The mail service behind the MX hosts: google, microsoft, zoho, yahoo, proton, yandex, fastmail, icloud, gmx, mailru, or other | `mailProvider` |
| Suppression list | Your own list of addresses or domains not to email | `suppressed` |

### Email verdicts

| Verdict | Meaning |
|---|---|
| `valid` | Passed every check |
| `risky` | Can receive mail, but often bounces or hurts deliverability: disposable, role address, possible typo, A-record fallback or duplicate |
| `invalid` | Cannot receive mail: bad syntax, domain does not exist, null MX, or no mail host at all |
| `unknown` | DNS did not answer for this domain. Run it again later |

The `reasons` field lists every problem found, in plain words.

### Deliverability score and send advice

Each address starts at 100. Points come off for each risk:

| Risk | Points off |
|---|---|
| Disposable domain | 60 |
| Possible typo | 40 |
| No MX record (A-record fallback) | 30 |
| Role address | 20 |
| Duplicate | 10 |
| No SPF record | 5 |
| No DMARC record | 5 |

Invalid addresses score 0. Unknown addresses get no score. `sendAdvice` is `send` at 80 or more, `review` from 40 to 79 and `remove` below 40. The verdict stays the main field; the score helps you sort risky rows.

A `valid` verdict does not prove the mailbox exists. Only an SMTP check can test that, and this Actor does not do SMTP checks by design. Use the `valid` rows as the input for an SMTP verifier if you need mailbox-level checks. That way you pay for SMTP checks only on addresses that can work.

### How to validate an email list

1. Paste addresses into **Email addresses**, one per line. Or put a link to a TXT or CSV file in **List file URL**. Every cell that contains @ is read. Or pick a dataset in **Dataset with email addresses**, for example the output of a lead scraper.
2. Set **Maximum addresses** to 0 to check the whole list. The default of 100 keeps test runs cheap.
3. Optional: paste addresses or domains you must not email (unsubscribes, complaints) into **Suppression list**, for example `@competitor.com`.
4. Click **Start**. Download the results as CSV, Excel or JSON.
5. Use the two ready files in the run's key-value store: `CLEAN_LIST.csv` (valid addresses, one `email` column, ready to import into a sending tool) and `REMOVE_LIST.txt` (invalid, disposable and suppressed addresses, one per line). Turn on **Clean list includes risky addresses** to add risky rows to the clean list.

### Validate emails from another Actor's dataset

Pick the dataset in **Dataset with email addresses**. The Actor reads the address from each row. It looks in `email`, `emails`, `emailAddress`, `email_address`, `Email`, `mail`, `contactEmail` and `contactEmails`, in that order. Set **Email field in the dataset** when your field has another name. Dot paths such as `contact.email` work. A field with a list of addresses gives one result per address.

Each result then carries `datasetId` and `datasetItemIndex`, the row number in your dataset. Use them to join the verdict back to the lead. The Actor only reads your dataset. It never changes it.

```json
{
    "datasetId": "YOUR_DATASET_ID",
    "datasetEmailField": "",
    "maxItems": 0
}
```

A run checks up to 200,000 addresses. Each domain is looked up once per run, so lists with many addresses on the same domains run fastest. The Actor stops cleanly before its timeout and keeps the rows it has checked.

### Input example

```json
{
    "emails": ["info@apify.com", "jane.doe@gmial.com", "test@mailinator.com", "someone@example.com"],
    "removeDuplicates": true,
    "checkSpfDmarc": true,
    "maxItems": 0
}
```

### Output example

A real record from a test run on 22 October 2026. The score, advice, tag, provider and suppression fields come from the same address in a run on 24 October 2026:

```json
{
    "id": "2",
    "position": 2,
    "input": "jane.doe@gmial.com",
    "email": "jane.doe@gmial.com",
    "localPart": "jane.doe",
    "domain": "gmial.com",
    "verdict": "risky",
    "reasons": [
        "No MX record, mail would go to the A or AAAA address",
        "Disposable email domain",
        "Possible typo, did you mean jane.doe@gmail.com"
    ],
    "syntaxValid": true,
    "syntaxError": null,
    "domainExists": true,
    "mxHosts": [],
    "nullMx": false,
    "mailHostSource": "a",
    "isDisposable": true,
    "isRoleAccount": false,
    "isFreeProvider": false,
    "suggestion": "jane.doe@gmail.com",
    "hasSpf": false,
    "hasDmarc": false,
    "dmarcPolicy": null,
    "isDuplicate": false,
    "dnsError": "MX: ESERVFAIL",
    "datasetId": null,
    "datasetItemIndex": null,
    "score": 0,
    "sendAdvice": "remove",
    "hasPlusTag": false,
    "baseEmail": null,
    "mailProvider": null,
    "suppressed": false,
    "scrapedAt": "2026-10-22T08:12:00.431Z"
}
```

The run also saves a `SUMMARY` record in the key-value store with counts per verdict, duplicates skipped and the number of domains checked.

### Email verifier pricing

You pay per checked address, plus a small start fee per run.

| Event | Free plan | GOLD plan and higher |
|---|---|---|
| Run start | $0.005 | $0.005 |
| Email check | $0.0003 ($0.30 per 1,000) | $0.00024 ($0.24 per 1,000) |

| Addresses in one run | Free plan | GOLD plan and higher |
|---|---|---|
| 100 | $0.035 | $0.029 |
| 1,000 | $0.305 | $0.245 |

Costs include the run start fee.

One run checks up to 200,000 addresses. Duplicates skipped by **Remove duplicates** are not charged. Writing the clean and remove list files adds no charge.

### Cut SMTP costs by filtering first

Mailbox-level (SMTP) verifiers usually charge more per address. Run this Actor first, then send only the `valid` rows to the SMTP verifier.

Worked example, with stated assumptions: a 100,000-address list where 30% is invalid, disposable or a duplicate, and an SMTP verifier at $2.00 per 1,000.

- SMTP check of the whole list: 100,000 x $0.002 = $200.
- This Actor first (about $30), then SMTP on the 70,000 that remain ($140): about $170.
- Saving: about $30, and 30,000 fewer SMTP connections to mail servers.

The saving grows with the share of bad addresses and with the SMTP price.

### Use it from the API, a schedule or a no-code tool

Run it and get the results in one call (put your own API token in place of `YOUR_TOKEN`):

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

- Schedules: check a sign-up list every day with an Apify schedule.
- No-code tools: the Apify integrations for Make, Zapier, n8n and Google Sheets can start the Actor and read its dataset.
- AI agents: the Actor works through the Apify MCP server with the same JSON input.

### Privacy and lawful use

Email addresses are personal data under laws such as the GDPR (EU and UK) and the PDPL (Saudi Arabia). You must have a lawful basis to process the list you upload, for example consent or a legitimate interest.

- The Actor returns only what you supplied plus the check results. It never looks up names, companies or any other data about the people behind the addresses.
- It never contacts mail servers and never sends email.
- DNS questions go to Cloudflare (1.1.1.1) and Google (8.8.8.8) public DNS. They contain the domain only, never the full address.
- The Actor keeps no copy of your list. Results exist only in this run's dataset and key-value store, in your own Apify account. Delete them when you no longer need them.

### Data sources

- Disposable domains: the [disposable-email-domains](https://github.com/disposable-email-domains/disposable-email-domains) list, CC0 1.0 (public domain). The Actor downloads the newest copy at the start of each run and falls back to a copy bundled with the Actor.
- DNS: Node.js built-in resolver, querying Cloudflare and Google public DNS at a polite rate.
- Role names, free providers and typo rules: lists written for this Actor.

### FAQ

**Does this email validator check if the mailbox exists?**
No. That needs an SMTP connection to the mail server. This Actor checks everything that DNS and the address itself can tell you.

**Why is a role address risky?**
Addresses such as info@ or sales@ often go to several people or to a ticket system. Many senders exclude them from marketing lists.

**Why is gmial.com marked disposable?**
Some typo domains of big providers are registered to catch mistyped mail. The disposable list includes several of them.

**Does it detect catch-all domains?**
No. A catch-all test needs an SMTP connection to the mail server, and this Actor uses DNS only. Run an SMTP verifier on the `valid` rows if you need catch-all flags.

**Can I check the emails a scraper found?**
Yes. Pick the scraper's dataset in **Dataset with email addresses**. Each result keeps the row number in `datasetItemIndex`.

**Can I use it from an AI agent or the API?**
Yes. Call the Actor with a JSON input as above and read the dataset.

**How is the score built?**
It starts at 100 and takes off fixed points per risk, as in the score table above. It never uses SMTP, so a high score means the address passed every DNS and list check, not that the mailbox exists.

**How do I keep unsubscribed people out?**
Paste their addresses or domains into **Suppression list**. Matching rows get `suppressed: true` and go to `REMOVE_LIST.txt`. The list is used for that run only and is never stored.

### Support

Open an issue on the Actor's Issues tab. Include the run ID and one example address with the domain changed if it is private.

### Related Actors

- [WHOIS Domain Lookup in Bulk](https://apify.com/maliksaid/domain-intelligence-rdap): registrar, age, DNS, SPF, DMARC and SSL for whole domains.
- [HTTP Request Runner](https://apify.com/maliksaid/http-request-runner): call an API for each row of a list, for example to push clean leads to your CRM.

# Actor input Schema

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

Paste the addresses to check, one per line. You can also paste a comma-separated list. Leave empty to use only the list file or the dataset below. Use only lists you have a lawful basis to process.

## `emailListUrl` (type: `string`):

Read addresses from a TXT or CSV file at this link, for example a Google Sheets CSV export or a file in an Apify key-value store. Every cell that contains @ is checked. Up to 20 MB.

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

Optional. Pick a dataset, for example the output of a lead or contact scraper. The Actor reads the email address from each row and adds datasetId and datasetItemIndex to every result, so you can join them back. It only reads the dataset.

## `datasetEmailField` (type: `string`):

The field that holds the address, for example email or contact.email. Empty: the Actor uses the first of email, emails, emailAddress, email_address, Email, mail, contactEmail, contactEmails that holds an address. Arrays give one row per address.

## `removeDuplicates` (type: `boolean`):

Skip repeats of an address already in the list. Skipped repeats are not saved and not charged. Turn off to keep every row, with isDuplicate set to true on repeats.

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

Look up whether each domain publishes SPF and DMARC records. Turn off for a slightly faster run.

## `suppressionList` (type: `array`):

Optional. Addresses or domains you must not email, such as unsubscribes or complaints. Write a full address or a domain like @example.com. Matches get suppressed: true and go to REMOVE_LIST.txt. The list is used for this run only and never stored.

## `cleanListIncludesRisky` (type: `boolean`):

Off: CLEAN_LIST.csv holds valid addresses only. On: it also holds risky ones, such as role addresses.

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

Stop after checking this many addresses. The default keeps test runs short and cheap. Set 0 to check the whole list, up to 200,000 per run.

## Actor input object example

```json
{
  "emails": [
    "info@apify.com",
    "jane.doe@outlook.com",
    "jane.doe@gmial.com",
    "test@mailinator.com",
    "someone@example.com"
  ],
  "datasetEmailField": "",
  "removeDuplicates": true,
  "checkSpfDmarc": true,
  "suppressionList": [],
  "cleanListIncludesRisky": false,
  "maxItems": 100
}
```

# Actor output Schema

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

No description

## `summary` (type: `string`):

No description

## `cleanList` (type: `string`):

No description

## `removeList` (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": [
        "info@apify.com",
        "jane.doe@outlook.com",
        "jane.doe@gmial.com",
        "test@mailinator.com",
        "someone@example.com"
    ],
    "maxItems": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("maliksaid/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": [
        "info@apify.com",
        "jane.doe@outlook.com",
        "jane.doe@gmial.com",
        "test@mailinator.com",
        "someone@example.com",
    ],
    "maxItems": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("maliksaid/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": [
    "info@apify.com",
    "jane.doe@outlook.com",
    "jane.doe@gmial.com",
    "test@mailinator.com",
    "someone@example.com"
  ],
  "maxItems": 100
}' |
apify call maliksaid/email-validator --silent --output-dataset

```

## MCP server setup

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