Catch bad email before it bounces.
One POST request checks the syntax, the domain’s live mail servers, the mailbox itself, and open disposable and role lists — then hands you a reasoned verdict and a typo suggestion, without ever sending a message.
Free credits every month. No credit card required. The demo on this page runs against the live service.
Live demo
POST /demo/email · no key needed
Response
Verify any address to see the full API response, straight from the live service.
Every response field, documented.
This is the actual contract of POST /api/v1/email — booleans are tri-state: false is a checked negative, null means the check did not apply (for example after invalid syntax). Each response also carries sources naming the datasets consulted and meta with your credit usage.
| Field | Type | What it tells you |
|---|---|---|
| The address as checked: local part preserved, domain lowercased and converted to punycode. Wrappers people paste — a display name (Mia K <mia@…>), a mailto: prefix, spreadsheet quotes, trailing punctuation, invisible characters — are stripped first. Input that still fails the syntax check is echoed back. | ||
| normalized_email | The canonical inbox the mail actually reaches: lowercased, plus-tag stripped, and Gmail dots and googlemail.com folded away. Dedupe your list on this, not on the raw input. | |
| is_alias | Whether the address carries a plus-tag (mia+promo@…) — the same inbox under a disposable label, a favorite of signup-bonus farmers. | |
| is_gibberish | Whether the local part reads as keyboard mash (xkqzwv@…). Deliberately conservative: y counts as a vowel to protect names like krzysztof, and digit-only locals (normal on qq.com) are never flagged. | |
| looks_generated | Whether the username looks machine-made or a placeholder rather than a person's choice: a hex string of 16 or more characters with at least one a–f, a dashed UUID, 20 or more alphanumerics mixing upper case, lower case and digits, one or two letters followed by four or more digits, any word (with at most one separator) followed by six or more digits, or a placeholder word — test, tester, testing, temp, fake, asdf, asdfasdf, qwerty, abc, abcd, aaa, xxx, sample, example, foo, foobar, null, none, noname, dummy, placeholder — with optional trailing digits. A heuristic about the string, never about the mailbox; +20 on the score. | |
| local_part_pattern | The shape of the username, read with the plus-tag and up to four trailing digits removed: first.last, first_last, first-last, firstlast, f.last, flast, first.l, firstl, last.first, lastfirst, first or last (the name shapes, recognised with the bundled name lists), numeric (digits only), word (an alphabetic token the lists do not know), mixed (letters and digits forming no name shape) or other. The bundled first-name list is small, so a lone first name the list lacks reads as last. | |
| first_name | The name token the bundled dictionary recognised as a first name, lowercased as typed, only for shapes that carry a full first name. Derived from the address you sent, never looked up anywhere, and never a promise of correctness: a pair like mia.kowalski reads positionally, while a lone name the first-name list lacks lands in last_name instead. | |
| last_name | The name token the bundled dictionary recognised as a surname, only for shapes that carry a full surname. Derived from the address you sent; nothing is appended from outside, and the lists say nothing about gender. | |
| deliverable | The verdict: deliverable (the exact mailbox was confirmed), risky (a burner, a typo-squat, a parked domain, mail served from a blocklisted network, a web-only domain, a no-reply or role inbox, a full mailbox, a mailbox that was dead and exists again, a catch-all, or a mailbox the handshake could not confirm), or undeliverable (bad syntax, an impossible username, no usable mail servers, or a missing or disabled mailbox). | |
| reason | Why the verdict is not deliverable. Undeliverable: invalid_syntax, invalid_local_part, no_mx_records, null_mx, mx_unresolvable, mailbox_not_found, mailbox_disabled. Risky: likely_typo, parked_domain, disposable, mx_blocklisted, implicit_mx, mailbox_full, no_reply, role, recycled_mailbox, catch_all, unverified. | |
| risk_score | A branchable 0–100 composite of the individual signals with published weights: disposable 80, parked domain 60, typo-squat 60, blocklisted mail network 60, web-only domain 30, role 30, catch-all 30, domain under 30 days old 30, recycled mailbox 30, full mailbox 20, gibberish 20, generated 20, no-reply 20 (on top of role), widely seen 20, catch-all behind a security gateway 15 (instead of 30), domain under 180 days 10, no SPF on a business domain 10, unverified mailbox 10, registration ending within 30 days 10, plus-tag alias 5, relay 5. Undeliverable is always 100. | |
| risk_level | The score bucketed for easy branching — low, medium (30+), or high (60+) — using the same thresholds as the IP endpoint. | |
| risk_signals | The signals that fired, each as { signal, weight, dataset } in the published order, so the score is the sum of the weights and every weight names the dataset it was read from. Empty when nothing fired, and empty on an undeliverable verdict, where the verdict is the answer and the score is fixed at 100. | |
| syntax_valid | Whether the address parses under RFC 5322 grammar. | |
| domain | The ASCII (punycode) domain actually used for the DNS and list checks. | |
| domain_category | The class of the domain, first match wins: relay (a forwarding service such as Firefox Relay or SimpleLogin), disposable, webmail (a free mailbox provider), military, government or academic from their suffixes, or other. Never "business": from here a personal domain and a company domain look alike. | |
| mx_found | Whether the domain has usable mail servers. An RFC 7505 null MX ("this domain accepts no mail") counts as false, and so do MX records that only point at loopback, private, or non-existent hosts. | |
| mx_implicit | True when the domain publishes no MX record and mail would fall back to its own address (RFC 5321). Such web-only domains rarely accept mail, so the verdict is risky and no mailbox check is run. | |
| mx_provider | Who runs the domain's mail, from its MX hostnames: google, microsoft, zoho, proofpoint, mimecast, and a dozen more, or "other" for self-hosted. Useful for routing and qualifying B2B signups. | |
| mx_provider_type | What that provider is: mailbox_provider (Google, Microsoft, Zoho, Proton, Fastmail, Yahoo, iCloud, Yandex, GMX, Tutanota, HEY), security_gateway (Proofpoint, Mimecast, Barracuda — they accept everything at the edge and sort it out behind), transactional (Mailgun, Amazon SES), hosting (Rackspace, GoDaddy), self_hosted (every MX host under the domain itself), or other. Null when there is no provider. | |
| mx_blocklisted | Whether any of the domain's mail hosts resolves into a network on the threat blocklists the IP product already installs (Spamhaus DROP and ASN-DROP, the abuse.ch botnet list). Mail served from a hijacked or criminal netblock is a burner-domain signal the disposable list has not caught yet: +60 and a risky verdict. false is a checked negative; null means no MX, unresolvable hosts, or no blocklist feed installed. | |
| mx_network_type | The class of network the first resolvable mail host sits in: hosting when it is inside a datacenter range, otherwise the operator's type from the ASN database (education, government, hosting, isp). Null when unknown. Informational; it carries no weight. | |
| has_spf | Whether the domain publishes an SPF policy. Real companies almost always do; a missing record suggests a parked or throwaway domain. | |
| dmarc_policy | The domain's DMARC policy — none, quarantine, or reject — or null when it publishes none. A strict policy signals a domain someone actually operates. | |
| domain_registered_at | The date the domain was registered, from public RDAP registry data. Null for free providers and burners (not consulted) and where the registry does not publish it. | |
| domain_age_days | Days since registration. Fraud rings register domains hours before they use them; anything under 30 days adds 30 to the risk score, under 180 adds 10. | |
| domain_expires_at | When the registration ends, from the same RDAP record as the registration date, consulted exactly when that is (business domains only). A registration ending within 30 days adds 10: most healthy domains renew at the last minute, so it is a nudge, not a verdict. | |
| parked | Whether the domain's nameservers belong to a parking or for-sale service. A parked domain that still accepts mail is a harvesting catch-all rather than a mailbox provider: +60 and a risky verdict. Evaluated only when the domain has usable mail servers; null otherwise, or when the nameserver lookup failed. | |
| disposable | Whether the domain — or a parent domain handing out subdomains — is a known burner-email provider, or routes its mail through one: a brand-new burner domain still points its MX at a listed operator. A local allow and deny list overrides the synced upstream, and a relay service is never disposable: for one, this is forced false. | |
| is_relay | Whether the domain belongs to a permanent alias or forwarding service: Firefox Relay, SimpleLogin, Addy.io, DuckDuckGo Email Protection, Proton Pass, Sign in with Apple's private relay and the rest. A relay address is a real person's real mailbox reached through a name they control and often kept for years, which is why it is never reported as disposable; it adds 5, because the identity behind it is withheld. Ordinary iCloud Mail and Proton Mail mailboxes are not relays, and an iCloud+ Hide My Email alias on icloud.com cannot be told apart from one. | |
| relay_service | The relay service's name when is_relay is true. | |
| role | Whether the local part is a role account such as admin@, support@, or billing@ — over a thousand prefixes from an open list, checked with any plus-tag removed, plus any no-reply spelling the list lacks. | |
| role_type | Which kind of role: no_reply (mail to it is discarded by design), postmaster (the RFC 2142 family — postmaster, abuse, hostmaster, webmaster, security), support, sales, info, admin, billing, marketing, careers, legal, or other for a listed username outside those families. Null when the address is not a role. | |
| free_provider | Whether the domain is a known free mailbox provider like gmail.com, outlook.com, or seznam.cz — thousands of providers and country variants from an open list. | |
| smtp_checked | Whether a definitive mailbox-level SMTP answer was obtained. Included in every lookup at the same single credit; when mail servers will not say (greylisting, timeouts), it reports false instead of guessing. | |
| smtp_checked_at | When the mailbox probe actually ran, as an ISO 8601 timestamp. Confirmed answers are shared across customers for up to a week, so this can predate the lookup; null when no definitive answer was obtained. | |
| mailbox_exists | Whether the exact inbox exists, from the SMTP-level check. Null when unconfirmed, or when a catch-all domain makes existence unknowable. | |
| previously_invalid | Whether this mailbox exists now but answered "no such mailbox" to an earlier handshake. That is the shape of a recycled address, the spam-trap kind included, so the verdict is risky with reason recycled_mailbox and +30. false means it exists with no such history; null whenever the mailbox is not confirmed. | |
| previously_invalid_at | The date of that earlier not-found answer. It is kept for a year under a keyed hash of the address, and erased with the address. | |
| catch_all | Whether the domain accepts mail for every address, which turns the verdict risky: a mailbox there can never be individually confirmed. Behind a security gateway the weight is 15 rather than 30, because the gateway accepts everything by design and the domain behind it is a real, administered one. | |
| smtp_reason | The detail behind the mailbox check: no_mailbox, mailbox_full, disabled, greylisted, timeout, no_connect, mx_unreachable, provider_unavailable when the verification provider itself could not be reached, or delivered_recently when your own account reported the address delivered within 90 days, which counts as a confirmed mailbox for your lookups. A full mailbox is risky and a disabled one undeliverable; the rest explain an unverified answer. | |
| mailbox_confidence | For catch-all domains and unanswered checks only: a recomputable 0–100 estimate that the specific mailbox exists. Starts at 50; DMARC, SPF, an established mail provider, an old registration, and a name-shaped address add to it; gibberish, a fresh domain, and a plus-tag take away. Once five or more delivery outcomes have been reported for the domain, a bounce rate of 10% or less adds 15 and one of 50% or more removes 20, and a username that follows the naming convention seen in the domain's confirmed deliveries adds 15 while a name shape that breaks it takes 10 away. A relay alias stays at 50 and moves only on the evidence against it: a bounce rate of 50% or more, or a plus-tag. A published heuristic, not a probe. | |
| widely_seen | Whether three or more other accounts looked this address up in the last 24 hours: an address hitting many signup forms in one day is being used by something automated. Counted from a keyed hash and account ids only, never the address, and +20 when it fires. Null where nobody could count: the guest demo, test addresses, invalid syntax. | |
| did_you_mean | A suggested correction ("[email protected]" for [email protected]), offered after the mail-server check fails or when the domain is a known typo-squat of a major provider — those resolve on purpose, to harvest misdirected mail. Candidates are the big mailbox providers, the thousand most-linked domains and the domains you verify most; a domain outside the provider list is suggested only when its own mail servers accept mail. | |
| ok_to_send | Your account's sending policy applied to this answer: false for every undeliverable verdict, and otherwise when a blocked signal fired, the score is over the policy's ceiling, or a catch-all or unverified mailbox sits under its confidence floor. The published defaults block disposable, likely_typo, parked_domain, mx_blocklisted and no_reply and any score over 60; GET, PUT and DELETE /api/v1/email/policy read, change and reset them. | |
| blocked_by | The rule that made ok_to_send false, in the order they are applied: undeliverable, the name of the first blocked signal that fired, risk_score, or mailbox_confidence. Null when ok_to_send is true. |
One request, and a verdict you can act on at the form.
- Invalid syntax answers instantly, without touching DNS
- Disposable and role flags explain every risky verdict
- Uncertain answers are free, and undeliverable ones are free within fair use — you pay for working addresses
- Repeats of a charged lookup are free for seven days
$ curl https://spaw.co/api/v1/email \ -H "Authorization: Bearer sk_live_…" \ -H "Content-Type: application/json" \ -d '{"email": "[email protected]"}' { "success": true, "data": { "deliverable": "risky", "reason": "disposable", "syntax_valid": true, "mx_found": true, "disposable": true, "domain_category": "disposable", "local_part_pattern": "word", "role": false, "risk_signals": [{ "signal": "disposable", "weight": 80, … }, …], "did_you_mean": null, "ok_to_send": false, "blocked_by": "disposable", "sources": […, { "dataset": "disposable-domains", … }], … }, "meta": { "credits_used": 1, "credits_remaining": 499, "cache_hit": false } }
Every data source, named in every response.
The sources block lists exactly the datasets a lookup consulted, with the version of each, so any verdict can be traced back to the list or the live check behind it. A check that never ran is never listed.
| Dataset | Covers | Refreshed |
|---|---|---|
| rfc-5322-syntax | RFC 5322 parsing of the address itself — the one check an invalid address ever reaches. | |
| live-dns | Live DNS MX, SPF, and DMARC resolution | |
| disposable-domains | Curated disposable-domain lists | |
| role-addresses, free-provider-domains | Open role-address and free-provider lists | |
| typo-domains, provider-username-rules | Provider username rules and typo-squat map | |
| mx-provider-patterns | The mail providers behind a domain’s MX hostnames, which name mx_provider. | |
| mailbox-smtp | SMTP mailbox checks, partner infrastructure | |
| delivery-feedback | Delivery outcomes your own account reported in the last 90 days, which confirm a mailbox without a probe. Only your own. | |
| rdap-registration | RDAP registry registration dates |
Data synced on 2026-10-12: the newest sync of the open lists and curated maps behind the answers on this page. Every response carries the same dates in its sources block.
The form helper, on your signup form in minutes.
A dependency-free script wires the API into any form: it corrects typos before they become dead signups and flags addresses that cannot receive mail.
Create a publishable key
A pk_ key is safe to ship in page source: it can only run single lookups through the four browser endpoints (email, phone, IP and postal address), and only from the domains you allow. Prefer your secret key server-side? A five-line proxy works too.
Mark your input
Include the script and add data-spaw-email to the field. The helper checks on blur and renders "Did you mean…?" corrections and dead-address notices.
Enforce your own policy
Every verdict fires a spaw:result event with the full response, so you decide what to do about disposable or role addresses. The helper itself never blocks a submit.
Protect it with Turnstile
Store a Cloudflare Turnstile pair on the key and add data-turnstile-site-key to the script tag. The helper fetches a fresh token before every lookup and the endpoint refuses lookups without one, so bots cannot spend your credits.
<script
src="https://spaw.co/spaw-form.js"
data-key="pk_live_…" defer></script>
<input type="email" name="email" data-spaw-email>
<script>
// optional: your own policy on top
addEventListener('spaw:result', (event) => {
if (event.detail.disposable) {
// e.g. disable the submit button
}
});
</script>Where one credit pays for itself.
Protect sender reputation
Keep dead domains and burner inboxes out of your list before they bounce and drag your deliverability down.
Stop trial abuse
Disposable detection with subdomain matching closes the loop on serial free-trial signups from temp-mail services.
Fix typos at the form
Turn did_you_mean into an inline "Did you mean…?" prompt and rescue signups that would otherwise silently fail.
What we won’t pretend to know.
Email verification is full of vendors selling certainty they don’t have. We document our limits instead.
Unknown is an answer we will give you
When mail servers refuse to say whether an inbox exists (catch-all domains, greylisting), we return null and smtp_checked: false instead of a confident guess. Nothing is ever simulated.
A message is never sent
Mailbox checks are SMTP handshakes that stop before any mail is transmitted, run on dedicated verification infrastructure. Nothing ever lands in an inbox or a spam folder, and your own domain and sender reputation are never involved.
A score you can recompute, not a black box
risk_score is just the documented flags added up with published weights — disposable, typo-squat, a parked domain, a blocklisted mail network, role, catch-all, a freshly registered domain, an unverified mailbox — and risk_signals lists exactly the ones that fired. Branch on the number for convenience, or ignore it and use the explicit booleans and dataset versions behind it.
Common questions about email lookups.
Does "deliverable" mean the inbox exists?
When mailbox_exists is true, yes: an SMTP-level check confirmed the exact inbox, and a missing one flips the verdict to undeliverable. Where mail servers will not say — catch-all domains, aggressive greylisting — we return null and smtp_checked: false rather than guessing. Every lookup includes this check at the same single credit.
Do you ever send email to the addresses I check?
Never. The free checks are syntax parsing, live DNS, and curated open lists. The mailbox check is an SMTP handshake that ends before any message is transmitted, so nothing ever reaches an inbox or a spam folder.
How fresh is the disposable-domain list?
It syncs weekly from an open aggregate of tens of thousands of burner domains, with subdomain matching so team.mailinator.com is caught by the mailinator.com entry. Every response names the list version that produced it.
What about international addresses?
Internationalized domains (mia@müller.de) are converted to their punycode form — the form DNS actually resolves — before the mail-server and list checks, and the response reports that canonical domain.
Guides
All guides- A catch-all behind a security gateway is the gateway's design, not a squat
- DKIM selectors explained, and why 'none found' is not 'no DKIM'
- Gate a signup form with the sending policy
- MTA-STS, TLS-RPT and BIMI for senders
- Reading a domain monitor alert
- Recycled spam traps, and the mailbox that came back
- Relay addresses are not burners
- What email verification keeps under GDPR
- Disposable email domains and how detection works
- Email bounce rate benchmarks and what "acceptable" means
- Email list cleaning checklist before a campaign
- How Spaw computes the risk score
- How to verify an email address without sending an email
- Role-based email addresses and how to treat them
- SMTP bounce codes explained, from 550 5.1.1 to 421 4.7.0
- SPF, DKIM and DMARC for developers
- What is a catch-all email address, and how should you handle it?
- Why an email validation regex is not enough
Verify your first address in minutes.
Create a key, send one POST request, and keep free lookups every month on us.