Email deliverability glossary: 40 terms every sender should know
Forty email deliverability terms defined in one or two sentences each: bounces, SPF, DKIM, DMARC, spam traps, greylisting, sender reputation, inbox placement and verification.
An email verification API checks an address over SMTP and returns a verdict your code can act on. Here is the request, the JSON response, and working examples in curl, Node.js and Python.
An email verification API is an HTTP endpoint that takes an email address and returns, usually within a second, whether that address can receive mail. Behind the endpoint the service checks syntax, DNS and MX records, then opens an SMTP conversation with the receiving server to confirm the mailbox exists, without sending a message. Integrating one means calling that endpoint at the moment an address enters your system, typically from a signup handler, a CRM import or a pre-send check, and branching on the verdict that comes back.
Each request runs the same pipeline that the free email checker runs in the browser: syntax parsing, domain and MX lookup, disposable and role detection, an SMTP handshake up to RCPT TO, a catch-all probe, and scoring. The stages are described in detail in what is email verification. The difference with the API is that the result arrives as JSON, so your own code decides what happens next.
Verifyre returns one of four verdicts plus a score from 0 to 100:
| Score band | Usual verdict | What it means |
|---|---|---|
| 90 to 100 | deliverable | The mail server confirmed the mailbox |
| 60 to 89 | risky or unknown | Exists but catch-all, full or role-based, or the server was slow to answer |
| 30 to 59 | unknown or risky | No firm answer, or several negative signals |
| 0 to 29 | undeliverable | Syntax, DNS or the server itself said no |
The score is what you threshold on when the verdict alone is too coarse. A signup form might accept anything above 60; a cold outreach tool might accept 50 and tolerate a few bounces.
Authentication is a bearer token in the Authorization header. A single check is either a GET with the address in the query string or a POST with a JSON body. The simplest possible call is one line of curl:
curl "https://verifyre.co/api/v1/verify?email=sara.khan@example.com" \
-H "Authorization: Bearer vfy_live_YOUR_API_KEY"
The response looks like this:
{
"email": "Sara.Khan@Example.com",
"normalized": "sara.khan@example.com",
"status": "deliverable",
"reason": "mailbox_exists",
"reason_text": "The mail server confirmed the mailbox exists.",
"score": 97,
"deliverable": true,
"checks": {
"syntax": true, "domain": true, "mx": true, "smtp": true, "mailbox": true,
"catch_all": false, "disposable": false, "free": false, "role": false,
"spf": true, "dmarc": true
},
"mx": [{ "exchange": "mx1.example.com", "priority": 10 }],
"provider": "Google Workspace",
"did_you_mean": null,
"cached": false,
"ms": 812,
"credits_charged": 1,
"credits_remaining": 4999
}
normalized is the lowercased, trimmed form you should store. status and score are what most integrations branch on. checks exposes each stage, so you can treat role: true differently from catch_all: true even though both land in the risky band. did_you_mean is set when the domain looks like a typo of a common provider. cached is true when the address was checked within the last 24 hours, and those repeats are free. The full field reference is in the developer docs.
This is a server-side handler, not browser code. Never put the key in a page; for client-side form checks use the JavaScript widget, which uses a scoped key and emits a DOM event instead.
async function verifyEmail(email) {
const url = "https://verifyre.co/api/v1/verify?email=" + encodeURIComponent(email);
const res = await fetch(url, {
headers: { Authorization: "Bearer " + process.env.VERIFYRE_KEY },
signal: AbortSignal.timeout(8000),
});
if (res.status === 402) throw new Error("out_of_credits");
if (!res.ok) throw new Error("verifyre_http_" + res.status);
return res.json();
}
export async function handleSignup(req, reply) {
let result;
try {
result = await verifyEmail(req.body.email);
} catch (err) {
// Fail open: a verification outage must never block a real customer.
return createAccount(req.body.email, { unverified: true });
}
if (result.status === "undeliverable") {
return reply.code(400).send({
error: "That address cannot receive mail.",
suggestion: result.did_you_mean,
});
}
if (result.status === "deliverable" || result.score >= 60) {
return createAccount(result.normalized);
}
// risky or unknown: let them in, but ask for a confirmation click
return createAccount(result.normalized, { requireConfirmation: true });
}
The three-way branch is the pattern that works in practice: reject only firm undeliverable verdicts, accept clear deliverable ones, and route the middle band to a confirmation email rather than refusing it.
import os
import requests
def verify_email(email: str) -> dict:
r = requests.post(
"https://verifyre.co/api/v1/verify",
json={"email": email},
headers={"Authorization": f"Bearer {os.environ['VERIFYRE_KEY']}"},
timeout=8,
)
r.raise_for_status()
return r.json()
result = verify_email("sara.khan@example.com")
if result["status"] == "undeliverable":
print("reject:", result["reason_text"])
elif result["score"] >= 60:
print("accept:", result["normalized"])
else:
print("hold for confirmation")
Three places pay for themselves, in this order.
For anything beyond a few hundred addresses, use the bulk endpoint rather than looping over single calls. It accepts up to 50,000 addresses per job and runs them concurrently on our side.
curl -X POST https://verifyre.co/api/v1/bulk \
-H "Authorization: Bearer vfy_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"emails": ["a@example.com", "b@example.org"], "name": "March import", "callback_url": "https://yourapp.com/hooks/verifyre"}'
The response contains a job id. Poll GET /api/v1/bulk/{id} for progress, or supply a callback_url and we will POST to it when the job finishes. Results, one object per address in the same shape as the single-check response, are at GET /api/v1/bulk/{id}/results. Duplicates within a job are charged once.
A verification call depends on a remote mail server answering an SMTP conversation, and some of them are slow or evasive. Design for that.
ms field says exactly how long), but a greylisting server can stretch that. On a timeout, treat the address as unknown and fail open.cached: true and cost nothing, so a short-window cache of your own is unnecessary. For longer horizons, store the verdict and timestamp and re-verify after 90 days.invalid_request is a malformed address or body, 401 unauthorized a bad key, 402 out_of_credits an empty balance, 413 too_many_emails a bulk job over 50,000, and 429 the rate limit.No. The SMTP conversation stops after RCPT TO and never issues the DATA command, so nothing is transmitted and the mailbox owner sees nothing.
Typically under one second for major providers, and up to a few seconds for slow or greylisting servers. The ms field in every response reports the actual time.
One credit per unique address. Re-checking the same address within 24 hours is free, and new accounts get 100 verifications to test with. See the email verification API page for plans.
Related reading
@Forty email deliverability terms defined in one or two sentences each: bounces, SPF, DKIM, DMARC, spam traps, greylisting, sender reputation, inbox placement and verification.
@Real-time email validation checks an address as it is typed: syntax, typos, disposable domains, role accounts, MX and an optional mailbox check. Here is how it works.
@Gmail, Outlook and Yahoo addresses carry no company signal, so B2B teams segment them. When free addresses are legitimate, how the free and role flags route them, and bulk detection.
Photos: Freepik