# Email Verifier & Validator for Bulk Email Lists (`mosaicked_sponge/email-verifier`) Actor

Bulk email verification and email list cleaning without sending mail. Checks syntax, live MX/DNS, disposable and role addresses, fixes typos (gmial.com to gmail.com) and labels every email valid, risky, invalid or unknown with a reason.

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

## Pricing

from $0.37 / 1,000 verified 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

### What does Email Verifier & Validator do?

![Email Verifier & Validator for Bulk Email Lists - sample output](https://api.apify.com/v2/key-value-stores/QpfrAb6wndEDB0tDd/records/output-D6eh1E7uGDFvnWo99.png)

Email Verifier & Validator checks email lists in bulk **without sending a single email**. For every address it returns a clear **status** (`valid`, `risky`, `invalid` or `unknown`), the **reason** in plain English, a **0-100 score**, and the evidence behind it:

- **Syntax**: RFC length and character rules; malformed strings are skipped **for free**
- **Live DNS / MX check**: the domain exists, publishes mail servers, and those servers resolve to a public address
- **Null MX detection** (RFC 7505): domains like `example.com` that publicly refuse all email
- **Disposable / throwaway domains**: 9,900+ domains (mailinator, guerrillamail, 10minutemail, yopmail ...)
- **Role inboxes**: `info@`, `sales@`, `support@`, `press@`, `kontakt@`, `noreply@` ... flagged
- **Free providers**: Gmail, Yahoo, Outlook, iCloud, GMX, Proton ... flagged
- **Typo fixes**: `gmial.com` -> `gmail.com`, `hotmial.com` -> `hotmail.com`, `acme.con` -> `acme.com` in `didYouMean`
- **Gmail username rules**: Gmail addresses that cannot exist (illegal characters, too long) are marked invalid
- **Same mailbox detection**: `j.o.h.n+news@gmail.com` and `john@gmail.com` are linked with `sameMailboxAs`
- **Mail provider** (Google, Microsoft 365, Proofpoint, Mimecast, Zoho ...) and **SPF / DMARC** presence

Paste a list, or **chain it after another Actor**: give it the dataset of [Website Contact Extractor](https://apify.com/mosaicked_sponge/website-contact-extractor) or any lead scraper and it verifies every email found, keeping `sourceWebsite` and `sourceCompany` on each row.

### How much does it cost to verify emails?

Pay per result: you pay only for addresses that come back as a result row. **Duplicates and values that are not email addresses are free.**

| Apify plan | Price per 1,000 emails |
|---|---|
| Free | **$0.53** |
| Starter (Bronze) | $0.47 |
| Scale (Silver) | $0.42 |
| Business (Gold and up) | $0.37 |

Plus $0.00005 per run start. No platform usage fees on top. 10,000 emails cost about $5.30 on the Free plan, and the monthly $5 free credit covers roughly 9,000 emails. A run of 1,000 addresses finishes in a few seconds.

### Honest about what it does not do

**It does not probe individual mailboxes (no SMTP check).** Confirming that one specific mailbox exists needs an SMTP conversation on port 25, and Apify, like every major cloud, blocks outbound port 25. We measured it from an Apify run: the MX lookup answered in 6 ms, while port 25 connections to Gmail, iCloud and Zoho mail servers were unreachable or timed out. Tools that promise per-mailbox proof on Apify are sending your list to an outside service.

So **`valid` means: well-formed address, on a domain whose mail servers work, with no risk signal.** It is not proof that a person reads that inbox, and every row says so (`mailboxChecked: false`, `catchAll: null`). This check removes the bulk of hard bounces (dead domains, no mail servers, typos, throwaway domains) without your list ever leaving Apify.

### Statuses and reasons

| status | reason | Meaning |
|---|---|---|
| `valid` | `domain_accepts_mail` | Well-formed, domain has working mail servers, no risk flags |
| `valid` | `role_account` | Same, but a shared inbox (info@, sales@ ...) |
| `risky` | `possible_typo` | Domain looks like a typo of a big provider; see `didYouMean` |
| `risky` | `disposable_domain` | Throwaway inbox service |
| `risky` | `a_record_only` | No MX record; mail falls back to the web server address |
| `risky` | `gmail_username_unusually_short` | Gmail username under 6 characters (rare legacy accounts) |
| `invalid` | `domain_not_found` | Domain does not exist |
| `invalid` | `no_mail_server` | Domain exists but has no mail server |
| `invalid` | `domain_accepts_no_mail` | Null MX: the domain refuses all email |
| `invalid` | `mail_server_unreachable` | MX hosts do not resolve to a public address |
| `invalid` | `gmail_username_not_allowed` | This Gmail address cannot exist |
| `unknown` | `dns_error` | DNS did not answer in time; retry later (not a bad address) |

### Input

```json
{
  "emails": ["support@github.com", "jane.doe@gmial.com", "test@mailinator.com", "hello@this-domain-does-not-exist-9f3k2.com"],
  "inputDatasetId": "",
  "emailFields": "",
  "maxEmails": 10000,
  "includeInvalidSyntax": false,
  "checkSpfDmarc": true
}
```

- `emails`: one per line; `Name <a@b.com>`, commas and semicolons are fine.
- `inputDatasetId`: verify the emails in another run's dataset. Field names are auto-detected (`emails`, `email`, `primaryEmail`, `emailAddress`, `contactEmail`) or set them in `emailFields`.
- `includeInvalidSyntax`: on = strings that contain an @ but are malformed come back as `invalid` rows (charged); off (default) = skipped for free and listed in the OUTPUT record.

### Output

One row per unique, well-formed address:

```json
{
  "email": "jane.doe@gmial.com",
  "status": "risky",
  "reason": "possible_typo",
  "reasonText": "The domain looks like a typo of a big provider (see didYouMean).",
  "score": 20,
  "didYouMean": "jane.doe@gmail.com",
  "domain": "gmial.com",
  "isDisposable": true,
  "isRoleAccount": false,
  "isFreeProvider": false,
  "domainExists": true,
  "hasMxRecords": false,
  "mxRecords": [],
  "mailProvider": null,
  "hasSpf": false,
  "hasDmarc": false,
  "mailboxChecked": false,
  "catchAll": null,
  "normalizedMailbox": "janedoe@gmail.com",
  "sameMailboxAs": null,
  "flags": ["disposable_domain", "possible_typo", "a_record_only"],
  "checkedAt": "2026-10-20T17:20:00Z"
}
```

The **OUTPUT** record of each run has the totals per status, duplicates removed, and every skipped (non-email) value with the reason.

### Spending limits

If you set a **maximum cost per run**, the Actor verifies as many addresses as fit and stops cleanly, telling you how many were left out (never charged). If the limit is below the price of one email, nothing is verified and the status message tells you how to raise it.

### Tips

- Filter the dataset by `status` = `valid` for sending; review `risky`; drop `invalid`; retry `unknown`.
- Run it on a schedule against fresh lead lists, or via webhook right after your scraper finishes.
- Works with the Apify API, Python and JavaScript clients, Make, Zapier, n8n and the Apify MCP server.

### FAQ

**Does it send emails?** No. Only DNS questions about each domain are asked.

**Is it legal?** Checking the technical validity of addresses you already hold is routine. How you got the list and what you send is governed by GDPR, CAN-SPAM and local rules.

**Can it detect catch-all domains?** Not without SMTP access, which Apify blocks, so `catchAll` is always `null` rather than a guess.

**Found a wrong verdict?** Open an issue with the address's domain and we will fix the rule.

# Actor input Schema

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

One address per line. Commas, semicolons and "Name <a@b.com>" are fine. Duplicates (case-insensitive) are removed for free; values that are not email addresses are skipped for free and listed in the OUTPUT record.

## `inputDatasetId` (type: `string`):

Verify the emails in a dataset, e.g. the output of Website Contact Extractor or any lead scraper. Each result keeps sourceWebsite / sourceCompany from the original row.

## `emailFields` (type: `string`):

Comma-separated field names that hold the emails (string or list). Leave empty for auto-detect: emails, email, primaryEmail, emailAddress, contactEmail.

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

Safety cap on unique addresses verified (and charged) in one run.

## `includeInvalidSyntax` (type: `boolean`):

Off (default): malformed strings are skipped, free, and listed in the OUTPUT record. On: strings that contain an @ but are malformed get an 'invalid' result row (charged like any row), handy if you need one output row per input.

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

Adds hasSpf / hasDmarc (domain authentication signals) to each row. Two extra DNS lookups per domain.

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

DNS lookups run per domain, so 10,000 addresses on 3,000 domains cost 3,000 lookups.

## Actor input object example

```json
{
  "emails": [
    "support@github.com",
    "press@apple.com",
    "jane.doe@gmial.com",
    "test@mailinator.com",
    "hello@this-domain-does-not-exist-9f3k2.com"
  ],
  "maxEmails": 10000,
  "includeInvalidSyntax": false,
  "checkSpfDmarc": true,
  "concurrency": 30
}
```

# Actor output Schema

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

One row per unique, well-formed email address.

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

OUTPUT record: counts by status, duplicates removed, skipped (non-email) values.

# 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@github.com",
        "press@apple.com",
        "jane.doe@gmial.com",
        "test@mailinator.com",
        "hello@this-domain-does-not-exist-9f3k2.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("mosaicked_sponge/email-verifier").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@github.com",
        "press@apple.com",
        "jane.doe@gmial.com",
        "test@mailinator.com",
        "hello@this-domain-does-not-exist-9f3k2.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("mosaicked_sponge/email-verifier").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@github.com",
    "press@apple.com",
    "jane.doe@gmial.com",
    "test@mailinator.com",
    "hello@this-domain-does-not-exist-9f3k2.com"
  ]
}' |
apify call mosaicked_sponge/email-verifier --silent --output-dataset

```

## MCP server setup

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

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/D6eh1E7uGDFvnWo99/builds/G8ABmXfuR9n7ypBWE/openapi.json
