An email verification API answers a deceptively difficult question: should your application accept and use this address?
A regular expression can catch a missing @. It cannot tell you whether the domain receives mail, whether the address uses a disposable provider, or whether the mailbox can be corroborated. A useful verifier combines several signals and returns the reason behind its verdict.
This guide shows how to add that check to a signup form, lead pipeline, or backend job with PushMail.
Quick start with cURL
Create an API key in the PushMail dashboard, then call the validation endpoint:
curl -X POST https://pushmail.dev/api/v1/validate \
-H "Authorization: Bearer pm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"person@example.com"}'The response separates the overall verdict from the checks that produced it:
{
"data": {
"email": "person@example.com",
"valid": true,
"status": "risky",
"confidence": "mx_only",
"checks": {
"syntax": true,
"mx": true,
"disposable": false,
"role": false,
"mailbox": "unverifiable"
}
}
}valid is useful for a simple allow/reject decision. status, confidence, and checks let you build a more careful policy.
JavaScript and TypeScript example
Keep the API key on your server. Do not expose it in browser JavaScript.
type VerificationResult = {
email: string;
valid: boolean;
status?: "deliverable" | "undeliverable" | "risky" | "unknown";
confidence?: "verified" | "best_effort" | "mx_only" | "none";
suggestion?: string;
reason?: string;
checks: {
syntax: boolean;
mx: boolean;
disposable: boolean;
role: boolean;
mailbox?: "exists" | "not_found" | "catch_all" | "unverifiable";
};
};
export async function verifyEmail(email: string): Promise<VerificationResult> {
const response = await fetch("https://pushmail.dev/api/v1/validate", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PUSHMAIL_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ email }),
});
const body = await response.json();
if (!response.ok) {
throw new Error(body.error || `Verification failed: ${response.status}`);
}
return body.data;
}For a public signup form, a sensible policy is:
- Reject malformed addresses, domains without mail routing, and confirmed missing mailboxes.
- Ask the user to correct a likely typo when
suggestionis present. - Decide whether disposable addresses fit your product. A free trial may reject them; a privacy-focused service may allow them.
- Treat
riskyandunknownas uncertainty, not proof that an address is bad.
Python example
The API is plain HTTP, so it works without a vendor SDK:
import os
import requests
def verify_email(email: str) -> dict:
response = requests.post(
"https://pushmail.dev/api/v1/validate",
headers={
"Authorization": f"Bearer {os.environ['PUSHMAIL_API_KEY']}",
"Content-Type": "application/json",
},
json={"email": email},
timeout=15,
)
response.raise_for_status()
return response.json()["data"]
result = verify_email("person@example.com")
if not result["valid"]:
print(result.get("reason", "Address is not deliverable"))What each check means
Syntax checks the shape and length of the address. It is the fastest filter, but it proves nothing about delivery.
MX checks whether the domain advertises mail servers. Under RFC 5321, a domain without MX records can sometimes use an implicit mail exchanger through its address record, so a correct implementation must account for that fallback.
Disposable flags known temporary-email services. This is useful for trial abuse, coupon abuse, and low-quality lead prevention.
Role identifies shared addresses such as admin@, support@, or sales@. These can be legitimate, so the signal should usually inform a policy rather than cause an automatic rejection.
Mailbox represents mailbox-level evidence. Some providers offer definitive signals; other providers and catch-all domains cannot be conclusively checked without sending a message. Read how email verification works without sending an email before treating every green result as certainty.
Handle API failures separately from invalid emails
A timeout or 429 response does not mean the email is invalid. In a signup flow, fail open or ask the user to retry. In a background import, retry with exponential backoff and preserve the original address.
The validation endpoint returns:
400for an invalid request body.401for missing or invalid credentials.402when paid validations require more credits.429when the organization exceeds its request rate.
Start with the free allowance
Every organization receives 100 validations per calendar month at no charge. After that, pricing is usage-based. See the complete email validation documentation or create a verification account to test the examples above.