# Bulk Email List Cleaner — MX, Disposable & Typo Checks (`saahir-job-data/bulk-email-list-cleaner`) Actor

Clean email lists before paid mailbox verification. Check syntax and DNS mail routes, flag disposable and role addresses, suggest domain typo fixes, skip duplicates, and export clear reasons. Paste emails, CSV, or an Apify dataset. No SMTP or email sends.

- **URL**: https://apify.com/saahir-job-data/bulk-email-list-cleaner.md
- **Developed by:** [Saahir Sultan](https://apify.com/saahir-job-data) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.30 / 1,000 completed email checks

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

### Clean email lists before paying for mailbox verification

Bulk Email List Cleaner finds address-format problems, duplicate entries, disposable domains, common domain typos and missing DNS mail routes. It is an inexpensive first pass for CRM imports, signup lists, newsletter hygiene and datasets produced by other Actors.

**This Actor does not verify that a mailbox exists.** It never connects to SMTP servers or sends an email. A domain with MX records can still reject a particular address. Catch-all status, spam traps, recipient activity and inbox placement are not checked. Addresses with a mail route receive `verify_mailbox`, not a promise of deliverability.

### Why use it?

- One DNS lookup can serve many addresses at the same domain, reducing repeated work.
- Duplicate rows are skipped by default and do not create paid results.
- Temporary DNS failures are saved separately and do not create paid results.
- Null MX, which explicitly indicates no mail service, is distinguished from ordinary MX records.
- If no MX exists, A and AAAA address records are checked for a possible implicit mail route.
- International domain names and supported international local parts are normalized; Gmail dots and plus tags are preserved.
- Every completed check includes its source row, recommendation and reasons.

### How to run

1. Paste addresses into **Email addresses**, or clear the examples and choose **CSV text** or **Source dataset**. Use exactly one input source.
2. Set your maximum completed checks and run spending limit.
3. Start the Actor. Open **Email checks** for results and **Completion and cost-control report** to confirm coverage.
4. Download **Spreadsheet-safe CSV** when opening results in Excel or another spreadsheet. Inspect excluded and review rows before removing anything from a customer list.

No external API key or proxy subscription is required. Runs can be called through the Apify API or connected to an Apify integration. For dataset input, select the dataset explicitly; the Actor requests read access only to that source.

### Input examples

```json
{
  "emails": ["sample@gmail.com", "sample@example.com", "sample@mailinator.com", "bad@@example.com", "sample@gmail.com"],
  "deduplicate": true,
  "maxResults": 10000
}
```

For CSV input:

```json
{"csvText":"name,email\nExample,sample@example.com\n", "emailField":"email"}
```

For a dataset, supply its actual ID as `sourceDatasetId`, clear `emails` and `csvText`, and select the exact top-level field using `emailField`. Nested fields are not supported. CSV supports headers, a UTF-8 BOM, quoted fields, and comma, semicolon or tab delimiters.

### Output

Each completed check is one dataset row. Example interpretation:

| Field | Meaning |
| --- | --- |
| `email` | Normalized address, or null when syntax is unsupported or invalid |
| `syntaxValid` | Supported address syntax passed validation |
| `domainStatus` | `mx_present`, `implicit_mx`, `null_mx`, `nxdomain`, `no_mail_route`, or `not_checked` |
| `recommendation` | `exclude`, `review`, or `verify_mailbox` |
| `reasons` | Explicit reasons for the recommendation |
| `disposable`, `roleBased`, `freeProvider` | List-based flags; none proves mailbox validity |
| `suggestedEmail` | A possible domain-typo correction, never automatically applied |
| `mailboxStatus` | Always `not_checked` |
| `catchAll` | Always null because catch-all detection is not performed |
| `sourceRow` | One-based input row, useful when joining results back to the source |

`REPORT` records delivered checks, skipped duplicates, unresolved rows, DNS requests, elapsed time and any stop reason. `UNRESOLVED` contains rows affected by DNS errors or operational limits. They are not labeled invalid. DNS is a point-in-time observation, and blocklists may be incomplete or outdated.

`CLEAN_CSV` contains completed checks with spreadsheet formula prefixes neutralized. Raw dataset exports preserve JSON values; use the supplied CSV for spreadsheet viewing.

### Pricing

The launch price is **$0.30 per 1,000 completed checks**, plus **$0.00005 per run start**. The live Pricing tab is authoritative. A completed syntax check that rejects a malformed address is still a result. Skipped duplicates and unresolved DNS failures do not incur a result fee. Disabling deduplication makes repeated completed rows billable.

For example, 10,000 delivered checks cost $3.00005 including one start at the launch rate. Configure your Apify run spending limit to cap charges. The SDK stops delivery when the remaining budget cannot pay for the next result. A partial run can contain useful results; always inspect `REPORT`.

### Limits and advanced behavior

Default limits are 10,000 results, 50,000 input rows, 5,000 distinct domains and 120 processing seconds. Maximum settings are 50,000 results, 100,000 input rows, 10,000 domains and 240 seconds. Text input is limited to 8 MB. Use smaller batches for diverse domains or large datasets. A batch of many addresses at one domain is much cheaper to resolve than a batch with thousands of different domains.

Deduplication applies only within one run. Domain case is normalized. Local-part case is preserved unless `caseInsensitiveDedupe` is enabled. Role-based addresses are flagged but not excluded by default. Syntax checking intentionally excludes some special-use and unusual address forms; it is not an exhaustive RFC acceptance test.

Domain queries go to Google Public DNS over HTTPS. Only domain names are sent to that resolver, not full addresses. Full inputs and outputs are stored in the run's Apify storage and follow your account's storage and sharing settings. Aggregate counts are logged; addresses are not written into application logs. Use lists you are permitted to process.

This is an on-demand batch Actor. Standby is unnecessary because there is no HTTP server. Automatic restart is disabled in the published configuration to avoid replaying paid output. On an abort, the dataset is authoritative; CSV and the report may be partial. To retry a partial run, remove already delivered rows and skipped duplicates from your input, and include unresolved rows that you wish to retry.

### FAQ and support

**Does `mx_present` mean the address works?** No. It means the domain publishes a mail route. Use a separate mailbox verifier or consent-based confirmation flow if recipient verification is required.

**Are typo suggestions guaranteed?** No. A similar domain may be intentional. Review suggestions before changing an address.

**Will this remove every disposable address?** No. Detection uses a bundled community list. New domains can appear between releases.

**How do I report a problem?** Open an issue on this Actor's Issues tab with the run ID and a redacted example. Do not post private contact lists or credentials.

Disposable-domain data is from [disposable-email-domains](https://github.com/disposable-email-domains/disposable-email-domains), distributed under CC0. See the bundled NOTICE for the exact source snapshot.

# Actor input Schema

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

Paste addresses, one per item. Clear these examples when using CSV or a dataset. Up to 100000 input rows; defaults stop at 10000 results. No email messages are sent.

## `csvText` (type: `string`):

Paste CSV with an email column, up to 8 MB. Clear the Email addresses field first. Comma, semicolon and tab delimiters supported.

## `sourceDatasetId` (type: `string`):

Choose an Apify dataset containing an email field. Read-only access to this selected dataset. Clear example addresses first.

## `emailField` (type: `string`):

Exact top-level CSV column or dataset field name; nested fields and dots are not supported.

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

Stops after this many delivered results; duplicates are skipped by default.

## `maxInputRows` (type: `integer`):

Includes duplicates and unresolved rows. Check REPORT for partial completion.

## `maxDomains` (type: `integer`):

Domains beyond this limit are unresolved and are not charged as results.

## `maxSeconds` (type: `integer`):

DNS work stops at this limit; export and platform operations add overhead.

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

Bounded concurrency. Repeated domains share DNS results within the run.

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

Within this run only. Duplicate rows are not billed when enabled.

## `caseInsensitiveDedupe` (type: `boolean`):

Optional: merge Alice and alice. Default preserves local-part case; domain case is always normalized. Gmail dots and plus tags remain distinct.

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

Uses the bundled public blocklist. Coverage is not exhaustive.

## `excludeRoleBased` (type: `boolean`):

Flags sales, info, support and similar mailboxes. A role address is not inherently invalid.

## Actor input object example

```json
{
  "emails": [
    "sample@gmail.com",
    "sample@example.com",
    "sample@mailinator.com",
    "bad@@example.com",
    "sample@gmail.com"
  ],
  "emailField": "email",
  "maxResults": 10000,
  "maxInputRows": 50000,
  "maxDomains": 5000,
  "maxSeconds": 120,
  "concurrency": 12,
  "deduplicate": true,
  "caseInsensitiveDedupe": false,
  "excludeDisposable": true,
  "excludeRoleBased": false
}
```

# Actor output Schema

## `checks` (type: `string`):

No description

## `report` (type: `string`):

No description

## `csv` (type: `string`):

No description

## `unresolved` (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": [
        "sample@gmail.com",
        "sample@example.com",
        "sample@mailinator.com",
        "bad@@example.com",
        "sample@gmail.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("saahir-job-data/bulk-email-list-cleaner").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": [
        "sample@gmail.com",
        "sample@example.com",
        "sample@mailinator.com",
        "bad@@example.com",
        "sample@gmail.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("saahir-job-data/bulk-email-list-cleaner").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": [
    "sample@gmail.com",
    "sample@example.com",
    "sample@mailinator.com",
    "bad@@example.com",
    "sample@gmail.com"
  ]
}' |
apify call saahir-job-data/bulk-email-list-cleaner --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,saahir-job-data/bulk-email-list-cleaner"
        }
    }
}
```

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/ARKKxzsMXl6AduCoD/builds/cqNsGWeG3gOuQL9BI/openapi.json
