Email List Pre-Flight: dedupe, syntax, MX, disposable screen
Pricing
from $0.30 / 1,000 email address screeneds
Email List Pre-Flight: dedupe, syntax, MX, disposable screen
Screen a large email list before you pay a per-address verifier: normalise, de-duplicate, syntax-check, classify role/disposable/freemail addresses and flag domains with no MX. No SMTP probe, so it is priced as a pre-filter and never claims mailbox-level deliverability.
Pricing
from $0.30 / 1,000 email address screeneds
Rating
0.0
(0)
Developer
Michael Campanelli
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
7 days ago
Last modified
Categories
Share
Email List Pre-Flight
Screen a large email list before you pay a per-address verifier. Deduplicate, normalise, syntax-check, classify (role / disposable / freemail) and flag domains that cannot receive mail — at a fraction of the cost of a full mailbox-level verification pass.
Why a pre-filter and not another "email verifier"
The Apify Store already has strong per-address SMTP verifiers at roughly
$0.00065–$0.00089 per address (observed posted prices, 2026-10-08). They are
worth it, but they are priced per address, so every address that was obviously
dead before you started — a typo, a duplicate, a mailinator.com throwaway, a
domain with no MX record at all — is money spent on nothing.
This actor does that first pass deterministically and cheaply, and it never
claims more than it measures. Its output separates definite problems
(invalid_syntax, duplicate, no_mx, disposable, role_account) from
unknowns (mxFound: null when a domain was not resolved). Nothing is ever
reported as "deliverable": stats.smtpChecked is false in every run.
What it checks
| Check | What it means | Needs DNS |
|---|---|---|
| normalise | strips display names, mailto:, quotes, trailing domain dots; lowercases | no |
| dedupe | one row per unique address; later copies get verdict: duplicate and duplicateOf | no |
| syntax | RFC-lite dot-atom local part, length limits (64 local / 254 total / 63 per label), valid label and TLD shape | no |
| MX | does the domain publish any MX record at all | yes |
| disposable | throwaway providers (short, auditable exact-domain list) | no |
| role | shared mailboxes (info@, sales@, noreply@, …) — deliverable, but usually not the person you want | no |
| freemail | consumer providers (gmail, outlook, …) — a flag, never a failure verdict | no |
| typo suggestion | gmial.com → gmail.com, example.con → example.com | no |
Input
{"addresses": ["Dana <dana@acme.example>", "sales@acme.example"],"emailsText": "optional pasted blob, newline/comma/space separated","checks": { "mx": true, "disposable": true, "role": true, "freemail": true },"concurrency": 8,"resolverTimeoutMs": 2500}
checks.mx: false makes a run completely DNS-free (pure CPU), which is the
cheapest possible shape: no proxies, no external services, no browser.
Output
One dataset row per input entry, in input order:
{"input": "Dana <dana@acme.example>","normalized": "dana@acme.example","domain": "acme.example","verdict": "ok","duplicateOf": null,"flags": { "syntaxOk": true, "mxFound": true, "isDisposable": false, "isRole": false, "isFreemail": false },"mxHosts": ["mx1.acme.example", "mx2.acme.example"],"suggestedCorrection": null,"screened": true}
screened marks the rows that were actually screened (and therefore billed);
duplicate rows are reported with screened: false and are never charged.
Plus a SUMMARY key-value record with the run totals (input, unique, screened,
duplicates, per-verdict counts, resolver errors).
Declared outputs
The Actor ships a machine-readable output schema (.actor/output_schema.json),
so the run's Output tab, the output property of the API's run response, and
MCP/agent clients can find the results without reading this file:
| Output | Link |
|---|---|
| Screened addresses (JSON) | {{links.apiDefaultDatasetUrl}}/items |
| Screened addresses (CSV) | {{links.apiDefaultDatasetUrl}}/items?format=csv |
| Run summary | {{links.apiDefaultKeyValueStoreUrl}}/records/SUMMARY |
The CSV link is the one to use if you want the cleaned list straight in a mail
tool; the JSON link keeps the flags object per row.
verdict is one of ok, invalid_syntax, no_mx, disposable,
role_account, duplicate. A duplicate row is reported but not billed.
Billing model
Pay-per-event: one address-screened event per unique address evaluated
(duplicates are never charged twice). stats.screened is the number of events
the run requests; SUMMARY.chargedEvents is the number the platform actually
confirmed, so a buyer can audit the bill from the dataset alone. The two differ
only when a run hits the buyer's maxTotalChargeUsd budget, and then the
shortfall is logged and recorded in chargeLimitReached/chargeError instead
of being hidden.
Limits, stated plainly
- No SMTP probe. Mailbox existence, catch-all domains, full mailboxes and greylisting are out of scope by design — run a verifier after this pass.
- The disposable and freemail lists are deliberately short, exact-domain lists rather than a scrape of a public blocklist: they are auditable and stable, and they will not catch every throwaway provider in existence.
- Local-part case is normalised for deduplication (RFC 5321 allows case-sensitive local parts; in practice none of the large providers use that).
- DNS answers are point-in-time and are reported as measured, never cached across runs.
Development
node test/screen.test.js && node test/input.test.js && node test/pipeline.test.js
run.js replays the whole pipeline offline from a JSON input that carries a
captured mx map, so the deterministic behaviour (and the charged-event count)
can be reproduced without any network access.
Offline replay (run.js)
PENNY_INPUT_FILE=/path/to/input.json node run.js
run.js calls the same lib/pipeline.js the actor calls, so its output is
byte-deterministic. If PENNY_INPUT_FILE is unset, unreadable or does not carry
addresses[]/emailsText, it falls back to an embedded demo fixture and says so
on stderr; the emitted JSON always carries inputSource (file:<path> or
demo), so a verifier can never mistake the fallback for their own input.