# Email Verifier & Validator — Bulk MX, SPF, DMARC Check $0.50/1k (`scrapersdelight/email-verifier-scraper`) Actor

Email verifier and validator for bulk email list cleaning. Returns syntax, MX, null-MX, disposable, role, free-provider plus the full SPF, DKIM, DMARC, MTA-STS, mail-provider, ASN and domain-age record — 80+ fields per address for $0.50/1,000. Honest: no SMTP mailbox probe.

- **URL**: https://apify.com/scrapersdelight/email-verifier-scraper.md
- **Developed by:** [Scrapers Delight](https://apify.com/scrapersdelight) (community)
- **Categories:** Lead generation, Business, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.50 / 1,000 per email verifieds

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

## 📧 Email Verifier & Validator — bulk list cleaning, 83 fields per address

Paste a list of email addresses (or point this at another actor's dataset) and get back **one deep record per address**: syntax, mail routing, who runs the mailserver, the complete SPF / DKIM / DMARC / MTA-STS authentication record, the ASN that owns the mail IP, how old the domain is, and a clear verdict — for **$0.50 per 1,000 addresses**.

**And one thing this actor says out loud that the rest of the category does not:** it does **not** open an SMTP connection and it does **not** claim to know whether a mailbox exists. It cannot. Nothing on this platform can. See [Honest limits](#-honest-limits) — that section is the reason to trust the other 80 fields.

***

### ✅ What does the Email Verifier & Validator do?

It answers everything about an address that can be answered **without lying**:

- ✉️ **Is the address well-formed?** Full RFC 5322 addr-spec check — dot-atom and quoted local parts, IDN domains, length limits, IP-address literals — with a machine-readable `syntax_error_code` (`missing_at`, `consecutive_dots`, `invalid_tld`, `whitespace`, …) instead of a bare `false`.
- 📮 **Can the domain receive mail at all?** MX records with priorities and TTL, **RFC 7505 null-MX** detection (`0 .` = "this domain accepts no mail, ever"), and **RFC 5321 implicit MX** fallback (no MX but an A record still routes mail — most verifiers call this undeliverable and are wrong).
- 🏢 **Who runs the mailbox?** Google Workspace, Microsoft 365, Proofpoint, Mimecast, Barracuda, Cloudflare Email Security, Zoho, Fastmail, Proton, Yandex, IONOS, Cisco IronPort, Symantec, self-hosted — fingerprinted from the MX host, with a confidence level.
- 🌐 **Which network?** MX IP → ASN, ASN org name and network CIDR (Team Cymru over DNS).
- 🔐 **The whole authentication record.** SPF (raw record, `-all`/`~all`/`?all`/`+all` policy, includes, and the RFC 7208 10-lookup count), DMARC (`p=`, `sp=`, `pct=`, `rua`, alignment), a **24-selector DKIM sweep** with ESP identification, BIMI, MTA-STS (the policy is actually **fetched**, not just detected), and TLS-RPT.
- 📅 **How old is the domain?** RDAP registration date, age in days, expiry, days to expiry, registrar, EPP status codes and registrar lock. A domain registered 6 days ago is a very different lead from one registered in 1995.
- 🗑️ **Disposable?** Checked against a **8,714-domain** blocklist, refreshed at run start and vendored in the image as a fallback.
- 👔 **Role account?** `info@` / `sales@` / `support@` / `billing@` / `hr@` / `noreply@` / `abuse@` / `postmaster@`, with the role *type*, and hyphen/dot variants (`customer-service@`) handled.
- 🆓 **Free mailbox provider?** 4,466-domain list, with a friendly provider name.
- 🔤 **Typo?** `gmial.com` → `gmail.com`, `gmail.con` → `gmail.com`, `hotmial.com` → `hotmail.com`, using Damerau-Levenshtein (a transposition is *one* typo, not two) with false-positive gates.
- 🧬 **Duplicate?** De-duplicated on the tag-stripped address, and on the dot-stripped form for Gmail — so `j.a.n.e.doe+news@gmail.com`, `janedoe@googlemail.com` and `JANEDOE@GMAIL.COM` are recognised as one person.
- 👤 **Gravatar.** The only per-address existence evidence available without SMTP. A hit is real evidence of a human; a miss proves nothing and is never scored against the address.
- 🎯 **A verdict.** `status` + a 0–100 `score` + machine-readable `reason_codes[]` and `risk_flags[]`, so you can build your own thresholds instead of trusting ours.

***

### 📊 Measured, from a real run

Everything below is from actual runs on the Apify platform on **2026-09-03**, build `0.1.4`. No estimates.

| Run | Input | Result |
|---|---|---|
| Field coverage | 61 addresses (mixed: B2B, consumer, disposable, typos, malformed) | **SUCCEEDED, 61 rows, 6.3 s** · 83 fields · charged `email-verified: 61` |
| Throughput | **600 addresses over 200 distinct domains** | **SUCCEEDED, 600 rows, 26.0 s** · 0.0072 compute units at 1 GB |
| Health test | prefill-only input | **PASS, 2.0 s, 5 items** |
| Badge preflight | prefill-only ×3, `{}` ×2, timeout 300 s | **5/5 SUCCEEDED**, slowest **3.4 s**, empty input bills **$0.0025** |

**Throughput: 600 addresses / 200 distinct domains in 26.0 seconds** — about **23 addresses per second**, or **~43 seconds per 1,000 addresses** at that domain density. Compute measured at **0.0072 CU for 600 addresses**, i.e. roughly **$0.005 of compute per 1,000 verified** against $0.40 of net revenue.

#### Per-field fill, measured over the 600-row run

200 real company and consumer domains. **50 of 83 fields populate on 100 % of rows; 65 of 83 on ≥ 90 %.**

| Field group | Fill |
|---|---|
| Identity + syntax (`email`, `email_normalized`, `local_part`, `domain`, `domain_unicode`, `tld`, `input_index`, `is_valid_syntax`, …) | **100 %** |
| Routing booleans (`has_mx`, `is_null_mx`, `mx_fallback_a`, `has_a`, `has_aaaa`, `mx_count`, `dns_rcode`, `dnssec_ad`) | **100 %** |
| `mx_records`, `mx_ttl` | 99 % (the 1 % are domains with no MX at all) |
| `ns_records`, `dns_provider`, `a_records` | **100 %** |
| `mail_provider`, `mail_provider_confidence`, `mx_ip`, `mx_asn`, `mx_asn_org`, `mx_network_cidr` | **100 %** |
| `spf_record` / `spf_lookup_count` | 99 % · `spf_policy` 96 % · `spf_includes` 92 % |
| `dmarc_record` / `dmarc_policy` / `dmarc_pct` | 98.5 % · `dmarc_subdomain_policy` 20 % (only when the domain sets `sp=`) |
| `dkim_selectors_found` 89 % · `dkim_esp_guess` 87.5 % | |
| `mta_sts_mode` 100 % · `mta_sts_mx` / `mta_sts_max_age` 8 % (only domains that publish a policy) | |
| `domain_created_at` / `domain_age_days` / `domain_status` / `domain_locked` | **91 %** · `domain_registrar` 89.5 % · `domain_locked` true on 42 of the 44 domains that returned an EPP status |
| `gravatar_found` / `gravatar_hash` | **100 %** (`gravatar_url` 2 % — that is the actual hit rate) |
| `status`, `score`, `reason_codes`, `risk_flags`, `smtp_checked`, `mailbox_existence`, `checked_at` | **100 %** |

**Where the 9 % of missing domain ages come from:** the IANA RDAP bootstrap maps **1,200 TLDs**, and several big ones have **no public RDAP server at all** — `.de`, `.ru`, `.io`, `.jp`, `.ch`, `.nl`, `.so`, `.me`, `.edu` among them. Those rows carry `rdap_error: "tld_not_in_bootstrap"` so you know *why* the age is null rather than guessing.

**Mail-provider naming:** a named provider on **519 of 600 rows**. The rest resolve to `Self-hosted` or `Other (cisco.com)`, `Other (db.com)`, `Other (boeing.net)` — genuinely self-run enterprise mail. The actor labels those honestly instead of inventing a vendor.

***

### 🚀 Quick start

Three ways to get addresses in. They merge, in this order.

```jsonc
// 1. a list
{ "emails": ["support@stripe.com", "jane.doe@gmail.com", "info@mailinator.com"] }
```

```jsonc
// 2. a paste — CSV, TSV, one-per-line, or "Name <a@b.com>"
{ "emailsText": "Jane Doe,jane@acme.com,Head of Ops\n\"Smith, Bob\" <bob@example.co.uk>\na@b.com, c@d.com" }
```

```jsonc
// 3. chain it onto a lead scraper's output
{ "inputDatasetId": "aBcD1234efGh5678", "emailField": "email" }
```

Send nothing at all and the actor runs a 5-address demo rather than failing — so an empty `{}` never breaks a scheduled task.

***

### 📥 Input

| Field | Type | Default | What it does |
|---|---|---|---|
| `emails` | array | — | Addresses to verify, one per row. A row containing a comma list is split. |
| `emailsText` | string | `""` | Paste raw text; every address in it is extracted in order. CSV columns and `Name <addr>` are handled. |
| `inputDatasetId` | string | `""` | Read addresses from a previous actor run's dataset. |
| `emailField` | string | `"email"` | Which field holds the address. Dotted paths (`contact.email`) work; array values are expanded. |
| `deepChecks` | boolean | `true` | DKIM selector sweep + RDAP domain age + fetched MTA-STS policy + MX network owner. All **per-domain** and cached, so on a real list they cost almost nothing. |
| `checkGravatar` | boolean | `true` | Per-address Gravatar existence lookup. |
| `refreshDisposableList` | boolean | `true` | Pull the current blocklist at run start; falls back to the vendored copy if the download fails or looks wrong. |
| `skipDuplicates` | boolean | `false` | Off by default so output rows line up 1:1 with your input file. On, repeats are dropped **before** they are verified or charged. |
| `maxEmails` | integer | `0` | Stop after N addresses. 0 = no limit. |
| `proxyConfiguration` | object | off | Optional. Not needed — every lookup answered 200 without a proxy when measured. Turn it on to spread Gravatar requests over more IPs on very large lists. |

***

### 📤 Output — 83 fields per address

```json
{
  "email": "hello@figma.com",
  "email_normalized": "hello@figma.com",
  "local_part": "hello", "domain": "figma.com", "domain_unicode": "figma.com", "tld": "com",
  "input_index": 16,

  "is_valid_syntax": true, "syntax_error_code": null,
  "has_plus_tag": false, "tag": null, "email_without_tag": "hello@figma.com",
  "gmail_canonical": null, "is_duplicate_in_list": false, "duplicate_of_index": null,
  "local_part_length": 5, "has_unicode_local_part": false, "is_quoted_local_part": false,

  "is_role_account": true, "role_type": "info",
  "is_disposable": false, "disposable_list_version": "upstream-2026-09-03 (8714 domains)",
  "is_free_provider": false, "free_provider_name": null,
  "did_you_mean": null, "typo_distance": null,

  "has_mx": true,
  "mx_records": [
    { "host": "aspmx.l.google.com", "priority": 1 },
    { "host": "alt1.aspmx.l.google.com", "priority": 5 },
    { "host": "aspmx2.googlemail.com", "priority": 10 }
  ],
  "mx_count": 5, "mx_ttl": 3600, "is_null_mx": false, "mx_fallback_a": false,
  "has_a": true, "a_records": ["18.160.41.23", "18.160.41.84"], "has_aaaa": false,
  "ns_records": ["ns-912.awsdns-50.net", "ns-1239.awsdns-26.org"],
  "dns_provider": "AWS Route 53", "dnssec_ad": false, "dns_rcode": 0,

  "mail_provider": "Google Workspace", "mail_provider_confidence": "high",
  "mx_ip": "142.251.179.27", "mx_asn": 15169,
  "mx_asn_org": "GOOGLE - Google LLC, US", "mx_network_cidr": "142.251.179.0/24",

  "spf_present": true,
  "spf_record": "v=spf1 a include:_spf.google.com ip4:149.72.216.165 include:mail.zendesk.com include:mg-spf.greenhouse.io -all",
  "spf_policy": "-all",
  "spf_includes": ["_spf.google.com", "mail.zendesk.com", "mg-spf.greenhouse.io"],
  "spf_lookup_count": 4,
  "dmarc_present": true, "dmarc_record": "v=DMARC1; p=quarantine;",
  "dmarc_policy": "quarantine", "dmarc_subdomain_policy": null, "dmarc_pct": 100,
  "dmarc_rua_present": false, "dmarc_aspf_adkim": "aspf=r;adkim=r",
  "dkim_selectors_found": ["google", "k1", "k2", "k3", "s1", "s2"],
  "dkim_esp_guess": ["Google Workspace", "Mailchimp", "SendGrid"],
  "bimi_present": true,
  "mta_sts_mode": "none", "mta_sts_mx": [], "mta_sts_max_age": null,
  "tls_rpt_present": false,

  "domain_created_at": "1999-04-10T04:00:00Z", "domain_age_days": 10007,
  "domain_expires_at": "2027-04-10T04:00:00Z", "domain_days_to_expiry": 219,
  "domain_registrar": "Amazon Registrar, Inc.",
  "domain_status": ["client transfer prohibited"],
  "domain_locked": true, "domain_nameserver_count": 4, "rdap_error": null,

  "gravatar_found": false, "gravatar_url": null,
  "gravatar_hash": "e9f43503669f400ab1eb0a136e2ba5b8",

  "status": "role", "score": 55,
  "reason_codes": ["mx_found:5", "mail_provider:Google Workspace", "spf:-all", "dmarc:quarantine",
                   "dkim_selectors:6", "domain_age_days:10007", "role_account:info"],
  "risk_flags": ["mailbox_existence_not_tested", "catch_all_not_tested"],

  "smtp_checked": false,
  "mailbox_existence": "not_tested_port_25_blocked",
  "checked_at": "2026-09-03T02:34:11.208Z"
}
```

#### `status` values

| Status | Meaning |
|---|---|
| `deliverable_infrastructure` | Well-formed, the domain routes mail, nothing suspicious. **Not** a claim that the mailbox exists. |
| `undeliverable` | Bad syntax, NXDOMAIN, null MX (RFC 7505), or no mail route at all. Safe to drop. |
| `disposable` | Throwaway-mailbox domain. |
| `role` | Functional address, not a person. Routable, but it does not belong in a personal-outreach sequence. |
| `risky` | Routable but flagged — likely typo, domain under 30 days old, `+all` SPF, domain past/near expiry, implicit-MX-only, or SMTPUTF8 local part. |
| `unknown` | The DNS lookup itself failed. `score` is `null`. **This is never reported as "undeliverable"** — a failed lookup is not a verdict. |

***

### 💰 Pricing

**Pay per event: `$0.0005` per address verified = $0.50 per 1,000.**

| List size | Cost |
|---|---|
| 1,000 addresses | **$0.50** |
| 10,000 addresses | **$5.00** |
| 100,000 addresses | **$50.00** |

- **No actor-start fee.** No monthly rental. No platform-usage surcharge.
- **Delivered = billed.** Rows are pushed with `Actor.pushData(items, 'email-verified')`, which is budget-aware: it delivers only what fits your charge cap and bills atomically. You are never charged for a row you did not receive, and you never receive rows past your cap that then get billed. Verified on the live run: 600 rows delivered, `chargedEventCounts: { "email-verified": 600 }`.
- **The deep checks are included**, not an upsell. DKIM sweep, RDAP domain age, MTA-STS policy fetch and ASN lookup are all inside the same $0.0005.
- `skipDuplicates: true` drops repeats **before** billing, so you can pay for 8,400 unique addresses instead of 10,000 rows.

***

### ⚠️ Honest limits

Read this section before you buy. It is the whole reason to trust the rest.

#### 1. There is no SMTP mailbox check. There cannot be.

Answering "does this mailbox exist" requires an SMTP `RCPT TO` probe on **outbound TCP port 25**, and port 25 is blocked platform-wide on Apify. Measured from a real Apify run on 2026-09-02: every MX host on `:25` returned `ETIMEDOUT` in ~260 ms, while `smtp.gmail.com:587` returned a live `220` banner from the same run — so the egress works and it is specifically port 25 that is closed.

Every row therefore carries:

```json
"smtp_checked": false,
"mailbox_existence": "not_tested_port_25_blocked"
```

**The second half of this, which matters more:** even where port 25 *is* reachable, an SMTP probe against Google Workspace or Microsoft 365 returns `250 2.1.5 OK` for a **garbage** recipient exactly as it does for a real one. Between them those two run a large share of all business email. So on most B2B domains an "SMTP verified" flag is not a weaker signal than this actor's — it is a **fabricated** one.

#### 2. There is no `catch_all` field, on purpose.

Catch-all is a **per-tenant mail-server setting**. Knowing the MX is Google Workspace tells you nothing about whether *that tenant* accepts all recipients. The only way to know is the SMTP probe from limit #1. Other actors in this category ship a `catch_all` / `is_catch_all` boolean; on a port-25-blocked platform that value cannot have been measured. This actor omits the field rather than guessing it, and that omission is deliberate.

#### 3. What "deliverable\_infrastructure" actually promises

It promises: the address parses, the domain resolves, the domain accepts mail, and nothing in the DNS, authentication or registration record is suspicious. It does **not** promise the mailbox exists, is monitored, or will not bounce. The status name is deliberately clumsy so it cannot be misread as "verified".

#### 4. Domain age is unavailable for some TLDs

The IANA RDAP bootstrap maps **1,200 TLDs**; `.de`, `.ru`, `.io`, `.jp`, `.ch`, `.nl`, `.so`, `.me` and `.edu` have no public RDAP server in it. Those rows return `domain_age_days: null` with `rdap_error: "tld_not_in_bootstrap"`. Measured: **91 % of rows** on a 200-domain international list got a real registration date.

#### 5. Gravatar is a positive-only signal

A Gravatar hit is real evidence of a person. A miss is not evidence of anything — most real addresses have no Gravatar (measured hit rate on the 600-row run: **2 %**). It adds to the score and never subtracts.

#### 6. SPF lookup counting is top-level only

`spf_lookup_count` counts the DNS-querying terms in the domain's own record. It does not recurse into `include:` chains, so a record can exceed the RFC 7208 limit of 10 through nesting without this actor flagging it.

#### 7. Anti-bot / blocking

There is none to report, and that is a measured statement, not an assumption. Every endpoint answered **HTTP 200** both directly and through the Apify proxy (`auto` and `groups-RESIDENTIAL,country-US`) when tested on 2026-09-02, and a burst test through the Apify proxy returned **150/150 DoH queries**, **25/25 RDAP lookups** and **80/80 Gravatar requests with zero 429s**. On the live 600-address run there were no blocks and no rate limits. If Gravatar ever does throttle you, those rows come back `gravatar_found: null` with a warning in the log — the run does not fail, and no other field is affected.

#### 8. robots.txt

No path this actor requests is disallowed:

- `gravatar.com/robots.txt` disallows `/*.json`, `/*.xml`, `/*.php`, `/*.vcf`, `/*.qr`, `/*.card`, `/accounts/…`, `/account/`, `/v1/`, `/v2/`, `/v3/`. This actor requests `/avatar/<hash>` — not covered by any of those.
- `data.iana.org/robots.txt` is `Disallow: /root-anchors/` and `Disallow: /ksk-ceremony/`. This actor requests `/rdap/dns.json`.
- `rdap.verisign.com` and the other RDAP registries serve no robots.txt (HTTP 400 / 404). RDAP is the IETF's standard **machine-readable** registration-data protocol (RFC 7482 / RFC 9082) — programmatic queries are its entire purpose.
- DNS and DNS-over-HTTPS are protocols, not crawled sites.

No personal data is scraped from any website. The inputs are addresses **you already have**.

***

### 🛠 How it works

| Signal | Source | Notes |
|---|---|---|
| MX / A / AAAA / NS / TXT, DKIM sweep | `node:dns` against the container's own resolver | UDP, local, no shared HTTP rate limit. Falls back to DoH on a transient failure. |
| `mx_ttl`, `dnssec_ad`, authoritative `dns_rcode` | DNS-over-HTTPS (Cloudflare + Google, round-robin) | The only source of these three — `node:dns` does not expose TTL or the AD flag. |
| Domain age, registrar, EPP status | RDAP, resolved through the IANA bootstrap | 1,200 TLDs → ~590 registry servers. |
| `mx_asn`, `mx_asn_org`, `mx_network_cidr` | Team Cymru, over plain DNS | |
| `mta_sts_mode`, `mta_sts_mx`, `mta_sts_max_age` | `https://mta-sts.<domain>/.well-known/mta-sts.txt` | Only fetched when the domain publishes the `_mta-sts` TXT record. |
| `gravatar_found` | `gravatar.com/avatar/<md5>?d=404` | 200 = found, 404 = not found. |
| `is_disposable` | disposable-email-domains blocklist | 8,714 domains, refreshed at run start, vendored as a fallback. |

**Everything except the Gravatar lookup is cached per DOMAIN, not per address.** A 10,000-address B2B list is usually a few hundred distinct domains, which is why the deep checks are affordable enough to include for free.

***

### ❓ FAQ

**Does this tell me if an email address really exists?**
No, and neither does anything else running on this platform — see [Honest limits](#-honest-limits) #1. It tells you whether the address is well-formed, whether its domain can receive mail, who runs that mail, and whether the address looks like a person, a role, a typo or a throwaway. On a real list that removes most of the addresses worth removing.

**Then what is it actually for?**
Cleaning a list before you send to it. Removing malformed addresses, dead domains, null-MX domains, disposable domains and role accounts; catching typos before they bounce; flagging brand-new domains; and splitting personal from functional addresses. Plus a second job most verifiers do not do at all: telling your sales team **what mail stack a prospect runs** (Google Workspace vs Microsoft 365 vs Proofpoint, which ESPs they send through, whether they enforce DMARC).

**Why do competitors have a `catch_all` field and you don't?**
Because it cannot be measured here. See limit #2. Shipping the field would mean shipping a guess.

**How does it compare to the other verifiers on the store?**
On price: $0.50/1,000 is the cheapest per-address rate in the category. On depth: the two category leaders declare **13** and **11** dataset fields; this one returns **83**, measured on a live run. On honesty: this is the only one that states what it cannot do, in the output rows themselves.

**Will it work on a 100,000-address list?**
Yes. Measured throughput is 600 addresses / 200 distinct domains in 26 seconds; the per-domain cache means cost scales with *distinct domains*, not with rows. Set `maxEmails` if you want a bounded first pass, and `skipDuplicates: true` if your list has repeats you would rather not pay for.

**Can I chain it onto a scraper?**
Yes — `inputDatasetId` + `emailField`. Point it at any actor's dataset (a lead scraper, a directory scraper, your own) and it reads the address out of each item, including from a dotted path like `contact.email` or from an array field.

**How does deduplication work?**
On the tag-stripped address, and additionally on the dot-stripped form for `gmail.com` / `googlemail.com` — so `j.a.n.e.doe+news@gmail.com`, `janedoe@googlemail.com` and `JANEDOE@GMAIL.COM` are recognised as one mailbox. By default every input row still produces one output row (with `is_duplicate_in_list` and `duplicate_of_index` set) so the output lines up with your source file; `skipDuplicates: true` drops them before billing instead.

**What does the `score` mean?**
0–100, starting at 100 and deducted for each real negative signal (no SPF, no DMARC, free provider, plus-tag, new domain, near-expiry domain, permissive `+all` SPF, likely typo), with a small bonus for a Gravatar hit or an enforcing MTA-STS policy. Disposable caps at 15, role caps at 55, anything undeliverable is 0, and a failed DNS lookup gives `null` rather than a made-up number. `reason_codes[]` shows every input to that number, so you can build your own scoring instead.

**Do I need a proxy?**
No. Every endpoint answered 200 without one — see limit #7. The option exists for very large lists where you would rather spread the Gravatar requests.

**Does it handle international / IDN addresses?**
Yes. Unicode domains are converted to punycode for the lookups and returned in both forms (`domain` and `domain_unicode`). Unicode local parts (SMTPUTF8, RFC 6531) pass syntax but are flagged `smtputf8_local_part`, because delivery to them is not universal.

**Why is `domain_age_days` empty on some rows?**
Because that TLD has no public RDAP server — see limit #4. The row tells you so via `rdap_error`.

**What happens on a run that gets blocked or hits an outage?**
It exits cleanly with an explanatory status message and keeps the rows it already produced. A transient failure is never reported as "no results", and lookups that failed come back as `status: "unknown"` with a `null` score — never as `undeliverable`.

***

### ⚖️ Legal & fair use

This actor performs **DNS lookups**, **RDAP registration queries** and **Gravatar avatar lookups** against addresses **you supply**. It does not crawl any website, does not collect personal data from any site, does not log in anywhere, and does not send email. RDAP is the IETF's public, standards-defined registration-data protocol. DNS is a public protocol.

You are responsible for having a lawful basis for holding and processing the address list you submit (GDPR/CCPA where they apply), and for complying with CAN-SPAM, GDPR and local anti-spam law when you send to it. Verifying a list does not create permission to email it.

# Actor input Schema

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

The addresses to verify, one per row. You can also paste a comma-separated list into a single row. Leave every input source empty and the actor runs a 5-address demo so it never fails with nothing to do.

## `emailsText` (type: `string`):

Paste raw text and every address in it is extracted, in order. Handles `one@perline.com`, comma lists, CSV rows like `Jane Doe,jane@acme.com,Head of Ops`, and `Name <a@b.com>` formats. Non-address columns are ignored.

## `inputDatasetId` (type: `string`):

Chain this verifier straight onto a lead scraper: paste the dataset ID of a previous run and every address in it is verified. Combine with the field name below.

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

Which field of each dataset item holds the address. Dotted paths work (`contact.email`), and if the field holds an array every entry is verified.

## `deepChecks` (type: `boolean`):

ON (default) adds the DKIM selector sweep, the RDAP registration record (domain age, expiry, registrar, EPP status), the fetched MTA-STS policy and the Team Cymru ASN/CIDR for the mail server. These are per-DOMAIN lookups, cached across your whole list, so on a typical B2B list of a few hundred distinct domains they cost very little. Switch OFF for the fastest possible pass on a very large consumer list.

## `checkGravatar` (type: `boolean`):

ON (default) looks up gravatar.com for each address. A hit is the only per-address evidence of a real human behind the address that exists without an SMTP probe. A miss proves nothing — most real addresses have no Gravatar — so it is scored as a small positive, never as a negative.

## `refreshDisposableList` (type: `boolean`):

ON (default) pulls the current disposable-email-domains blocklist and merges it over the copy vendored in the image. If the download fails, or comes back implausibly small, the vendored 8,714-domain copy is used and the run says so in the log.

## `skipDuplicates` (type: `boolean`):

OFF by default, so every input row gets exactly one output row and the indexes line up with your source file. Switch ON to drop repeats before they are verified — duplicates are matched on the tag-stripped address, and on the dot-stripped form for Gmail, so `j.doe+news@gmail.com` and `jdoe@gmail.com` count as one.

## `maxEmails` (type: `integer`):

Stop after this many addresses. 0 = no limit.

## `proxyConfiguration` (type: `object`):

Optional, and off by default: every lookup this actor makes (DoH, RDAP, MTA-STS, Gravatar) answered 200 without a proxy when measured on 2026-09-02. Turn one on if you are verifying very large lists and want the Gravatar requests spread over more IPs. DNS itself always uses the container resolver and is never proxied.

## Actor input object example

```json
{
  "emails": [
    "support@stripe.com",
    "sales+q3@shopify.com",
    "info@mailinator.com",
    "zzzz-not-real-9931@gmial.com",
    "nobody@nonexistent-domain-zzq12345.com"
  ],
  "emailField": "email",
  "deepChecks": true,
  "checkGravatar": true,
  "refreshDisposableList": true,
  "skipDuplicates": false,
  "maxEmails": 0,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `verifiedEmails` (type: `string`):

The dataset of verified addresses (one item per submitted address, in input order).

# 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": [
        "support@stripe.com",
        "sales+q3@shopify.com",
        "info@mailinator.com",
        "zzzz-not-real-9931@gmial.com",
        "nobody@nonexistent-domain-zzq12345.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/email-verifier-scraper").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": [
        "support@stripe.com",
        "sales+q3@shopify.com",
        "info@mailinator.com",
        "zzzz-not-real-9931@gmial.com",
        "nobody@nonexistent-domain-zzq12345.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/email-verifier-scraper").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": [
    "support@stripe.com",
    "sales+q3@shopify.com",
    "info@mailinator.com",
    "zzzz-not-real-9931@gmial.com",
    "nobody@nonexistent-domain-zzq12345.com"
  ]
}' |
apify call scrapersdelight/email-verifier-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapersdelight/email-verifier-scraper"
        }
    }
}

```

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/eN5wf4iJvPPexJEFC/builds/itxcYXhNMmo172Eyw/openapi.json
