# Email Validator – MX & Disposable Check (`sfoln/email-validator`) Actor

Validate email lists without sending mail: strict syntax, domain and MX records (incl. null MX), disposable and free-mail domains, role accounts, typo fixes, SPF and DMARC. $0.40 per 1,000 emails, no SMTP probing. Need mailbox-level checks? Use Email Verifier.

- **URL**: https://apify.com/sfoln/email-validator.md
- **Developed by:** [SFOLN LLC](https://apify.com/sfoln) (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.36 / 1,000 checked emails

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

Validate email addresses in bulk **without sending any mail**: each address gets a verdict (`ok`, `role`, `disposable`, `no_mail_server`, `domain_not_found` or `invalid_syntax`) from a strict syntax check, the domain's MX records (including RFC 7505 null MX), a list of about 9,900 disposable domains, free-mail and role-account lists, a typo suggestion (`gmial.com` → `gmail.com`) and, optionally, the domain's SPF and DMARC policy. Built for cleaning sign-up and lead lists, blocking throw-away addresses in forms, and AI agents that need a quick yes/no before they email someone. **$0.40 per 1,000 emails.**

- **What you get:** `verdict`, `quality` (good / risky / bad), `reason`, normalized `email`, `mx` hosts and `mailProvider`, `disposable`, `freeProvider`, `role`, `typoSuggestion`, `domainSpf`, `domainDmarc`.
- **Price:** $0.0004 per checked email (`email`), no subscription; addresses whose domain could not be looked up are not charged.
- **Honest about what it checks:** no SMTP probing (from cloud IPs it is unreliable and gets blocked), so it never claims a mailbox exists. For a mailbox-level verdict, send the addresses that pass to Email Verifier.
- **Works everywhere:** API, MCP for AI agents, Make, Zapier, n8n, schedules and webhooks.

### Input

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `emails` | string\[] | – | Addresses to validate, one per line. Objects with an `email` key are accepted too. Duplicates are removed, also differently written forms of one address (`Jane@X.com`, `<jane@x.com>`). |
| `checkSpfDmarc` | boolean | true | Also read each domain's SPF record and DMARC policy (two extra DNS lookups per domain, cached for the run). |
| `maxEmails` | integer | 10000 | Safety cap for this run (up to 100,000); addresses beyond it are ignored and counted as `inputItemsDropped` in the `OUTPUT` record. |
| `concurrency` | integer | 10 | Addresses validated in parallel (1–20). |
| `includeRaw` | boolean | false | Attach the unmodified pre-check result to each record. |

```json
{
  "emails": ["support@apify.com", "jane.doe@gmial.com", "info@example.com"],
  "checkSpfDmarc": true
}
```

### Output

One dataset record per address:

```json
{
  "input": "support@apify.com",
  "email": "support@apify.com",
  "verdict": "role",
  "quality": "risky",
  "reason": "Role account (a shared mailbox such as info@ or sales@), not a person",
  "deliverable": "unknown",
  "domain": "apify.com",
  "canonicalEmail": "support@apify.com",
  "syntaxValid": true,
  "syntaxError": null,
  "domainExists": true,
  "mx": ["aspmx.l.google.com", "alt1.aspmx.l.google.com", "alt2.aspmx.l.google.com", "aspmx2.googlemail.com", "aspmx3.googlemail.com"],
  "nullMx": false,
  "hasMailServer": true,
  "mailProvider": "google",
  "disposable": false,
  "freeProvider": false,
  "role": true,
  "typoSuggestion": null,
  "domainSpf": { "record": "v=spf1 a mx include:_spf.google.com include:mailgun.org -all", "multipleRecords": false, "allQualifier": "-all", "includes": ["_spf.google.com", "mailgun.org"] },
  "domainDmarc": { "record": "v=DMARC1; p=reject; sp=reject; pct=100", "policy": "reject", "subdomainPolicy": "reject", "pct": 100, "rua": [] },
  "status": "ok",
  "charged": true,
  "chargedEvent": "email",
  "source": "dns",
  "fetchedAt": "2026-10-14T10:00:00.000Z"
}
```

A typo domain on the disposable list, with the suggested fix:

```json
{
  "input": "Jane.Doe@Gmial.com",
  "email": "jane.doe@gmial.com",
  "verdict": "disposable",
  "quality": "bad",
  "reason": "Disposable (throw-away) e-mail domain",
  "deliverable": "unknown",
  "domain": "gmial.com",
  "disposable": true,
  "freeProvider": false,
  "role": false,
  "typoSuggestion": "jane.doe@gmail.com",
  "status": "ok",
  "charged": true,
  "chargedEvent": "email",
  "source": "dns",
  "fetchedAt": "2026-10-14T10:00:00.000Z"
}
```

| `verdict` | Meaning | `quality` | `deliverable` |
| --- | --- | --- | --- |
| `invalid_syntax` | Not a valid address (`syntaxError` says why) | bad | `no` |
| `domain_not_found` | The domain does not exist (NXDOMAIN or an unknown top-level domain) | bad | `no` |
| `no_mail_server` | The domain accepts no mail: null MX, or no MX, A or AAAA record | bad | `no` |
| `disposable` | Throw-away mailbox domain | bad | `unknown` |
| `role` | Shared functional mailbox (info@, sales@ …) on a domain that accepts mail | risky | `unknown` |
| `ok` | Passed every local check (`risky` when `typoSuggestion` is set) | good | `unknown` |
| `unknown` | DNS lookups failed, so the domain could not be checked (status `error`, not charged) | risky | `unknown` |

| Field | Description |
| --- | --- |
| `email`, `domain`, `canonicalEmail` | Normalized address (lower case, punycode domain), its domain, and the provider-aware form for de-duplication (Gmail ignores dots and +tags). |
| `syntaxValid`, `syntaxError` | Strict RFC 5321/5322 syntax as mailbox providers accept it (quoted local parts and IP-literal domains are refused). |
| `domainExists`, `mx`, `nullMx`, `hasMailServer`, `mailProvider` | The domain's mail setup from DNS; a domain without MX that has an A/AAAA record still counts as having a mail server (RFC 5321). |
| `disposable`, `freeProvider`, `role`, `typoSuggestion` | List-based flags and the corrected address for a likely mistyped domain. |
| `domainSpf`, `domainDmarc` | The domain's SPF record (`allQualifier`, `includes`) and DMARC policy (`policy`, `pct`, `rua`); null when absent or when `checkSpfDmarc` is off. |
| `spfDmarcLookup` | `ok`, or `failed` when the DNS TXT lookups failed: `domainSpf` / `domainDmarc` are then unknown, not absent (a failed lookup is retried for the next address on that domain); null when `checkSpfDmarc` is off. |
| `quality`, `reason`, `deliverable` | Summary for list cleaning and a one-line explanation; `deliverable` is `no` or `unknown`, never `yes`. |

`status` is `ok` for every address with a verdict (charged), `error` when the domain could not be looked up (`error.code` `dns_lookup_failed`, verdict `unknown`, not charged; retry later) or the item timed out, `skipped_budget` when your run's cost cap was reached and `skipped_free_plan` for addresses beyond the free-plan limit (not checked, not charged). The run's key-value store `OUTPUT` record contains the totals (counts and charged amount).

### Pricing

Store discounts: the FREE and BRONZE tiers use the list prices below. SILVER receives 5% off; GOLD, PLATINUM and DIAMOND receive 10% off billable result events. The platform start fee is unchanged. Check the Actor's Store pricing table for the prices currently in effect.

| Event | Price | When charged |
| --- | --- | --- |
| `email` | **$0.0004** per email ($0.40 / 1,000) | once per address with a verdict, including `invalid_syntax`; never for `unknown` (DNS failure) |

Example: 25,000 addresses cost about $10. Apart from Apify's automatic Actor-start charge ($0.00005 per GB of run memory) there is no start fee or subscription. Use **Maximum cost per run** to cap spend; when the cap is hit the remaining addresses are returned with `status: "skipped_budget"` and are not charged. Accounts on the Apify free plan can process up to 10 email addresses per run; further addresses are returned with `status: "skipped_free_plan"` and are not charged. Upgrade to any paid Apify plan for full lists.

### Use with AI agents (MCP)

Use this Actor from Claude, ChatGPT, Cursor, VS Code or any other MCP client through the [Apify MCP server](https://mcp.apify.com). Add this server URL to your client and sign in with your Apify account when asked:

```text
https://mcp.apify.com?tools=sfoln/email-validator
```

To run the MCP server locally instead, start `npx -y @apify/actors-mcp-server --tools sfoln/email-validator` with your `APIFY_TOKEN` in the environment.

Example prompt:

> Validate these email addresses and tell me which ones to drop from my list and why: support@apify.com, jane.doe@gmial.com, info@example.com.

Runs started by an agent are billed like any other run (see Pricing). Agents can cap what a run may spend with `maxTotalChargeUsd`.

### API usage

Run the Actor and get its dataset items in one call (the call waits up to 5 minutes for the run to finish):

```bash
curl -X POST "https://api.apify.com/v2/acts/sfoln~email-validator/run-sync-get-dataset-items" \
  -H "Authorization: Bearer <APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"emails": ["support@apify.com", "jane.doe@gmial.com", "info@example.com"]}'
```

For large inputs, start the run asynchronously and download the dataset when it has finished:

```bash
## 1. Start a run: the response holds the run ID (data.id) and its dataset ID (data.defaultDatasetId)
curl -X POST "https://api.apify.com/v2/acts/sfoln~email-validator/runs" \
  -H "Authorization: Bearer <APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"emails": ["support@apify.com", "jane.doe@gmial.com", "info@example.com"]}'
## 2. Repeat until data.status is SUCCEEDED (each call waits up to 60 s)
curl "https://api.apify.com/v2/actor-runs/<runId>?waitForFinish=60" -H "Authorization: Bearer <APIFY_TOKEN>"
## 3. Download the results (format=json, csv or xlsx)
curl "https://api.apify.com/v2/datasets/<defaultDatasetId>/items?format=json" -H "Authorization: Bearer <APIFY_TOKEN>"
```

Python ([apify-client](https://pypi.org/project/apify-client/)):

```python
from apify_client import ApifyClient

client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("sfoln/email-validator").call(run_input={"emails": ["support@apify.com", "jane.doe@gmial.com", "info@example.com"]})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)
```

Instead of polling, you can attach a webhook to the run (see Integrations). The API tab of this page shows the same calls for JavaScript and the Apify CLI.

### Integrations

- **Make, Zapier and n8n:** run `sfoln/email-validator` with the Apify app (in n8n, the Apify node) and pass the dataset items to the next step of your workflow.
- **Google Sheets:** add the items to a sheet with the Google Sheets step of Make, Zapier or n8n, or download them as CSV or Excel from the run's Storage tab or the API (`format=csv` or `format=xlsx`).
- **Webhooks:** get a POST request when a run succeeds or fails: add a webhook in the Actor's Integrations tab, or pass `webhooks` when you start a run through the API.
- **Schedules:** run it hourly, daily or weekly with the same input from Apify Schedules; every run keeps its results in its own dataset.
- **More:** Slack, Google Drive, GitHub, LangChain, LlamaIndex and other [Apify integrations](https://docs.apify.com/platform/integrations).

### Related Actors

- [Website Contact Details Scraper](https://apify.com/sfoln/company-contacts-finder)
- [Google Maps Leads Scraper + Emails](https://apify.com/sfoln/google-maps-business-leads)
- [Phone Number Validator](https://apify.com/sfoln/phone-validator)

See [all our Actors](https://apify.com/sfoln) on Apify Store.

### FAQ

#### How much does it cost, and am I charged for failed items?

You pay per event at the prices under Pricing above, plus a run start fee of $0.00005 per GB of memory, with no subscription; platform usage is included. Errors and skipped items (`skipped_…` statuses) are never charged; a lookup that finds nothing is charged only where Pricing says so, and every record's `charged` field shows whether it was billed. Set **Maximum cost per run** to cap what a single run can spend.

#### How fresh is the data?

Each run queries its source while it runs, so you get what the source returns at that moment. Source-specific details, such as how often a provider refreshes its own database, are under Limits & notes.

#### What are the limits?

Per-run caps (for example the maximum number of items) are listed under Input, source limits under Limits & notes, and the free-plan limit under Pricing. For big batches, start the run asynchronously (see API usage) and read the dataset when it has finished.

#### Can I schedule runs or get results by webhook?

Yes. Create a schedule in Apify Console to run it hourly, daily or weekly with the same input, and add a webhook to get a POST request when a run finishes (see Integrations).

#### What does this Actor record about my runs?

This Actor records operational metrics about each run (item counts, duration, charges, error codes and a pseudonymous hash of your Apify user ID) to monitor reliability and costs. It never stores your input or results.

#### Does it check whether the mailbox exists?

No. It never connects to the mail server (SMTP probing from cloud IPs is unreliable and gets blocked). It settles what DNS and the bundled lists can settle: invalid syntax, domains that do not exist or accept no mail, and disposable domains. For a mailbox-level verdict (valid, invalid, catch-all) run the addresses that pass through Email Verifier.

#### Which addresses are charged?

Every address that gets a verdict, including invalid syntax, is charged one email event ($0.0004). An address whose domain could not be looked up because DNS failed comes back with status error and verdict unknown and is not charged; run it again later.

#### How do I get help?

Open an issue in the Actor's Issues tab and include the link to the run, so we can reproduce the problem. Feature requests are welcome there too.

### Limits & notes

- Up to 100,000 addresses per run (`maxEmails`). Each domain is looked up once per run and shared by all its addresses, so lists with many addresses on the same domains run fastest.
- DNS comes from Cloudflare's DNS-over-HTTPS service, with Google Public DNS as the fallback when Cloudflare fails or answers with an error. A domain whose own name servers are broken (SERVFAIL on both) ends as `unknown` and is not charged.
- No SMTP connection is ever made, so a deliverable-looking address may still bounce (a mailbox that does not exist, a full mailbox, a catch-all domain). Use Email Verifier for a mailbox-level verdict.
- The disposable-domain list (CC0, about 9,900 domains) and the free-mail and role lists ship with the Actor and are refreshed with its releases; a brand-new throw-away domain may not be listed yet.
- Results are not cached between runs and no address is stored by this Actor beyond your run's dataset.

# Actor input Schema

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

Email addresses to validate, one per line. Duplicates (also differently written forms of one address) are removed. Also accepts objects with an `email` field. Accounts on the Apify free plan can process up to 10 email addresses per run.

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

Also read each domain's SPF record and DMARC policy (two extra DNS lookups per domain, cached for the run). Turn off for the fastest runs on very large lists.

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

Safety cap on the number of addresses validated in this run; addresses beyond it are ignored and counted as inputItemsDropped in the OUTPUT record.

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

How many addresses are validated in parallel (1–20).

## `includeRaw` (type: `boolean`):

Attach the unmodified pre-check result (every field of the local check, e.g. the local part and whether DNS was consulted) to each record.

## Actor input object example

```json
{
  "emails": [
    "support@apify.com",
    "jane.doe@gmial.com",
    "info@example.com"
  ],
  "checkSpfDmarc": true,
  "maxEmails": 10000,
  "concurrency": 10,
  "includeRaw": false
}
```

# 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": [
        "support@apify.com",
        "jane.doe@gmial.com",
        "info@example.com"
    ]
};

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

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

```

## MCP server setup

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