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.

POST /v1/email

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
}
Fits what you already use npm PyPI Zod Django WooCommerce MCP

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.

deliverable undeliverable risky unknown
  • 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.