Email validations
Check an address before you mail it: is it well-formed, does its domain accept mail, is it a throwaway or a role inbox. Use it on sign-up forms and before importing a list, so bad addresses never reach your bounce rate.
What a validation checks#
A validation is a set of lookups, not a delivery attempt. No mail is sent and the recipient's mail server is never contacted, so a deliverable result means the address is well-formed and its domain publishes MX records — not that this particular mailbox exists. A typo in the local part (jhon@gmail.com) comes back deliverable.
checks.syntaxboolean- The address is well-formed. The request body is already validated as an email address before any check runs (a malformed address is a
400), so through the API this is alwaystrue. checks.mxboolean- The domain has at least one MX record, looked up through public resolvers (8.8.8.8, 1.1.1.1). A domain with only an A record counts as no MX, and so does a lookup that times out or fails.
checks.disposableboolean- The domain is on PostStack's list of about 5,000 disposable/temporary inbox providers (e.g.
mailinator.com). Exact domain match — subdomains of a listed domain are not flagged. checks.roleboolean- The whole local part is a role name such as
info,admin,support,billing,noreplyorpostmaster. Exact match:info.sales@is not a role address. checks.freeboolean- The domain is a free mailbox provider (Gmail, Outlook, Yahoo and similar). Informational only — it never changes
resultorrisk.
Results are cached per address for one hour, so validating the same address again within the hour returns the earlier answer without repeating the DNS lookup — including an undeliverable caused by a DNS timeout.
How the verdict is decided#
The first matching row wins:
| Condition | result | risk |
|---|---|---|
| No MX records (or invalid syntax) | undeliverable | high |
| Disposable domain | risky | high |
| Role address | risky | medium |
| None of the above | deliverable | low |
Endpoints#
Base URL https://api.poststack.dev. Authenticate with Authorization: Bearer sk_live_…; both full-access and sending-access API keys can validate. Rate limits apply per credential; over the limit you get 429 with a Retry-After header in seconds.
Validate an address#
Validate one address and get the verdict in the response. Limited to 60 requests per minute.
https://api.poststack.dev/email-validationsconst validation = await poststack.emailValidations.validate(
'alice@example.com',
);
if (validation.result === 'undeliverable') {
// Ask the user to check the address before you create the account.
}curl -X POST https://api.poststack.dev/email-validations \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{ "email": "alice@example.com" }'{
"email": "alice@example.com"
}{
"email": "alice@example.com",
"result": "deliverable",
"checks": {
"syntax": true,
"mx": true,
"disposable": false,
"role": false,
"free": false
},
"risk": "low"
}Body parameters
emailstringrequired- The address to check. Must be a valid email address, at most 320 characters.
Response fields
emailstring- The address you sent, trimmed and lowercased.
result"deliverable" | "risky" | "undeliverable"- The verdict — see "How the verdict is decided".
risk"low" | "medium" | "high"- How risky sending to the address is, derived from the same checks.
checksobject- The individual checks:
syntax,mx,disposable,role,free(all booleans).
Status codes
| Status | Meaning |
|---|---|
| 200 | The validation result. |
| 400 | email is missing, not an email address, or over 320 characters, e.g. { "error": "email: Invalid email address" }. |
| 401 | Missing, invalid or revoked API key. |
| 403 | No team on the credential, the team is suspended, or an OAuth token lacks the email:send scope ({ "error": "insufficient_scope" }). |
| 429 | More than 60 validations in a minute from this credential. |
| 500 | { "error": "Failed to validate email" }. Safe to retry. |
Validate a batch#
Validate up to 100 addresses in one request. Results come back in the same order as the input. Every entry must be a valid email address — one malformed entry fails the whole request with 400, so strip obvious garbage first. Limited to 10 requests per minute.
https://api.poststack.dev/email-validations/batchconst { results } = await poststack.emailValidations.validateBatch([
'alice@example.com',
'bob@mailinator.com',
'info@example.org',
]);
const keep = results.filter((r) => r.result === 'deliverable');curl -X POST https://api.poststack.dev/email-validations/batch \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"emails": [
"alice@example.com",
"bob@mailinator.com",
"info@example.org"
]
}'{
"emails": [
"alice@example.com",
"bob@mailinator.com",
"info@example.org"
]
}{
"results": [
{
"email": "alice@example.com",
"result": "deliverable",
"checks": { "syntax": true, "mx": true, "disposable": false, "role": false, "free": false },
"risk": "low"
},
{
"email": "bob@mailinator.com",
"result": "risky",
"checks": { "syntax": true, "mx": true, "disposable": true, "role": false, "free": false },
"risk": "high"
},
{
"email": "info@example.org",
"result": "risky",
"checks": { "syntax": true, "mx": true, "disposable": false, "role": true, "free": false },
"risk": "medium"
}
]
}Body parameters
emailsstring[]required- 1–100 addresses, each a valid email address of at most 320 characters.
Response fields
results[].emailstring- The address you sent, trimmed and lowercased.
results[].result"deliverable" | "risky" | "undeliverable"- The verdict — see "How the verdict is decided".
results[].risk"low" | "medium" | "high"- How risky sending to the address is, derived from the same checks.
results[].checksobject- The individual checks:
syntax,mx,disposable,role,free(all booleans).
Status codes
| Status | Meaning |
|---|---|
| 200 | One result per input address, in input order. |
| 400 | emails is missing, empty or over 100 entries, or an entry is not a valid address — the error names its index, e.g. { "error": "emails.3: Invalid email address" }. |
| 401 | Missing, invalid or revoked API key. |
| 403 | No team on the credential, the team is suspended, or an OAuth token lacks the email:send scope ({ "error": "insufficient_scope" }). |
| 429 | More than 10 batch requests in a minute from this credential. |
| 500 | { "error": "Failed to validate emails" }. Safe to retry. |
From other tools#
The same two calls in the Python SDK and the MCP server. You can also validate by hand on the Email validation page in the dashboard.
from poststack import PostStack
client = PostStack(api_key="sk_live_...")
one = client.email_validations.validate("alice@example.com")
many = client.email_validations.validate_batch(["alice@example.com", "bob@mailinator.com"])validate_email { "email": "alice@example.com" }
validate_email_batch { "emails": ["alice@example.com", "bob@mailinator.com"] }Troubleshooting#
A real address came back undeliverable
The domain's MX lookup failed or returned nothing. Check with dig MX example.com. Domains that receive mail only through an A record (no MX) are reported undeliverable, and a DNS timeout is too. Because results are cached for an hour, re-validating straight away returns the same answer.
The whole batch failed with 400
One entry is not a valid address; the error names its position (emails.3 is the fourth). Remove or fix it and resend — nothing from the failed request was validated.
A deliverable address bounced
Validation cannot see whether a mailbox exists, only whether the domain accepts mail. The bounce adds the address to your suppression list if it proves the mailbox is dead.