# Email Syntax, MX & Disposable Check (`grit-77/email-validator`) Actor

Email validator for addresses you supply. Export syntax, DNS mail-routing, disposable-domain and role-account checks as JSON with errors. Does not verify individual mailbox existence.

- **URL**: https://apify.com/grit-77/email-validator.md
- **Developed by:** [Grit](https://apify.com/grit-77) (community)
- **Categories:** Lead generation, Marketing
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.50 / 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

## Email Syntax, MX & Disposable Check

Email validator API returns syntax, DNS mail-routing, disposable-domain, and role-account checks for supplied addresses. Email validation scraper rows include evidence and errors without claiming that an individual mailbox exists.

Supply addresses you already have permission to process. This actor checks your input; it does not discover or harvest addresses. It uses the local syntax parser, a bundled open list and public DNS. No SMTP session, verification email, login, third-party verification API or API key is used for the checks.

### What it returns

| Field | Type | Example value |
|---|---|---|
| `inputIndex` | integer | `0` |
| `input` | string | `"person@gmail.com"` |
| `normalizedEmail` | string | `"person@gmail.com"` |
| `domain` | string | `"gmail.com"` |
| `syntaxValid` | boolean | `true` |
| `domainLiteral` | boolean | `false` |
| `status` | string | `"domain_routable"` |
| `isDisposable` | boolean | `false` |
| `disposableDomain` | string | `"mailinator.com"` |
| `isRoleAccount` | boolean | `false` |
| `mailboxExists` | null | `null` |
| `smtpProbed` | boolean | `false` |
| `billable` | boolean | `true` |
| `checkedAt` | string | `"2026-10-05T13:02:32.704643+00:00"` |
| `mxRecords` | array | `[{"priority": 1, "exchange": "mail.mailinator.com"}, {"priority": 1, "exchange": "mail2.mailinator.com"}]` |
| `aRecords` | array | `["142.251.27.17", "142.251.27.18", "142.251.27.19", "142.251.27.83"]` |
| `aaaaRecords` | array | `["2a00:1450:4025:1807::11", "2a00:1450:4025:1807::12", "2a00:1450:4025:1807::13", "2a00:1450:4025:1807::53"]` |
| `hasMx` | boolean | `true` |
| `nullMx` | boolean | `false` |
| `mailDomainRoutable` | boolean | `true` |
| `dnsStatus` | string | `"mx"` |
| `errors` | array | `[]` |

### Input example

```json
{
  "emails": [
    "person@gmail.com"
  ],
  "maxItems": 10
}
```

Use the input schema for the remaining filters and defaults.

### Output example

This is a selected-field excerpt of the first object in [sample_output.json](sample_output.json).

```json
{
  "inputIndex": 0,
  "input": "person@gmail.com",
  "normalizedEmail": "person@gmail.com",
  "domain": "gmail.com",
  "syntaxValid": true,
  "domainLiteral": false,
  "status": "domain_routable",
  "isDisposable": false,
  "disposableDomain": null,
  "isRoleAccount": false,
  "mailboxExists": null,
  "smtpProbed": false,
  "billable": true,
  "checkedAt": "2026-10-05T13:02:32.704643+00:00"
}
```

### Pricing

Proposed event: **`email` at $0.0005**, or **$0.50 per 1,000 billable rows**. A completed invalid-syntax string is a billable verdict; non-string inputs, item failures and indeterminate DNS/literal rows are free. Duplicates can be billable twice. No result beyond `maxItems` is emitted or charged. Event charges stop at the Apify event ceiling.

The observed Store leader's lowest event tier was $0.00065. See [PRICING.md](PRICING.md) for the dated API response, tier prices, comparison scope and publisher configuration. These are proposed event fees; publication and platform-usage settings remain the owner's work.

### Syntax and routing limits

The syntax check accepts **bare RFC 5321 mailboxes**, using RFC 5322 ASCII dot-atom or quoted-string local parts (including escaped quotes and an @ inside quotes), DNS hostnames/IDNA domains and IPv4/IPv6 literals. Local parts are limited to 64 ASCII octets; normalized addresses to 254 octets. Domain labels follow hostname rules.

Display names such as `Name <address@example.com>`, comments, obsolete header forms, folded/control characters and international Unicode local parts (SMTPUTF8/EAI) are outside this supported mailbox syntax. A single-label hostname can be syntactically accepted but usually lacks public routing. Valid address literals retain syntax validity and return a free, unknown domain-routing verdict; no host connection is made.

MX evidence only indicates domain routing. A/AAAA fallback applies when MX has no answer, including IPv6-only domains. A valid null MX overrides A/AAAA; malformed/mixed null MX stays unknown. MX query failures do not trigger an optimistic A fallback. MX targets are not contacted and their mailbox policy is not tested.

The list is a **9205-domain snapshot** from [disposable-email-domains](https://github.com/disposable-email-domains/disposable-email-domains), commit `1aac72a07cd89792016dd5d2056bc418d85b8f14`, bundled under **CC0-1.0** with its [licence](data/LICENSE.disposable-email-domains.txt) and [provenance/hash](data/SOURCE.json). Parent matches respect dot boundaries. Lists can have stale entries, omissions and false positives; role flags are heuristics.

### FAQ

**What limits the output?** The maxItems input caps output rows; its schema maximum is 100,000. Source availability and errors may yield fewer rows.

**Does it need a proxy, login, or API key?** The actor checks supplied addresses using local syntax rules and DNS. It does not log in, use a proxy input, or probe SMTP mailboxes.

**How is it priced?** PRICING.md proposes $0.0005 per delivered row with a completed syntax or determinate routing verdict. Unknown and item-error rows do not trigger that event.

**What happens on rate limits?** DNS requests use bounded retries for temporary failures. DNS failures can leave routing status unknown.

**How fresh is the data?** DNS evidence is checked during the run and may be cached within it; checkedAt is the row time. Routing can change later.

**Does domain_routable mean a mailbox exists?** No. It describes mail-routing DNS evidence, not mailbox existence or delivery.

### Use with AI agents / MCP

An AI agent can call a published actor through the Apify MCP server or Apify API, pass the JSON input, then read the run’s default dataset. Check the actor’s published identifier and permissions in your Apify account.

After the owner publishes this actor, add its published actor identifier to your Apify MCP server or invoke it from your existing Apify client. Pass the input object above, then read the default dataset using the actor output's `dataset` link. MCP means the interface through which an agent invokes tools; here its tool input is the email array and its result is the dataset. Do not use `domain_routable` as a mailbox-existence answer. A deployed MCP integration has not been tested here.

### Local development

From this actor directory, use the existing project interpreter. Put the input JSON in `storage/key_value_stores/default/INPUT.json` and run in PowerShell:

```powershell
$env:APIFY_LOCAL_STORAGE_DIR = "$PWD/storage"
C:/GritWork/earn/.venv/Scripts/python -m src
C:/GritWork/earn/.venv/Scripts/python -m pytest tests -q -p no:cacheprovider
C:/GritWork/earn/.venv/Scripts/python -m pytest tests -m live -q -p no:cacheprovider
apify validate-schema
```

The saved output is in `storage/datasets/default/`; run summary is the default key-value-store record `SUMMARY`. Local charging does not activate the proposed Store price. Test dependencies already exist in the specified environment; no package installation into it is required. Container runtime dependencies are in [requirements.txt](requirements.txt). The Dockerfile uses `apify/actor-python:3.12`; Docker/cloud execution remains unverified.

# Actor input Schema

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

Bare email addresses to check, one row per supplied item; duplicates are preserved. Surrounding spaces are trimmed. No mailbox connection or email is sent.

## `maxItems` (type: `integer`):

Cap on all result rows, including invalid inputs. Entries beyond this cap are neither checked nor charged.

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

Maximum addresses checked in parallel. Each unique domain uses up to three concurrent DNS queries. Repeated domains share cached results within a run.

## `dnsTimeoutSecs` (type: `integer`):

Each MX/A/AAAA query has this deadline per attempt. Temporary failures get at most three attempts with 0.25s and 0.5s backoff.

## Actor input object example

```json
{
  "emails": [
    "person@gmail.com",
    "support@mailinator.com",
    "user@example.com",
    "bad-address"
  ],
  "maxItems": 1000,
  "concurrency": 10,
  "dnsTimeoutSecs": 3
}
```

# Actor output Schema

## `dataset` (type: `string`):

One row per admitted email with check results and any per-item errors.

# 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": [
        "person@gmail.com",
        "support@mailinator.com",
        "user@example.com",
        "bad-address"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("grit-77/email-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": [
        "person@gmail.com",
        "support@mailinator.com",
        "user@example.com",
        "bad-address",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("grit-77/email-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": [
    "person@gmail.com",
    "support@mailinator.com",
    "user@example.com",
    "bad-address"
  ]
}' |
apify call grit-77/email-validator --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,grit-77/email-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/9zcNHIUCtfZbimOo0/builds/PLSlAnkw3kWKQ5YRC/openapi.json
