# Bulk Email Verification API - List Cleaning and Spam Traps (`nabeelbaghoor/bulk-email-verification-api`) Actor

Clean a mailing list before you send to it. Submits the whole list as one job, verifies each address at the receiving mail server, and returns a deliverable, risky, undeliverable or unknown verdict with the exact reason behind it. Pay per result.

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

## Pricing

$6.00 / 1,000 results

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?

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

## Bulk Email Verification API - List Cleaning and Spam Traps

Clean a mailing list before you send to it. Every address is checked at the receiving mail server and comes back with a plain verdict and the exact reason behind it. One flat row per address.

### What this actor does

- **Verifies the whole list as one job.** The list is submitted in a single request and the provider works through it at whatever speed the receiving mail servers allow. That is why a list of ten thousand costs far less time here than ten thousand separate lookups, and why there is no per-address call to make.
- **Gives you a verdict and a reason, not one merged score.** Every address gets a broad verdict, deliverable, risky, undeliverable or unknown, and a precise status code underneath it. Route on the verdict; diagnose with the status. Forty-two distinct status codes, each its own value and each filterable.
- **Separates a bad address from an unanswered one.** A server that timed out, grey-listed the check or refused the sender is not the same as a mailbox that does not exist, and those come back as their own status codes rather than as a failure. The run tells you how many, because they are usually good addresses that resolve on a second pass or at a higher quality level.
- **Flags spam traps.** A honeypot is an address that exists only to catch senders who mail without verifying, and hitting one damages your sending reputation out of all proportion to the single message. It gets its own column and its own filter.
- **Separates disposable, role, free mail and catch-all.** Four different reasons to be careful, four different decisions, four columns rather than one lumped quality flag.
- **Suggests corrections for misspelt addresses.** When the provider believes an address is a near miss for a real one, the suggested spelling comes back in its own column. That is the difference between losing a signup to a typo and recovering it.
- **Lets you choose how hard to try.** Three quality levels, from the everyday setting up to one that keeps retrying the uncooperative mail servers. Higher levels turn timeouts into real verdicts and cost more credits on your own plan, so it is your call rather than a default.
- **Filters at the provider, not after the fact.** Status filters are sent with the request, so addresses you asked to leave out are never paged over.
- **Never submits more than you asked for.** This provider charges when a job is submitted rather than when it is read, so the list is trimmed to your result cap before it is sent. The cap limits what you spend on your own plan, not just what you spend here.
- **Leaves the job on your account.** The results stay readable for free at the provider until their retention period expires, so a second run costs nothing. There is a switch to delete the job immediately when a list is sensitive.
- **Never charges for a miss.** Only rows carrying a verification result are billed.

### Input

| Field | What it does |
| --- | --- |
| `emails` | Addresses to verify, one per line. |
| `quality` | Standard, High or Extreme effort per address. |
| `deduplication` | Off, Safe or Relaxed duplicate detection. |
| `classifications` | Keep only deliverable, risky, undeliverable or unknown. |
| `deliverableOnly` | Keep only addresses confirmed deliverable. |
| `statuses` | Keep only these status codes. |
| `excludeStatuses` | Drop these status codes. |
| `excludeDisposable` | Drop throw-away addresses. |
| `excludeRoleAccounts` | Drop shared mailboxes such as info and sales. |
| `excludeFreeEmail` | Drop consumer mail providers. |
| `excludeCatchAll` | Drop servers that accept everything. |
| `excludeHoneypots` | Drop identified spam traps. |
| `excludeDuplicates` | Drop repeats found inside the job. |
| `jobName` | A label for the job on your provider account. |
| `retention` | How long the provider keeps the job. |
| `deleteJobWhenDone` | Delete the job as soon as results are read. |
| `priority` | How this job competes with your other jobs. |
| `providerWaitSeconds` | How long the provider holds each request open. |
| `jobTimeoutMinutes` | Give up waiting after this long. |
| `reportBalance` | Log your remaining credits first. |
| `skipNotFound` | Leave refused inputs out of the dataset. |
| `maxResults` | Cap on rows, and on what the job costs to submit. |
| `username` | Your own account user name. |
| `password` | Your own account password. Stored as a secret. |
| `apiKey` | A bearer token instead, if you already hold one. |

### Example output

```json
{
  "found": true,
  "query": "j.smith@example.com",
  "email": "j.smith@example.com",
  "classification": "Deliverable",
  "status": "Success",
  "isDeliverable": true,
  "isRisky": false,
  "localPart": "j.smith",
  "domain": "example.com",
  "asciiDomain": "example.com",
  "isDisposable": false,
  "isRoleAccount": false,
  "isFreeEmailAddress": false,
  "isCatchAll": false,
  "isHoneypot": false,
  "isTemporaryFailure": false,
  "hasInternationalMailboxName": false,
  "hasInternationalDomainName": false,
  "syntaxFailureIndex": null,
  "suggestions": [],
  "isDuplicate": false,
  "duplicateOfIndex": null,
  "index": 0,
  "custom": null,
  "completedOn": "2026-08-28T09:14:02.5957669Z",
  "jobId": "18f5a933-af67-421c-b63a-1cbee297fa19",
  "jobQuality": "Standard",
  "jobDeduplication": "Safe",
  "error": null,
  "raw": { }
}
```

### Frequently asked questions

#### How is this different from a syntax or MX check?

A syntax check tells you the address is well formed. An MX check tells you the domain can receive mail at all. Neither tells you whether that particular mailbox exists, which is the thing that decides whether your message bounces. This verification opens a conversation with the receiving mail server and asks about the specific address. That is why it is slower than a regular expression, and why it is the only check worth running before a send.

#### What is the difference between the verdict and the status code?

The verdict is the broad grouping: deliverable, risky, undeliverable or unknown. The status code is the precise reason: the mailbox does not exist, the domain has a null MX record, the mail exchanger is a honeypot, the server is catch-all, the address is a duplicate of line 402. There are four verdicts and forty-two status codes. Route your sending on the verdict, because that is the decision. Investigate with the status, because that is the explanation. Both are columns here, and both can be filtered.

#### What does risky actually mean?

Risky means the address is real enough to accept mail but carries something you should know about before sending. A catch-all server that will accept anything and tell you nothing. A disposable address that will be gone next week. A parked mail exchanger. A server that does not allow multiple recipients and therefore cannot confirm the individual mailbox. These are genuinely different situations, which is why the status code matters more than the verdict here than anywhere else.

#### Why did some addresses come back unknown?

Because the answer could not be reached, not because it was negative. A DNS query timed out, the mail server was temporarily unavailable, the mailbox check timed out, the receiving server grey-listed the attempt. Grey-listing in particular is a spam defence that refuses a first contact from an unfamiliar sender and accepts the retry, so it is a statement about the check rather than about the address. Treating unknowns as bad is one of the most common ways to throw away good subscribers. Run them again, or run them at a higher quality level, which exists precisely for this.

#### What do the quality levels change?

How long the provider persists with a mail server that will not give a straight answer. Standard is the everyday setting and resolves the large majority of addresses. High and Extreme retry the awkward cases for longer, which converts some timeouts and grey-listing into real verdicts. Higher levels consume more credits per address on your own plan, so the choice is exposed rather than made for you. A sensible pattern is to run the whole list at Standard and re-run only the unknowns at High.

#### What is a spam trap and why does it get its own column?

A honeypot, or spam trap, is an address that exists for the sole purpose of catching senders who mail without permission or without verifying. Some are addresses that were abandoned and later recycled as traps; others were never real people. Sending to one can affect your standing with the major mailbox providers far out of proportion to a single message, which is why it is its own flag here rather than being folded into a general risk score.

#### Should I drop catch-all domains?

It depends on how much bounce risk you can carry. A catch-all server accepts mail addressed to every possible mailbox, so it cannot tell you whether one particular address is real. The result is neither a pass nor a fail. Plenty of legitimate business domains are configured this way, so dropping them all removes real people along with the risk. The flag and the filter are both here so that you decide.

#### How does duplicate detection work?

Turn it on and the provider marks repeats inside the job rather than silently collapsing them. Safe only marks addresses that are certainly the same mailbox. Relaxed also treats provider-specific aliases, such as dots and plus tags on modern mail services, as the same person, which catches the duplicates a plain text comparison misses. Either way a duplicate still comes back as a row, flagged, with the index of the line it repeats, so you can see what was merged instead of guessing.

#### Why does the result cap also limit what I spend at the provider?

Because this provider bills when a job is submitted, not when the results are read. Submitting a thousand addresses to return a hundred would spend your credits on nine hundred rows you never see. So the address list is trimmed to the result cap before it is sent, and the run says how many were left out. Raise the cap to include them.

#### What happens to my list after the run?

The job stays on your own provider account until its retention period expires, and its results can be read again for free while it does. That is usually what you want. Set a retention period in the input to shorten it, or turn on the delete switch to have the job and the addresses in it removed as soon as this run has read them.

#### Do I need my own API key?

Yes. This actor does not include data access. You use your own account with Verifalia, the provider whose email verification API this actor calls, and supply either the account user name and password or a bearer token you already hold. They are stored as Apify secrets. Use a dedicated user rather than the account administrator and grant it only the email verification permissions, so a leaked credential costs as little as possible. Your own plan, allowance and terms apply.

#### Why would a correct password still be refused?

This provider lets an account restrict which network addresses may use it, and a request from outside that list is refused in exactly the same way as a wrong password. If the credential works from your own machine but not here, check the account firewall rules before you go looking for a typo. The run says so in its error message for that reason.

#### How much does a run cost?

Pricing is pay per result: you are charged for each address verified into the dataset, and never for inputs refused before submission, for rows dropped by the filters, or for repeated lines in your own input. Apify platform usage is included in the per-result price. Your own provider credits are separate and billed by them.

### Keyword map

bulk email verification API, email validation API, mailing list cleaning, email list hygiene, mailbox verification, SMTP verification, bounce rate reduction, deliverability check, spam trap detection, honeypot detection, disposable email detection, DEA detection, throwaway address check, catch all server detection, role account detection, free mail detection, duplicate email detection, email typo correction, did you mean email, sender reputation protection, list scrubbing API, email hygiene automation, cold email list cleaning, newsletter list cleaning, signup form validation

# Actor input Schema

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

The addresses to check, one per line. The whole list is submitted as a single verification job, which is how this provider works and why a long list costs far less time than the same addresses checked one at a time. Repeated lines are submitted once.

## `quality` (type: `string`):

How hard the provider works before it gives up on an address. Standard is the everyday setting. High and Extreme retry the uncooperative mail servers for longer, which turns some timeouts and grey-listing into real verdicts, and they cost more credits per address on your own plan. Leave empty to use your account default.

## `deduplication` (type: `string`):

Whether the provider marks repeated addresses inside the job, and how strictly. Safe only marks addresses that are certainly the same mailbox. Relaxed also treats provider-specific aliases, such as dots and plus tags on modern mail services, as the same person. Duplicates still return a row, marked as such, so nothing disappears silently.

## `classifications` (type: `array`):

Only keep rows with one of these verdicts. This is the broad grouping the provider sorts every result into, and it is what most runs route on. Leave empty to keep every verdict.

## `deliverableOnly` (type: `boolean`):

Drop everything except addresses that came back deliverable. The one-tick way to turn a raw list into a sending list. It also drops the risky and unknown rows, and the unknown ones are often good addresses behind a server that would not answer, so consider verifying those again rather than discarding them.

## `statuses` (type: `array`):

The precise reason behind the verdict, rather than the verdict itself. Only keep rows whose status is one of these. This is asked of the provider directly, so the run never pages over addresses you did not want. Setting this overrides the exclusions below, because the provider accepts only one of the two at a time.

## `excludeStatuses` (type: `array`):

The mirror of the field above: keep everything except these. Also asked of the provider directly. Ignored when an explicit keep list is set.

## `excludeDisposable` (type: `boolean`):

Leave out throw-away addresses, whether the provider recognised the domain, the mailbox or the mail exchanger as disposable. These are the usual signature of a signup that was never meant to be reachable.

## `excludeRoleAccounts` (type: `boolean`):

Leave out shared mailboxes such as info, sales, support and postmaster. They are real and deliverable, but they belong to a function rather than a person, and marketing sent to them is a common source of complaints.

## `excludeFreeEmail` (type: `boolean`):

Leave out addresses on consumer mail services, keeping only company domains. Useful when the list is meant to be a business one.

## `excludeCatchAll` (type: `boolean`):

Leave out addresses whose receiving server accepts mail for every possible mailbox, so the individual address cannot be confirmed either way. Many real business domains are configured this way, so dropping them removes real people as well as risk. The flag is there either way, so you can decide rather than have it decided.

## `excludeHoneypots` (type: `boolean`):

Leave out addresses identified as honeypots. A spam trap exists purely to catch senders who mail without verifying, and hitting one can damage your sending reputation far out of proportion to the single message.

## `excludeDuplicates` (type: `boolean`):

Leave out addresses the provider marked as a repeat of an earlier entry in the same job. Only does anything when duplicate detection is switched on above.

## `jobName` (type: `string`):

An optional label for this verification job, for your own reference when you look at it later in your provider account.

## `retention` (type: `string`):

How long the provider keeps this job and the addresses in it once it completes, written as dd.hh:mm:ss, for example 0:30:0 for thirty minutes or 2.0:0:0 for two days. The minimum is five minutes and the maximum is thirty days. Leave empty to use your account default.

## `deleteJobWhenDone` (type: `boolean`):

Remove the job and the addresses in it from your provider account as soon as this run has read them. Off by default: the job has already been paid for and its results can be read again for free while it exists, so deleting it early throws that away. Turn it on when the list is sensitive enough that it should not sit on the provider.

## `priority` (type: `integer`):

How this job competes with your other concurrent jobs on the same account, from 0 for the lowest to 255 for the highest, with 127 as normal. Only matters when you have several running at once. Leave at zero to let the provider treat them all the same.

## `providerWaitSeconds` (type: `integer`):

How long the provider holds each request open waiting for the job to finish, up to its own ceiling of 30 seconds. Leaving this high is usually right: it turns a poll loop into one or two requests. The provider may still answer sooner and the run will poll behind it.

## `jobTimeoutMinutes` (type: `integer`):

Give up waiting after this long. The job keeps running at the provider and its results stay available there, so nothing is lost and a later run can collect them. Mailbox verification waits on other people servers, so a long list at a high quality level genuinely can take a while.

## `reportBalance` (type: `boolean`):

Log how many credits your provider account has left before the run starts. Informational only, and it needs its own permission on the credential, so a credential that verifies perfectly well may still not be allowed to read it. The run continues either way.

## `skipNotFound` (type: `boolean`):

Leave inputs that were refused before submission out of the dataset. Off by default, because knowing which of your lines were not usable addresses is usually the point. They are never charged for either way.

## `maxResults` (type: `integer`):

Stop after this many rows. This provider charges when a job is submitted rather than when it is read, so the address list is trimmed to this number before it is sent: the cap controls what you spend at the provider as well as what you spend here.

## `username` (type: `string`):

The user name of your own account with the email verification provider. Use a dedicated user rather than the account administrator, and grant it only the email verification permissions, so a leak costs you as little as possible.

## `password` (type: `string`):

The password for that user. Stored as a secret and never logged. Note that this provider can also refuse a perfectly correct credential when the account has a firewall rule that does not include the address the run comes from.

## `apiKey` (type: `string`):

A bearer token you have already obtained from the provider, used instead of the user name and password. Optional: most runs should leave this empty and fill in the two fields above. Stored as a secret.

## Actor input object example

```json
{
  "emails": [
    "user@example.com"
  ],
  "quality": "",
  "deduplication": "",
  "classifications": [],
  "deliverableOnly": false,
  "statuses": [],
  "excludeStatuses": [],
  "excludeDisposable": false,
  "excludeRoleAccounts": false,
  "excludeFreeEmail": false,
  "excludeCatchAll": false,
  "excludeHoneypots": false,
  "excludeDuplicates": false,
  "deleteJobWhenDone": false,
  "priority": 0,
  "providerWaitSeconds": 30,
  "jobTimeoutMinutes": 30,
  "reportBalance": false,
  "skipNotFound": false,
  "maxResults": 100
}
```

# Actor output Schema

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

One row per address, carrying the deliverable, risky, undeliverable or unknown verdict, the exact status code behind it, the disposable, role, free mail, catch-all, spam trap and duplicate flags, the parsed local and domain parts, and any suggested correction for a misspelt address.

# 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": [
        "user@example.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("nabeelbaghoor/bulk-email-verification-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": ["user@example.com"] }

# Run the Actor and wait for it to finish
run = client.actor("nabeelbaghoor/bulk-email-verification-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": [
    "user@example.com"
  ]
}' |
apify call nabeelbaghoor/bulk-email-verification-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nabeelbaghoor/bulk-email-verification-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/tesVEp8ICjM8CNqmg/builds/OD1XlRuclbRKRZPKZ/openapi.json
