# Bulk Email Verifier (`rodexco/bulk-email-verifier`) Actor

Verify email addresses at the protocol level: syntax, domain, disposable detection, and an SMTP mailbox check that never sends mail. You pay only for determined results. Timeouts and blanket rejections come back as unknown and are not charged.

- **URL**: https://apify.com/rodexco/bulk-email-verifier.md
- **Developed by:** [Syed Muhammad Ismail](https://apify.com/rodexco) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.90 / 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 Verifier

A product of **Rodexco** — fast, protocol-level email verification that tells you what's actually deliverable, not just what looks right.

### What it does

Checks a list of email addresses and returns a clear status for each:
syntax validity, domain existence, disposable-email detection, and a real
SMTP-level mailbox check — without ever sending an actual email.

### Why it's different

- **You only pay for determined results.** Addresses we genuinely can't
  verify (timeouts, unreachable servers) are returned as `unknown` and
  are **never charged** — most verification tools charge for every row
  regardless of outcome.
- **Blanket-reject detection.** Some mail providers reject every
  unverified address with an identical generic error specifically to
  defeat verification tools. We detect this pattern and report it
  honestly as `unknown` instead of falsely marking a real, working
  address as `invalid`.
- **Catch-all detection.** If a domain accepts mail to any address, we
  flag it as `risky` rather than reporting false confidence that a
  specific address is deliverable.
- **Respectful of mail servers.** Checks are throttled per mail domain,
  so we never hammer a single provider hard enough to get flagged or
  blocked.

### Input

| Field | Description | Default |
|---|---|---|
| `emails` | List of email addresses to verify (or a dataset/KV store reference) | — |
| `smtpTimeoutMs` | Timeout per SMTP connection attempt | 10000 |
| `maxConcurrentPerDomain` | Max simultaneous checks against one mail domain | 2 (max 3) |
| `checkCatchAll` | Detect catch-all domains | true |
| `checkRoleAccounts` | Flag role-based addresses (info@, admin@, etc.) | true |

### Output

One record per address:

```json
{
  "email": "name@example.com",
  "status": "valid",
  "reason": "Mailbox accepted",
  "isDisposable": false,
  "isRoleAccount": false,
  "isCatchAll": false,
  "possibleBlanketReject": false,
  "mxRecordFound": true,
  "checkedAt": "2026-10-03T12:00:00Z"
}
```

Possible `status` values: `valid`, `invalid`, `disposable`, `risky`, `unknown`.

### Notes

- `unknown` is a legitimate, honest outcome — not a failure. It means we
  could not conclusively determine deliverability (server timeout, or a
  provider that uniformly rejects all unverified probes).
- This Actor never sends actual email — verification stops before the
  `DATA` command in every case.
- Pricing applies only to `valid`, `invalid`, `risky`, and `disposable`
  results.

### Support

Built and maintained by Rodexco. For issues or feature requests, reach
out via the Actor's Issues tab on Apify Store.

# Actor input Schema

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

Addresses to verify, one per line. To load them from storage instead, add a line such as dataset:DATASET_ID#email or kv:STORE_ID/RECORD_KEY#email. The part after # is the field name and defaults to email. The sample lines use the reserved .invalid domain so a run finishes without contacting a real mail server.

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

How long to wait for each SMTP connection, in milliseconds. The same limit applies to the domain lookup. The random-address probe uses the time left on that same connection.

## `maxConcurrentPerDomain` (type: `integer`):

How many checks may run at the same time against one mail domain. Other domains still run in parallel. The maximum is 3, so a large batch cannot open a pile of connections to one server.

## `checkCatchAll` (type: `boolean`):

After a mail server accepts an address, check one random address at the same domain on that same connection. If the server accepts that too, the result is risky instead of valid.

## `checkRoleAccounts` (type: `boolean`):

Mark shared mailboxes such as info@, admin@, and support@ with isRoleAccount. This is a flag on the result, not a separate status.

## Actor input object example

```json
{
  "emails": [
    "not-an-email",
    "person@mailbox-check.invalid",
    "info@mailbox-check.invalid"
  ],
  "smtpTimeoutMs": 10000,
  "maxConcurrentPerDomain": 2,
  "checkCatchAll": true,
  "checkRoleAccounts": true
}
```

# Actor output Schema

## `results` (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": [
        "not-an-email",
        "person@mailbox-check.invalid",
        "info@mailbox-check.invalid"
    ],
    "smtpTimeoutMs": 10000,
    "maxConcurrentPerDomain": 2,
    "checkCatchAll": true,
    "checkRoleAccounts": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("rodexco/bulk-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": [
        "not-an-email",
        "person@mailbox-check.invalid",
        "info@mailbox-check.invalid",
    ],
    "smtpTimeoutMs": 10000,
    "maxConcurrentPerDomain": 2,
    "checkCatchAll": True,
    "checkRoleAccounts": True,
}

# Run the Actor and wait for it to finish
run = client.actor("rodexco/bulk-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": [
    "not-an-email",
    "person@mailbox-check.invalid",
    "info@mailbox-check.invalid"
  ],
  "smtpTimeoutMs": 10000,
  "maxConcurrentPerDomain": 2,
  "checkCatchAll": true,
  "checkRoleAccounts": true
}' |
apify call rodexco/bulk-email-verifier --silent --output-dataset

```

## MCP server setup

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