Email and address verification
Does the data exist?With the reasons.
Every endpoint takes one thing a user typed and returns a verdict with the reasons behind it. Validating syntax is a solved problem worth nothing. Existence is the hard part: does this mailbox receive mail, does this street have that number.
Request
curl https://api.webkitapi.dev/v1/email \
-H "Authorization: Bearer $WEBKIT_KEY" \
-d '{"email": "john.doe@gmial.com"}'
Response
{
"verdict": "undeliverable",
"reasons": ["domain_typo"],
"suggestion": "john.doe@gmail.com",
"checks": {
"syntax": "pass",
"typo": "fail",
"mx": "skipped"
},
"duration_ms": 29
}
Three endpoints
One question per page, because they are three questions.
- Email verificationSyntax, provider rules, domain typo, disposable address, MX, SMTP probe, catch-all. Cheapest check first, and it stops as soon as the answer is settled.
- Postal address verificationDoes the street exist in that commune, is the number inside the range that really exists, do the postcode and the commune belong together. France, on open data.
- Signup checkBoth in one call, one combined verdict, the per-signal detail underneath.
Verdicts
Four answers, not a percentage.
There is no confidence score. A number nobody has calibrated is a claim we cannot stand behind, and it hands you a threshold decision you have less information than we do to make.
- deliverableMail will be accepted.
- undeliverableMail will bounce, and we can say why.
- riskyReal, but something here should give you pause.
- unknownWe could not tell, and we say so rather than guess.
The reasons array carries what a score destroys.
catch_all and disposable_domain would
compress to the same number, and they call for opposite
handling.
Start without an account
The calling code does not change.
Typo detection, syntax, provider rules and disposable domains run entirely inside your own process. No key, no network, no telemetry, and you can check that by reading the source.
Without a key
import { checkEmail } from "@webkitapi/email";
await checkEmail("john@gmial.com");
// verdict: "undeliverable"
// suggestion: "john@gmail.com"
// source: "local"
Instant, offline, cannot fail. Run it on blur.
With a key
import { checkEmail } from "@webkitapi/email";
await checkEmail("john@acme.com", { apiKey });
// verdict: "deliverable"
// checks: smtp "pass", catch_all "pass"
// source: "api"
The authoritative verdict. Run it on submit, so the bill follows real signups rather than typing.
Questions
Including the awkward answers.
- Why is there no confidence score?
- A percentage nobody has calibrated is a claim we cannot stand behind, and it hands you a threshold decision you have less information than we do to make. The reasons array carries what a score destroys: a catch-all domain and a disposable domain would compress to the same number, and they call for opposite handling.
- What does the unknown verdict mean?
- That we could not tell. A catch-all domain accepts every address, so the mailbox cannot be checked; a greylisting server asks us to come back later; a postcode can fall outside our coverage. Answering deliverable or undeliverable in those cases would be inventing a certainty.
- Which countries are covered for addresses?
- France today, from the Base Adresse Nationale. Anywhere else the verdict is unknown rather than a guess. Google really is better on worldwide coverage and on US CASS certification, and claiming otherwise would not survive the first customer.
- Can I use this without an account?
- Yes. The npm and PyPI packages do syntax, provider rules, typo detection and disposable domains entirely in your own process, with no key and no network. A key adds MX resolution, the SMTP probe and catch-all detection, which need a server and a sending reputation.
- Do you verify phone numbers?
- No. Twilio sets a reference price on number lookups that leaves no room underneath it, so building a worse version of it would help nobody.
- What happens when you are down?
- The client packages fall back to the local verdict and set a degraded flag rather than throwing. A signup form has to keep working when we are the broken part.