# Bulk Email Validation API | $0.90/1K | MX + Disposable (`extractkit/bulk-email-validation-api`) Actor

Validate email addresses in bulk. Syntax check, MX/DNS lookup, disposable and role-account detection, typo suggestions and a deliverability score. No SMTP probing. $0.90 per 1,000 emails.

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

## Pricing

from $0.90 / 1,000 address verifieds

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/platform/actors/running/actors-in-store#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 Verifier & Validator — Bulk Email Validation

Clean your email list before you send. This Actor verifies email addresses in bulk and tells you which are **safe to send to**, which will **bounce**, and which are **risky** — with a reason for every decision.

No API key. No SMTP setup. No account anywhere else.

### What it checks

| Check | What it catches |
|---|---|
| **Syntax** | Malformed addresses, per RFC 5321 length and character rules |
| **Domain / MX** | Domains that cannot receive mail at all |
| **Typo detection** | `gmial.com` → suggests `gmail.com` — recovers real customers from bad data |
| **Disposable** | Mailinator, Guerrilla Mail, temp-mail and ~100 other throwaway providers |
| **Role accounts** | `info@`, `support@`, `sales@` — deliverable, but they hurt conversion and raise complaints |
| **Free providers** | Gmail, Yahoo, Outlook — valid, but not business contacts |
| **Catch-all** | Domains that accept everything, where "valid" means nothing |
| **SMTP mailbox** *(optional)* | Individual mailbox existence — see the honest note below |

### Why bounce rate matters

Mailchimp, SendGrid and Klaviyo suspend accounts over high bounce rates, and above ~2% your domain reputation starts costing you inbox placement on the mail that *does* deliver. Verifying first is cheaper than recovering a burned sending domain.

### Input

Paste addresses directly:

```json
{
  "emails": ["ada@example.com", "support@gmail.com", "someone@mailinator.com", "typo@gmial.com"]
}
```

Or point it at a list — the right approach for anything over a few hundred:

```json
{
  "emailListUrl": "https://example.com/contacts.csv",
  "csvColumn": "email"
}
```

A plain `.txt` with one address per line works too. For Google Sheets: **File → Share → Publish to web → CSV**.

### Output

One row per input address, in a shape you can filter directly:

```json
{
  "email": "ada@example.com",
  "normalized": "ada@example.com",
  "canonical": "ada@example.com",
  "status": "deliverable",
  "score": 85,
  "reason": "mx_valid",
  "domain": "example.com",
  "mxFound": true,
  "mxHost": "mx.example.com",
  "isDisposable": false,
  "isRoleAccount": false,
  "isFreeProvider": false,
  "didYouMean": null,
  "checkedAt": "2026-08-16T09:00:00.000Z"
}
```

**Four statuses:**

- `deliverable` — safe to send
- `undeliverable` — do not send; it will bounce
- `risky` — deliverable but low quality (role account, catch-all domain, or A-record-only)
- `unknown` — a check could not complete; `reason` says which

Filter on `status`, or on `score >= 70` to keep deliverable and drop risky. The score is **deterministic** — the same address always scores the same, so your filters keep working tomorrow.

The run also writes a `SUMMARY` record with counts by status, so you can see your list quality at a glance.

### Honest notes

These are the things most verifiers do not tell you. They are here because a wrong answer costs you more than a missing one.

**SMTP checking is off by default, and that is deliberate.** Outbound port 25 is blocked on Apify and virtually every cloud platform, so an SMTP probe will usually time out. Worse, Gmail and Outlook accept `RCPT TO` for addresses that do not exist, so a positive result there proves nothing anyway. When SMTP cannot answer, the address falls back to its DNS-level verdict and `smtp.reason` records why the probe failed — the score stays capped at `mx_valid` (85) and the reason is never `smtp_verified`, so a failed probe can never be mistaken for a confirmed mailbox.

**What DNS-level verification can and cannot tell you.** It proves the domain accepts mail. It cannot prove one specific mailbox exists. That distinction is reported honestly in `reason` (`mx_valid` means the domain is good, `smtp_verified` means the mailbox was confirmed) rather than being blurred into a single confident-looking "valid".

**The disposable list is embedded, not fetched.** That keeps the Actor from breaking when a third-party blocklist goes down, but it means `isDisposable: false` means "not on our list", not "definitely permanent".

**Runs do not fail because an address is bad.** An address that does not exist is a successful verification with a negative result. The run fails only if there is genuinely nothing to verify or DNS itself is unreachable.

### De-duplication

On by default, and provider-aware: Gmail ignores dots and `+tags`, so `a.da+news@gmail.com` and `ada@gmail.com` are the same mailbox and are verified once. Every input row still appears in the output, so results line up with your source file row-for-row.

### Pricing

Pay per verified address. You are charged only for results actually delivered to your dataset, one at a time, so your run budget cap is respected exactly.

### Common uses

- Clean a list before a Mailchimp / SendGrid / Klaviyo campaign
- Validate signups at import time to keep junk out of the CRM
- De-risk a purchased or scraped lead list before outreach
- Recover mistyped addresses from signup forms via `didYouMean`
- Filter role accounts out of cold outreach

### Integrations

Runs on a schedule, via API, or from Make, Zapier and n8n. Results export as JSON, CSV, Excel or XML.

# Actor input Schema

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

Addresses to verify, one per line. Accepts a bare address (ada@example.com) or a pasted contact (Ada Lovelace <ada@example.com>). For lists larger than a few hundred, use the list URL field below instead.

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

Public link to a .txt or .csv file of addresses — one per line, or a CSV with an 'email' column. Use this for bulk lists instead of pasting thousands of addresses above. Google Sheets: File → Share → Publish to web → CSV.

## `csvColumn` (type: `string`):

Which column holds the addresses, when the list URL points at a CSV with a header row. Ignored for plain text files.

## `deduplicate` (type: `boolean`):

Verify each unique mailbox once. Understands provider rules — on Gmail, a.da+news@gmail.com and ada@gmail.com are the same mailbox. Every input row still appears in the output, so results line up with your source file.

## `flagRoleAccounts` (type: `boolean`):

Mark shared addresses (info@, support@, sales@) as 'risky' rather than 'deliverable'. They usually deliver fine, but convert poorly and raise spam complaints. Turn off to treat them as normal.

## `smtpCheck` (type: `boolean`):

Try to confirm the individual mailbox by connecting to the mail server. IMPORTANT: outbound port 25 is blocked on Apify (and most cloud platforms), so this usually times out; the address then keeps its DNS-level verdict and the 'smtp' field records why the probe failed. It also cannot prove anything for Gmail or Outlook, which accept every address by design. Leave off unless you know your case needs it — DNS-level results are unaffected either way.

## `smtpTimeoutMs` (type: `integer`):

How long to wait for a mail server before giving up and returning 'unknown'. Only used when the SMTP check is enabled.

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

How many addresses to verify in parallel. DNS is the bottleneck; the default suits almost every list.

## `dnsServers` (type: `array`):

Optional. Resolver IPs such as 1.1.1.1 or 8.8.8.8. Leave empty to use the platform default, which is correct for almost all runs.

## Actor input object example

```json
{
  "emails": [
    "ada@example.com",
    "support@gmail.com",
    "someone@mailinator.com",
    "typo@gmial.com"
  ],
  "emailListUrl": "https://example.com/contacts.csv",
  "csvColumn": "email",
  "deduplicate": true,
  "flagRoleAccounts": true,
  "smtpCheck": false,
  "smtpTimeoutMs": 8000,
  "concurrency": 20,
  "dnsServers": []
}
```

# Actor output Schema

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

All verification results as JSON — status, score, reason, MX, disposable/role flags and typo suggestions.

## `resultsCsv` (type: `string`):

The same results as a CSV download, for spreadsheet and CRM import.

## `deliverableOnly` (type: `string`):

Just the addresses safe to send to — the list most buyers actually want, ready for spreadsheet or CRM import. One row per mailbox: addresses that reach the same inbox are collapsed so nobody is mailed twice.

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

Counts by status, plus disposable and role-account totals and how many domains were resolved.

## `runDetails` (type: `string`):

This run in Apify Console, including its log.

# 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": [
        "ada@example.com",
        "support@gmail.com",
        "someone@mailinator.com",
        "typo@gmial.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("extractkit/bulk-email-validation-api").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": [
        "ada@example.com",
        "support@gmail.com",
        "someone@mailinator.com",
        "typo@gmial.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("extractkit/bulk-email-validation-api").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": [
    "ada@example.com",
    "support@gmail.com",
    "someone@mailinator.com",
    "typo@gmial.com"
  ]
}' |
apify call extractkit/bulk-email-validation-api --silent --output-dataset

```

## MCP server setup

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

```

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/SlOzqmG18371hnrh3/builds/VpxG8myQIteGj1qSt/openapi.json
