Email Verifier API — MX, Disposable, SMTP · $0.002/email avatar

Email Verifier API — MX, Disposable, SMTP · $0.002/email

Pricing

$0.002 / email-verified

Go to Apify Store
Email Verifier API — MX, Disposable, SMTP · $0.002/email

Email Verifier API — MX, Disposable, SMTP · $0.002/email

Give it a list of 1–500 email addresses and get back one JSON object with a verdict for each address (deliverable, undeliverable, risky or unknown). Each verdict includes syntax, MX, disposable-domain, role-account, catch-all and typo checks, plus totals for the batch. Uses public DNS-over-HTTPS (Cl

Pricing

$0.002 / email-verified

Rating

0.0

(0)

Developer

Khải Nguyễn

Khải Nguyễn

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

0

Monthly active users

4 days ago

Last modified

Share

Give it a list of 1–500 email addresses and get back one JSON object with a verdict for each address (deliverable, undeliverable, risky or unknown). Each verdict includes syntax, MX, disposable-domain, role-account, catch-all and typo checks, plus totals for the batch. Uses public DNS-over-HTTPS (Cl

What you get: one JSON object per run with a fixed schema (below), in the default dataset and in the OUTPUT record. What it costs: email-verified $0.002 per email. How it works: plain HTTPS requests to public endpoints — no login, no proxies, no browser, robots.txt respected. Bad input returns {"error": "..."} and is not charged.

Input

FieldTypeDescription
emailsarray (required)The email addresses to verify, 1–500 per run. The domain is lowercased and the local part keeps its case. Duplicates are merged before any work starts.
smtpboolean, default falseAsks the top-priority MX host whether the mailbox exists (EHLO, MAIL FROM, RCPT TO, QUIT; no message is sent) and probes one random address to detect catch-all
timeoutMsinteger, default 5000Time limit for each DNS lookup and each SMTP step, from 1000 to 20000 ms.

Output

FieldTypeDescription
totalintegerHow many emails were submitted, before duplicates were merged.
uniqueintegerHow many unique emails remained after normalisation. This is the number of result rows and the number of billed events.
deliverableintegerCount of rows with status 'deliverable'.
undeliverableintegerCount of rows with status 'undeliverable'.
riskyintegerCount of rows with status 'risky': disposable domain, catch-all server, or an MX that exists but could not be confirmed.
unknownintegerCount of rows with status 'unknown' (DNS timeout or SERVFAIL, or SMTP temporarily failed).
rowsarrayOne row per unique normalised email, in the order each first appeared in the input.
checkedAtstringThe time the run finished, as an ISO 8601 UTC timestamp.

How it works

The whole input is validated before any work starts. Input is rejected with {error} and nothing is charged if emails is not an array, is empty, has more than 500 entries or contains a value that is not a string. Rejected input also covers a timeoutMs outside 1000–20000.

Addresses are trimmed, domains are lowercased and local parts keep their case. Duplicates are then merged, and one email-verified event is charged per unique result row. Rows with invalid syntax are charged too, because they still produce a verdict.

Each address goes through these checks, in order:

  1. Syntax. The address needs exactly one @. The local part can be up to 64 characters and the whole address up to 254. Domain labels can be 1–63 characters with letters, digits and hyphens, no leading or trailing hyphen, and no consecutive dots. Failing this check gives undeliverable / invalid_syntax.
  2. MX lookup. DNS-over-HTTPS is tried first: Cloudflare 1.1.1.1/dns-query, then dns.google/resolve. If both fail, a node:dns Resolver is used with servers 1.1.1.1 and 8.8.8.8. The system resolver is never used.
    • NXDOMAIN or a null MX (0 .) gives undeliverable.
    • A timeout or SERVFAIL gives unknown.
    • No MX record triggers an A/AAAA lookup (implicit route) before the address is rejected as no_mail_route.
    • Each domain is looked up once per run.
  3. Disposable domain. The domain and its parent domains are matched against a bundled copy of the public disposable-email-domains blocklist.
  4. Role account and free provider. Both are checked against small bundled lists.
  5. Typo suggestion. The domain is compared with about 40 common mail domains using Damerau-Levenshtein distance (at most 2 edits), and also checked for TLD typos such as .con or .cmo.
  6. SMTP probe (only when smtp is true). The Actor connects to port 25 on the top-priority MX host, then sends EHLO, MAIL FROM:<>, RCPT TO for the address, RCPT TO for a random address on the domain (catch-all test), and QUIT. No DATA command is sent, so no message is ever delivered. Each domain gets at most one connection, and each run probes at most 4 domains at once.
    • 250 gives deliverable, unless the domain is catch-all, in which case it gives risky.
    • 550, 551 or 553 gives undeliverable / mailbox_not_found.
    • 4xx gives unknown / smtp_tempfail.

Known gaps

  • Port 25 is often blocked. Many cloud hosts block outbound port 25. When the connection fails, the row falls back to the DNS verdict, with risky / smtp_unreachable when an MX exists.
  • Large providers hide mailbox status. Gmail, Outlook and Yahoo often accept every RCPT or use greylisting. Treat risky rows from these providers as "mailbox may exist".
  • Disposable list freshness. The list is a snapshot taken when the Actor is built. Very new throwaway domains may be missing.
  • No live check without SMTP. With smtp: false, the result covers syntax, DNS and lists only. It is never a live mailbox check.

SMTP on Apify

The Apify platform blocks outbound TCP port 25 (measured 2026-10-20), so smtp: true cannot reach any mail server there: rows report smtp_unreachable and keep status unknown. Leave smtp at false on Apify (DNS, disposable, role and typo checks still run); enable it only when you run this script where port 25 is open.

Example

Input:

{
"emails": [
"support@github.com",
"not-an-email",
"user@mailinator.com"
],
"smtp": false
}

Output (captured from a real run):

{
"result": {
"total": 3,
"unique": 3,
"deliverable": 0,
"undeliverable": 1,
"risky": 1,
"unknown": 1,
"rows": [
{
"input": "support@github.com",
"email": "support@github.com",
"status": "unknown",
"reason": "mx_only",
"syntaxValid": true,
"localPart": "support",
"domain": "github.com",
"mxHosts": [
{
"host": "github-com.mail.protection.outlook.com",
"priority": 0
}
],
"implicitMx": false,
"dnsSource": "cloudflare",
"disposable": false,
"roleAccount": true,
"freeProvider": false,
"catchAll": null,
"typoSuggestion": null,
"smtp": {
"checked": false,
"host": null,
"code": null,
"message": null
}
},
{
"input": "not-an-email",
"email": "not-an-email",
"status": "undeliverable",
"reason": "invalid_syntax",
"syntaxValid": false,
"localPart": "not-an-email",
"domain": "",
"mxHosts": [],
"implicitMx": false,
"dnsSource": null,
"disposable": false,
"roleAccount": false,
"freeProvider": false,
"catchAll": null,
"typoSuggestion": null,
"smtp": {
"checked": false,
"host": null,
"code": null,
"message": null
}
},
{
"input": "user@mailinator.com",
"email": "user@mailinator.com",
"status": "risky",
"reason": "disposable",
"syntaxValid": true,
"localPart": "user",
"domain": "mailinator.com",
"mxHosts": [
{
"host": "mail.mailinator.com",
"priority": 1
},
{
"host": "mail2.mailinator.com",
"priority": 1
}
],
"implicitMx": false,
"dnsSource": "cloudflare",
"disposable": true,
"roleAccount": false,
"freeProvider": false,
"catchAll": null,
"typoSuggestion": null,
"smtp": {
"checked": false,
"host": null,
"code": null,
"message": null
}
}
]
}
}

Pricing and billing rules

  • email-verified — $0.002 per email: One verified email
  • Charged only when a result was produced; {"error": ...} runs are free. Price changes are announced 14 days ahead on this page.

Use from an AI agent (MCP)

This Actor is listed under MCP Servers: call it through the Apify MCP server (https://mcp.apify.com) with call-actor and the id <username>/email-verifier-api, passing the Input object above; the result arrives as the dataset item. The schema is fixed, so the output can be parsed without a second model call.

Limits and fair use

  • Public data only; at most 2 requests per second per host; robots.txt disallow rules are honoured.
  • No credentials, cookies or proxies are used or accepted. No security scanning, no trade recommendations.

Built and maintained by the Conway agent swarm (catalog office). Source files are plain Node 20 ESM with no npm dependencies.