# Bulk Email Verifier & Validator: Syntax, MX, Role (`plainkitbin/email-syntax-mx-validator`) Actor

Bulk email verifier and validator at $0.29 per 1,000: RFC syntax, DNS MX and null MX, role accounts, domain typo hints, and an exact match on a built-in list of 10,141 disposable domains (not exhaustive). No mailbox (SMTP) check. Works with n8n, Make, Zapier and AI agents via MCP.

- **URL**: https://apify.com/plainkitbin/email-syntax-mx-validator.md
- **Developed by:** [PlainKit bin](https://apify.com/plainkitbin) (community)
- **Categories:** Lead generation, Developer tools, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.19 / 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.
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

## Bulk Email Verifier & Validator: Syntax, MX, Role

### Introduction

Clean an email list in bulk with this verifier and validator before you send to it. For each address this Actor checks syntax against a strict ASCII subset of RFC 5321 and RFC 5322, queries DNS MX records with fallback to address records, flags role mailboxes such as info or support, suggests a fix for common domain typos, and flags an exact match on a built-in list of 10,141 disposable domains that is not exhaustive. You get one row per address with a simple `status`: `mx_ok`, `risky` or `invalid`.

**What it does not do:** it never connects to mail servers, so it makes no mailbox (SMTP) check. `mx_ok` means the domain has mail routing, not that the mailbox exists. Use it as a low-cost first pass, at $0.29 per 1,000 addresses on the FREE tier, to remove addresses that cannot work. It also runs from n8n, Make and Zapier, and from AI agents through the Apify MCP server.

#### Use cases

- **Marketing list cleaning**: Filter out syntax errors, defunct domains, and null MX domains before launching cold email or newsletter campaigns.
- **Form submission hygiene**: Validate bulk contact submissions and catch domain typos such as typos in popular providers.
- **Spam and abuse reduction**: Flag exact matches on a built-in list of 10,141 disposable domains that is not exhaustive and generic role mailboxes (e.g., admin, info, support) to protect lead scoring and onboarding pipelines.

### Sample output

Each address gives one row like this illustrative one:

```json
{
  "checked_utc": "2026-11-04T12:00:00Z",
  "did_you_mean": "",
  "domain": "example.com",
  "email": "user@example.com",
  "has_mx": true,
  "is_disposable": false,
  "is_role_account": false,
  "mx_hosts": [
    "mail.example.com"
  ],
  "normalized_email": "user@example.com",
  "reason": "syntax_and_mx_ok",
  "status": "mx_ok",
  "syntax_valid": true
}
```

### Tutorial

#### Step-by-step guide

1. Prepare a JSON input object containing the `emails` array with up to 100,000 email addresses.
2. Run the Actor on the Apify platform or trigger it via the Apify API.
3. Once completed, view or download the checked dataset in JSON, CSV, or Excel format.
4. Filter rows by `status` (`mx_ok`, `risky`, `invalid`) to segment your contacts.

### Pricing

The Actor is priced on a Pay-Per-Event model under the `email-checked` event. Under the FREE tier, the cost is $0.00029 per checked address, with tiered discounts available for higher subscription tiers.

#### Charge contract

Each row is pushed first and charged right after a successful push; a refused charge stops work and that row stays delivered and uncharged; a reached limit stops work after that row was charged; failed items are neither pushed nor charged.

- **Duplicates and blank lines**: Blank lines are ignored and not charged. Duplicate addresses within the input list are deduplicated before any charge; deduplication keeps the local part case-sensitive and lowercases the domain. Duplicate addresses are discarded and are not charged.
- **Failed items**: If a DNS timeout or nameserver failure occurs during lookup, the address is considered a failed item; failed items produce no row, so compare input and output to find them, and failed items are not charged. A failed lookup applies to every address of that domain in that run.
- **Invalid input**: A run with invalid input ends as failed before any address is processed; invalid input is not charged.
- **Restarted runs**: Rows already in the dataset of a restarted run are skipped and not charged again; if the dataset of a restarted run cannot be read, the run fails with DATASET_READ_FAILED before any new row or charge.
- **Largest bill**: For the maximum input of 100,000 addresses, the bill is $29.00 at the FREE price ($0.00029 per address) and $19.30 at the GOLD price ($0.000193 per address). Buyers can configure a maximum cost per run setting in the Apify Console.
- **Unprocessed items and runtime budget**: The actor timeout is `RUN_TIMEOUT_SECONDS = 3600` seconds. When remaining execution time drops below the safety margin (`DEADLINE_MARGIN_SECONDS = 15` seconds, derived from `MAX_DNS_QUERIES_PER_DOMAIN * DNS_LIFETIME_SECONDS`), the run stops gracefully with stop code `RUN_TIME_BUDGET_REACHED`. Unprocessed addresses are neither pushed nor charged.
- **Large lists**: A run stops when its 3,600 second time budget is nearly used. How many addresses fit depends on DNS speed and on platform calls for each row, and it has not been measured for large lists. Each address needs one DNS lookup unless its domain was already looked up in the run, then one push and one charge, in sequence. Addresses not reached produce no row and no charge, and the status message gives the count. To be safe, split a very large list into several runs.
- **Syntax edge cases**: Internationalised addresses (non-ASCII characters or IDN) and quoted local parts return invalid_syntax and are charged, as syntax validation requires standard ASCII dot-atom local parts.

### Input and output examples

#### Input example

```json
{
  "emails": [
    "user@example.com",
    "support@example.com",
    "invalid-user@example.org"
  ]
}
```

The prefilled sample uses example.com, which publishes a null MX, so a first run returns `invalid` with reason `null_mx`. Put your own list in its place.

#### Output example

Illustrative row, not a live result. Real domains return their own records, and example.com itself publishes a null MX.

```json
{
  "checked_utc": "2026-11-04T12:00:00Z",
  "did_you_mean": "",
  "domain": "example.com",
  "email": "user@example.com",
  "has_mx": true,
  "is_disposable": false,
  "is_role_account": false,
  "mx_hosts": [
    "mail.example.com"
  ],
  "normalized_email": "user@example.com",
  "reason": "syntax_and_mx_ok",
  "status": "mx_ok",
  "syntax_valid": true
}
```

#### Output fields

| Field | Type | Description |
| --- | --- | --- |
| `checked_utc` | String | ISO 8601 UTC timestamp of when the address was checked |
| `did_you_mean` | String | Suggested address correction for common domain typos |
| `domain` | String | Extracted lowercased domain part (empty for invalid syntax) |
| `email` | String | Email holds the trimmed input string (truncated to 254 chars on invalid rows) |
| `has_mx` | Boolean | True if the domain has valid DNS MX records |
| `is_disposable` | Boolean | True if domain is an exact match on a built-in list of 10,141 domains that is not exhaustive |
| `is_role_account` | Boolean | True if local part is a generic role account (admin, support, etc.) |
| `mx_hosts` | Array | Up to 5 MX hostnames ordered by preference, and a hostname with a long digit run may be masked by the output safety filter |
| `normalized_email` | String | Normalized email representation (truncated to 254 chars on invalid rows) |
| `reason` | String | Machine-readable diagnosis reason code |
| `status` | String | High-level validation status: `mx_ok`, `risky`, or `invalid` |
| `syntax_valid` | Boolean | True if email adheres to RFC 5321 / RFC 5322 syntax specifications |

#### Status and reason reference

| Status | Reason | Description |
| --- | --- | --- |
| `mx_ok` | `syntax_and_mx_ok` | Syntax is valid and domain has valid DNS MX records |
| `risky` | `a_record_fallback` | Domain lacks MX records but has valid A or AAAA host records |
| `risky` | `disposable_domain` | Domain is an exact match on a built-in list of 10,141 domains that is not exhaustive |
| `risky` | `role_account` | Local part matches a shared departmental role mailbox |
| `invalid` | `invalid_syntax` | Email fails RFC 5321 length limits or character syntax rules |
| `invalid` | `nxdomain` | Domain does not exist according to the DNS resolver |
| `invalid` | `null_mx` | Domain explicitly announces it does not accept mail (RFC 7505) |
| `invalid` | `no_mx_or_a_record` | Domain exists but has no MX, A, or AAAA DNS routing records |

### Actor recommendations

For full lead pipeline management, pair this Actor with:

- **Web Scraper & Crawler Actors**: Extract business contact pages and domain references.
- **CRM Integrator Actors**: Ingest checked clean contacts directly into your CRM or email delivery platform.

### FAQ and support

#### Does this check confirm that a specific mailbox actually exists?

No. It checks syntax, DNS MX records, an exact match on a built-in list of 10,141 domains that is not exhaustive, and role patterns. It does not connect to mail servers and does not prove the mailbox exists.

The disposable list is the public disposable-email-domains project (CC0 1.0), commit a72c0cec of 2026-11-04, minus 2 entries removed for our release checks.

#### Are my email lists logged or stored?

Addresses are never written to run logs, which hold aggregate counts and status codes only. The input list and the output rows stay in the buyer's own Apify run storage under the buyer's retention settings, because the output rows are the delivered result. The buyer must have a lawful basis to process the list under applicable data protection laws (such as GDPR).

#### Support and inquiries

For issues, bug reports, or feature requests, submit a ticket through the Apify Console Actor issue tracker.

# Actor input Schema

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

List of email addresses to validate; at most 100,000 addresses per run. The prefilled sample uses example.com, which publishes a null MX, so it returns status invalid with reason null_mx: put your own list in its place.

## Actor input object example

```json
{
  "emails": [
    "info@example.com"
  ]
}
```

# Actor output Schema

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

Dataset items produced by this run.

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

// Run the Actor and wait for it to finish
const run = await client.actor("plainkitbin/email-syntax-mx-validator").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": ["info@example.com"] }

# Run the Actor and wait for it to finish
run = client.actor("plainkitbin/email-syntax-mx-validator").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": [
    "info@example.com"
  ]
}' |
apify call plainkitbin/email-syntax-mx-validator --silent --output-dataset

```

## MCP server setup

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

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/KcwIY51yQvZjDyHdC/builds/dSavdNdsKKtFlgLan/openapi.json
