Validate emails on sign-up forms
Validating addresses at sign-up catches typos and throwaway inboxes before they reach your database, but a check must never stop a real person from signing up. The rule of thumb: block only what is clearly wrong, accept everything else, and re-check what was unclear later.
Call the API from your server
Validate in your sign-up handler on the server, not in browser code: your API key stays secret, and bots cannot spend your credits by calling the API directly. If you also want feedback while the person types, call your own endpoint, add a bot check (such as a CAPTCHA or Turnstile) and rate-limit it.
Validate once, when the form is submitted, rather than on every keystroke: each completed check costs a credit.
Keep it fast
Send "timeout": 6 (or anything from 5 to 15) so slow mail servers do not hold up the form. A check that runs out of time is unknown and free, and you can re-check it later.
Decide on the result
| Result | Suggested handling |
|---|---|
deliverable | Accept. |
undeliverable | Ask the person to fix the address. Offer did_you_mean when it is set. |
risky with disposable | Ask for a permanent address, if your product needs one. |
risky with another reason | Accept and flag (for example for a confirmation email or review). |
unknown | Accept and re-check later. |
Error 402, 429 or 503, or no answer | Treat like unknown: accept and re-check later. |
JavaScript
// Returns { ok, message?, suggestion? } for your sign-up handler.
export async function screenSignupEmail(email, ip) {
let res;
try {
res = await fetch('https://api.email-validation.org/v1/validate', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.EV_API_KEY}`,
'Content-Type': 'application/json',
},
// A short budget keeps the form fast; a check that runs out of time is unknown and free.
body: JSON.stringify({ email, ip_address: ip, timeout: 6 }),
signal: AbortSignal.timeout(10_000),
});
} catch {
return { ok: true, recheck: true }; // network problem: accept, check later
}
if (!res.ok) return { ok: true, recheck: true }; // 402, 429, 503 …: treat like unknown
const r = await res.json();
if (r.status === 'undeliverable') {
return {
ok: false,
message: r.did_you_mean ? `Did you mean ${r.did_you_mean}?` : 'Please check this email address.',
suggestion: r.did_you_mean,
};
}
if (r.status === 'risky' && r.reason === 'disposable') {
return { ok: false, message: 'Please use a permanent email address.' };
}
// deliverable, other risky reasons, unknown: accept (flag risky and unknown for review or a later re-check)
return { ok: true, flag: r.status === 'risky' || r.status === 'unknown' };
}Python
import os
import requests
API = "https://api.email-validation.org/v1/validate"
def screen_signup_email(email: str, ip: str | None = None) -> dict:
"""Returns {"ok": bool, "message": str | None}. Never blocks on our errors."""
payload = {"email": email, "timeout": 6}
if ip:
payload["ip_address"] = ip
try:
resp = requests.post(
API,
headers={"Authorization": f"Bearer {os.environ['EV_API_KEY']}"},
json=payload,
timeout=10,
)
except requests.RequestException:
return {"ok": True, "recheck": True}
if not resp.ok: # 402, 429, 503 …: treat like unknown
return {"ok": True, "recheck": True}
r = resp.json()
if r["status"] == "undeliverable":
hint = f"Did you mean {r['did_you_mean']}?" if r["did_you_mean"] else "Please check this email address."
return {"ok": False, "message": hint}
if r["status"] == "risky" and r["reason"] == "disposable":
return {"ok": False, "message": "Please use a permanent email address."}
return {"ok": True, "flag": r["status"] in ("risky", "unknown")}Words that help
- Likely typo: "Did you mean name@gmail.com?" with a button that applies the suggestion.
- Undeliverable: "We could not deliver to this address. Please check it for typos."
- Disposable: "Please use an email address you will keep: we send your receipts and sign-in codes there."
Avoid messages that reveal how your screening works in detail, and never tell the person that "our provider" is down: just let them through.
Afterwards
- Re-check addresses flagged
unknownwith a bulk job or later single checks. - A confirmation email (double opt-in) remains the only way to prove the person controls the mailbox; validation makes sure the message can arrive.
- Watch your bounce and complaint rates per sign-up source to tune what you accept.
Related
- Errors and retries
- The risky status and the unknown status
- Test addresses to try every branch of your handler for free