# Spaw: full documentation
This file concatenates every public Spaw document in Markdown. Spaw (https://spaw.co) is an email verification API: one API key, one credit balance, typed JSON responses that name their data sources. The short index lives at https://spaw.co/llms.txt; the OpenAPI document at https://spaw.co/openapi.json; every page below also exists on its own at the URL it names, with a .md twin.
---
# Getting started
Get started with the Spaw verification API: create an API key, make your first request, and understand how credits work.
Spaw verifies email addresses over a plain REST API and a dashboard, with one credit balance across everything. This guide takes you from a fresh account to your first verified address in a few minutes.
## Create an API key
Every API request authenticates with a key created from the dashboard.
1. **Create an account.** Sign up with an email and password at https://spaw.co/register. Every account gets 10 free credits, renewed at the start of each month — no card required.
2. **Open the API keys page.** Once signed in, choose API keys in the header (https://spaw.co/api-keys). Name the key after where it will live — "Production", "Staging" — so your usage history stays readable.
3. **Copy the key.** The secret key is shown once and stored hashed; copy it before leaving the page. Revoking a key stops its requests immediately.
There is a second kind of key: publishable `pk_` keys, made for the browser form widget (https://spaw.co/docs/email-verification#widget). They can only run email lookups, only from the domains you allow, and stay visible in the dashboard. Keep secret keys on servers; ship publishable keys to pages.
## Your first request
Send the key as a bearer token, JSON in and out. Verifying an address is one POST:
```bash
curl https://spaw.co/api/v1/email \
-H "Authorization: Bearer sk_live_…" \
-d '{"email": "mia@acme.com"}'
```
```json
{
"success": true,
"data": {
"email": "mia@acme.com",
"deliverable": "deliverable",
"reason": null,
"risk_score": 0,
"mx_provider": "google",
"disposable": false,
"…": "21 more fields, see the API reference"
},
"meta": { "credits_used": 1, "credits_remaining": 9, "cache_hit": false }
}
```
Every endpoint answers the same envelope — `{ success, data, meta }` on success, a typed `error.code` on failure — and no verification ever sends mail. The dashboard playground runs the exact same lookups if you want to try addresses before writing code, and six fixed test addresses (deliverable@spaw.test, undeliverable@spaw.test, …) answer canonical verdicts at no cost for your integration tests; see the API reference at https://spaw.co/docs/api.
## Credits & billing
You pay for answers, not attempts, and anything you paid to check once is free to check again for a week.
| Outcome | Credits |
| --- | --- |
| Fresh lookup with a usable answer | 1 credit |
| Invalid input or undeliverable verdict | 0 |
| Repeat of a recent lookup, within 7 days | 0 |
| Rejected or failed request | 0 |
| Monthly free grant, every account | +10 |
The dashboard's Usage page lists every credit movement with the key or channel that spent it, and `GET /api/v1/account` returns the live balance for pre-flight checks.
Need more than the free grant? Buy a credit pack from the Billing page in the dashboard. Packs start at $9 for 1,000 credits, are one-time purchases rather than subscriptions, and never expire. Pricing details: https://spaw.co/pricing.
## Related
- Email verification guide: https://spaw.co/docs/email-verification — verdicts, lists, bulk runs, and the form widget
- API reference: https://spaw.co/docs/api — every endpoint and field
- MCP server: https://spaw.co/docs/mcp — the same tools for AI agents
- OpenAPI document: https://spaw.co/openapi.json
---
# Email verification
How Spaw email verification works: verdicts and the risk score, list validation, bulk runs with webhooks, monitors, the suppression list, and the form widget.
Every lookup runs RFC syntax parsing, live DNS and MX checks, SPF and DMARC records, open disposable and role lists, and a mailbox-level SMTP handshake — without ever sending mail. This guide covers how to read the answer and every way to run verifications at scale.
## Verdicts and the risk score
Each answer leads with one of three verdicts in `deliverable`, and a machine-readable `reason` when it is not a clean pass:
| Verdict | Meaning |
| --- | --- |
| deliverable | The mailbox accepts mail. Nothing in the checks argues against sending. |
| risky | Mail may arrive, but something argues for caution: a typo-squat of a major provider, a disposable domain, a web-only domain with no MX record, a full mailbox, a role inbox, or a catch-all server that accepts everything. |
| undeliverable | Do not send. The syntax is invalid, the username breaks its own provider's rules, the domain has no usable mail server, or the exact mailbox was confirmed missing or disabled. |
Every reason value is explained at https://spaw.co/docs/reasons.
The `risk_score` is not a black box: it is the documented flags added up with published weights, so you can recompute it — or ignore it and branch on the explicit booleans behind it.
| Signal | Points |
| --- | --- |
| disposable domain | +80 |
| known typo-squat domain (gamil.com) | +60 |
| web-only domain, no MX record | +30 |
| role address (info@, support@, …) | +30 |
| catch-all domain | +30 |
| domain registered under 30 days ago | +30 |
| mailbox full | +20 |
| gibberish local part | +20 |
| domain registered under 180 days ago | +10 |
| no SPF record on a business domain | +10 |
| mailbox not SMTP-verified | +10 |
| plus-tag alias | +5 |
An undeliverable verdict is always 100. Scores of 60 and above report `risk_level: "high"`, 30 and above medium, below that low. When mail servers refuse to say whether an inbox exists, the answer degrades honestly — `smtp_checked: false` and null — never to a guess. Where the mailbox could not be settled, catch-all domains above all, `mailbox_confidence` adds a recomputable 0–100 estimate from the signals already in the answer: DMARC, SPF, an established mail provider, an old registration, and a name-shaped address raise it; gibberish, a fresh domain, and a plus-tag lower it. Reported delivery outcomes for the domain outweigh all of that once five or more have been sent in: a bounce rate under 10% adds 15, one of 50% or more takes 20 away.
## From the dashboard
Everything the API does is also a tab on the verification page (https://spaw.co/verify/email), under the same billing rules:
| Tab | What it does |
| --- | --- |
| Single address | One lookup with the full field breakdown and raw response — the same call the API serves. |
| Validate a list | Paste or drop up to 500 addresses; results stream in with verdict filters, CSV export, and a "Monitor list" save. |
| Bulk runs | Upload a CSV with any columns or paste up to 100,000 addresses; runs process in the background, keep your columns in the results, count repeated addresses, and can be cancelled mid-run. |
| Monitors | Saved lists re-verified weekly or monthly, with an email alert when previously deliverable addresses go bad. |
| Suppressions | Your known-bad list: view, import, export, and remove entries. |
| History | Your last verifications with a 30-day quality breakdown, re-verify shortcuts, and CSV export. |
## Bulk runs & webhooks
A bulk run queues up to 100,000 addresses and processes them in the background — upload or paste from the Bulk runs tab, or automate with `POST /api/v1/email/bulk` (https://spaw.co/docs/api/create-bulk-job). Poll the job for progress, then download the result CSV with every response field per row.
Cancelling stops a queued job on the spot and a running one at its next checkpoint; rows already verified stay billed and downloadable. If a webhook URL was set at creation, the finish — completed, failed, or cancelled — is POSTed to it with an `X-Spaw-Signature` HMAC of the raw body, keyed with a secret returned once; transient delivery failures are retried. Verify it, and the exact request shape, in the API reference: https://spaw.co/docs/api/create-bulk-job.
## Monitors
Lists decay: mailboxes are abandoned, domains lapse. Saving a finished list run with "Monitor list" re-verifies it weekly or monthly. The first run only sets the baseline; after that, any address that was deliverable and no longer is triggers an email naming the decayed addresses. Repeats of unchanged answers ride the 7-day cache and the suppression list, so a scheduled run costs far less than the first one. "Run now" re-verifies on demand.
## Suppression list
Every undeliverable verdict adds the address — in normalized form, so tagged and dotted variants count as one mailbox — to your account's suppression list. List, bulk, and monitor runs answer suppressed addresses from the stored verdict, free and marked `meta.suppressed: true`, instead of re-checking them.
Two escape hatches keep the list honest: a single-address re-verify always runs fresh, and a recovered verdict removes the entry; and entries untouched for 90 days are re-verified instead of trusted. You can also import your email provider's bounce export — up to 10,000 addresses per request, from the Suppressions tab or `POST /api/v1/email/suppressions` (https://spaw.co/docs/api/add-suppressions) — so your known-bads are honored from day one without spending a credit.
## Form widget
The form helper verifies addresses as visitors type them into your signup form. Create a publishable key on the API keys page — name it and list the domains it may run from — then include the script and mark your input:
```html
```
On blur it renders "Did you mean…?" corrections and can't-receive-mail notices, and never blocks a submit on its own. Every verdict also fires a `spaw:result` event with the full response, so your own policy is one listener away:
```js
addEventListener('spaw:result', (event) => {
if (event.detail.disposable) {
// e.g. disable the submit button
}
});
```
Lookups bill your account and responses carry no meta block, so visitors never see your balance. Prefer to keep even the publishable key off the page? Point `data-endpoint` at a small server-side proxy that forwards to the API with your secret key instead.
Bots that script your form can spend your credits. To keep them out, create a Cloudflare Turnstile widget, store its site key and secret on the publishable key when you create it, and add the site key to the script tag:
```html
```
The helper loads Turnstile once, renders it next to the field only when Cloudflare asks for an interaction, and obtains a fresh token before every lookup. The browser endpoint refuses lookups without a confirmed token with `403 TURNSTILE_FAILED` before any credit is spent. If Turnstile cannot load, the helper skips the lookup rather than blocking the form. The attribute also works on the input itself, and a proxy endpoint simply receives the token as `turnstile_token`.
## Related
- API reference: https://spaw.co/docs/api — every endpoint, field, and error code
- Verify an email address: https://spaw.co/docs/api/verify-email
- Browser endpoint for publishable keys: https://spaw.co/docs/api/verify-email-public
- Verdict reasons: https://spaw.co/docs/reasons · Error codes: https://spaw.co/docs/errors
- Getting started: https://spaw.co/docs
---
# MCP server
Give AI agents Spaw's verification tools over the Model Context Protocol: one endpoint, your existing API key, the same billing.
Spaw speaks the Model Context Protocol (https://modelcontextprotocol.io), so AI agents — Claude Code, Cursor, or anything MCP-capable — can verify addresses and inspect domains as tools. Same API keys, same billing, same answers; agent usage shows up on your Usage page as `mcp · key name`.
## Connect a client
The server lives at `https://spaw.co/mcp` over streamable HTTP, authenticated with a secret API key as a bearer token. In Claude Code, one command:
```bash
claude mcp add --transport http spaw https://spaw.co/mcp \
--header "Authorization: Bearer sk_live_…"
```
For clients configured with JSON:
```json
{
"mcpServers": {
"spaw": {
"type": "http",
"url": "https://spaw.co/mcp",
"headers": { "Authorization": "Bearer sk_live_…" }
}
}
}
```
Use a secret key, never a publishable one — the agent runs server-side on your behalf. Requests share the API's rate limit of 5 per second per account.
## Tools
Four tools, mirroring the REST endpoints. An agent that runs out of credits gets a clear tool error, not a half answer.
| Tool | Arguments | Behavior |
| --- | --- | --- |
| verify-email | email | One full verification — verdict, risk score, and every documented field. 1 credit for a deliverable or risky verdict; undeliverable and 7-day repeats free. |
| verify-email-batch | emails (≤50) | Compact per-address rows under the same billing. Suppressed addresses answer free; running dry mid-batch returns the paid partial results. |
| domain-intelligence | domain | MX, provider, SPF/DMARC, and disposable/free-provider lists for a whole domain. 1 credit when it accepts mail; dead or invalid domains free. |
| account-balance | — | The current credit balance and monthly grant. Free — useful before a batch. |
Field meanings and billing details are the same as the REST API — see the API reference (https://spaw.co/docs/api) and the email verification guide (https://spaw.co/docs/email-verification).
## Related
- API reference: https://spaw.co/docs/api
- Verify an email address over REST: https://spaw.co/docs/api/verify-email
- Email verification guide: https://spaw.co/docs/email-verification
- Getting started: https://spaw.co/docs
---
# Spaw API reference
Base URL: `https://spaw.co/api/v1`. Machine-readable description: https://spaw.co/openapi.json (also /openapi.yaml).
Spaw verifies email addresses over plain REST: JSON in and out, typed errors, and one credit balance across every endpoint. Every lookup runs RFC syntax parsing, live DNS and MX checks, SPF and DMARC records, open disposable and role lists, and a mailbox-level SMTP handshake. No verification ever sends mail.
## Authentication
Every request carries a secret API key as a bearer token. Create and revoke keys on the dashboard's API keys page; a key is shown once at creation and stored hashed.
```http
Authorization: Bearer sk_live_…
Content-Type: application/json
```
There are two kinds of keys. Secret `sk_live_` keys authenticate servers and unlock the full API. Publishable `pk_` keys are safe in browser page source: they can only run single email lookups through `POST /api/v1/email/public`, and only from the domains they are locked to.
## Response envelope
Successful lookups answer `{ "success": true, "data": …, "meta": … }`. The `meta` block carries `credits_used`, `credits_remaining`, `cache_hit`, and `request_id` (email lookups add `suppressed`, and `settling` when a callback was requested). Failures answer `{ "success": false, "error": { "code", "message", "request_id" } }` with a stable code to branch on; validation failures add `error.errors` keyed by field. Every response also carries an `X-Request-Id` header that matches every log line the request produced. Quote it when writing to support.
## Billing
You pay for answers, not attempts, and anything you paid to check once is free to check again for seven days.
| Outcome | Credits |
| --- | --- |
| Fresh lookup with a usable answer (deliverable or risky verdict, a domain that accepts mail, a valid phone number, a located IP) | 1 |
| Invalid input or an undeliverable verdict | 0 |
| Repeat of a recent lookup, within 7 days | 0 |
| Rejected or failed request | 0 |
| Monthly free grant, every account | +10 |
Credit packs are one-time purchases from the dashboard's Billing page (from $9 for 1,000 credits) and never expire. `GET /api/v1/account` returns the live balance for pre-flight checks.
## Rate limits
Every key may make 5 requests per second; a batch or bulk call counts as one request. Over the limit, the API answers `429 RATE_LIMITED` with a `Retry-After` header.
## Test addresses
Six fixed addresses on the reserved `spaw.test` domain always answer the same canonical result, cost no credits, touch no DNS or mail server, and never appear in your history or suppression list. A plus-tag (`deliverable+ci@spaw.test`) works too, on every endpoint including batch, bulk, and the browser endpoint.
| Address | Answer |
| --- | --- |
| deliverable@spaw.test | deliverable · mailbox confirmed, risk_score 0 |
| unverified@spaw.test | deliverable · smtp_checked false, smtp_reason greylisted, mailbox_confidence set |
| catch-all@spaw.test | risky · reason catch_all, mailbox_exists null |
| disposable@spaw.test | risky · reason disposable, risk_score 90 |
| role@spaw.test | risky · reason role |
| undeliverable@spaw.test | undeliverable · reason mailbox_not_found, smtp_reason no_mailbox |
## Data sources
Every lookup's `data.sources` array names the dataset and version behind the answer (`rfc-5322-syntax`, `live-dns`, `disposable-domains`, `role-addresses`, `free-provider-domains`, `typo-domains`, `provider-username-rules`, `mx-provider-patterns`, `rdap-registration`, the mailbox probe's provider and date, `delivery-feedback`, `suppression-list`, or `test-addresses`), so an answer can always be audited.
## Endpoints
| Method | Path | Summary |
| --- | --- | --- |
| POST | `/api/v1/email` | [Verify an email address](https://spaw.co/docs/api/verify-email) |
| POST | `/api/v1/email/batch` | [Verify up to 50 addresses in one call](https://spaw.co/docs/api/verify-email-batch) |
| POST | `/api/v1/email/public` | [Verify an address from the browser with a publishable key](https://spaw.co/docs/api/verify-email-public) |
| GET | `/api/v1/email/domain/{domain}` | [Inspect a domain's mail setup](https://spaw.co/docs/api/domain-intelligence) |
| POST | `/api/v1/email/bulk` | [Queue a bulk verification run](https://spaw.co/docs/api/create-bulk-job) |
| GET | `/api/v1/email/bulk/{jobId}` | [Poll a bulk job](https://spaw.co/docs/api/get-bulk-job) |
| POST | `/api/v1/email/bulk/{jobId}/cancel` | [Cancel a bulk job](https://spaw.co/docs/api/cancel-bulk-job) |
| GET | `/api/v1/email/bulk/{jobId}/results` | [Download a bulk job's results as CSV](https://spaw.co/docs/api/download-bulk-results) |
| GET | `/api/v1/email/suppressions` | [List the suppression list](https://spaw.co/docs/api/list-suppressions) |
| POST | `/api/v1/email/suppressions` | [Import known-bad addresses](https://spaw.co/docs/api/add-suppressions) |
| DELETE | `/api/v1/email/suppressions/{suppressionId}` | [Remove a suppression entry](https://spaw.co/docs/api/remove-suppression) |
| POST | `/api/v1/email/feedback` | [Report delivery outcomes](https://spaw.co/docs/api/report-delivery-feedback) |
| GET | `/api/v1/email/feedback/summary` | [Measured accuracy](https://spaw.co/docs/api/feedback-summary) |
| POST | `/api/v1/email/feedback/{provider}/{feedbackKey}` | [Receive bounce webhooks from an email provider](https://spaw.co/docs/api/esp-bounce-webhook) |
| GET | `/api/v1/account` | [Read the account and credit balance](https://spaw.co/docs/api/get-account) |
| POST | `/api/v1/phone` | [Validate and enrich a phone number](https://spaw.co/docs/api/validate-phone) |
| POST | `/api/v1/ip` | [Locate and risk-score an IP address](https://spaw.co/docs/api/lookup-ip) |
Every error code is explained at https://spaw.co/docs/errors and every verdict reason at https://spaw.co/docs/reasons.
---
# Verify an email address
`POST /api/v1/email`
- Authentication: Secret API key as a bearer token
- Billing: 1 credit for a fresh deliverable or risky verdict; undeliverable verdicts, invalid input, 7-day repeats and test addresses are free.
- Group: Email
Runs the full pipeline on one address and answers with a verdict, a machine-readable reason, an auditable 0–100 risk score, and 27 named fields. The checks run in order: address extraction (display names, `mailto:` prefixes, spreadsheet quotes and invisible characters are stripped), RFC 5322 syntax, live MX resolution including the null-MX and implicit-MX rules, the disposable, role, free-provider and typo-squat lists, provider-specific username rules, SPF and DMARC records, the domain's registration age from RDAP, and finally a mailbox-level SMTP handshake that ends before any message is transmitted.
The mailbox probe runs only when the domain accepts mail and no free signal already settled the answer, so a disposable domain or a typo-squat never reaches the mail server. Confirmed mailbox answers are shared across customers for up to a week, which is why `smtp_checked_at` can predate the lookup. When mail servers refuse to say whether an inbox exists (greylisting, timeouts), the answer degrades honestly to `smtp_checked: false` and `mailbox_exists: null`, plus a recomputable `mailbox_confidence` estimate.
A single lookup never consults your suppression list: an explicit re-verify is the only way an address earns its way off it. The batch, bulk and monitor paths do honor it.
### Settling an unverified answer later
Pass `callback_url` and `callback_secret` and an unverified answer is re-checked after 5 and 20 minutes. The settled verdict is POSTed to the callback as `{ "event": "email.settled", "request_id", "settled", "attempt", "data", "meta" }`, signed with the HMAC-SHA256 of the exact raw body in the `X-Spaw-Signature` header. The response to this request then carries `meta.settling: true`. Re-checks are repeats of a charged lookup, so they cost nothing; `settled` is false when the last re-check still could not say.
## Request body
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `email` | string | yes | The address to verify. Wrappers people paste (a display name, a `mailto:` prefix, quotes, trailing punctuation) are stripped before checking. At most 254 characters. |
| `callback_url` | string | null | no | An https URL. When the answer comes back unverified (greylisted, timed out, provider unavailable), the address is re-checked after 5 and 20 minutes and the settled verdict is POSTed here. At most 2,048 characters. |
| `callback_secret` | string | null | no | Required with `callback_url`. Signs the callback body — `X-Spaw-Signature` is the HMAC-SHA256 of the exact raw body, like the bulk webhook. 16 to 128 characters. |
## Example request
```bash
curl -X POST https://spaw.co/api/v1/email \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"email": "mia@acme.com"
}'
```
## Responses
### 200 — The verdict and every field, whether or not the address is deliverable.
```json
{
"success": true,
"data": {
"email": "mia@acme.com",
"normalized_email": "mia@acme.com",
"is_alias": false,
"is_gibberish": false,
"deliverable": "deliverable",
"reason": null,
"risk_score": 0,
"risk_level": "low",
"syntax_valid": true,
"domain": "acme.com",
"mx_found": true,
"mx_implicit": false,
"mx_provider": "google",
"has_spf": true,
"dmarc_policy": "reject",
"domain_registered_at": "1998-03-12",
"domain_age_days": 10402,
"disposable": false,
"role": false,
"free_provider": false,
"smtp_checked": true,
"smtp_checked_at": "2026-09-03T10:12:44+00:00",
"mailbox_exists": true,
"catch_all": false,
"smtp_reason": null,
"mailbox_confidence": null,
"did_you_mean": null,
"sources": [
{
"dataset": "rfc-5322-syntax",
"version": "2026-09-03"
},
{
"dataset": "live-dns",
"version": "2026-09-03"
},
{
"dataset": "disposable-domains",
"version": "2026-09-01"
},
{
"dataset": "mx-provider-patterns",
"version": "2026-08-30"
},
{
"dataset": "mailbox-smtp",
"version": "2026-09-03"
}
]
},
"meta": {
"credits_used": 1,
"credits_remaining": 9,
"cache_hit": false,
"suppressed": false,
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 401 — The key is missing, malformed, or revoked.
```json
{
"success": false,
"error": {
"code": "UNAUTHENTICATED",
"message": "Provide a valid API key as a bearer token.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 402 — The balance is empty. The lookup did not run.
```json
{
"success": false,
"error": {
"code": "INSUFFICIENT_CREDITS",
"message": "Your credit balance is empty.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 422 — The request body could not be validated; `error.errors` lists the fields.
```json
{
"success": false,
"error": {
"code": "VALIDATION_FAILED",
"message": "The email field is required.",
"errors": {
"email": [
"The email field is required."
]
},
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 429 — Over 5 requests per second for the key. Retry after the limit resets.
```json
{
"success": false,
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Retry after the limit resets.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
## Error codes
- `UNAUTHENTICATED` — https://spaw.co/docs/errors/UNAUTHENTICATED
- `VALIDATION_FAILED` — https://spaw.co/docs/errors/VALIDATION_FAILED
- `INSUFFICIENT_CREDITS` — https://spaw.co/docs/errors/INSUFFICIENT_CREDITS
- `RATE_LIMITED` — https://spaw.co/docs/errors/RATE_LIMITED
---
Canonical page: https://spaw.co/docs/api/verify-email · OpenAPI document: https://spaw.co/openapi.json · All endpoints: https://spaw.co/docs/api
---
# Verify up to 50 addresses in one call
`POST /api/v1/email/batch`
- Authentication: Secret API key as a bearer token
- Billing: Each address bills like a single lookup — 1 credit for a fresh deliverable or risky verdict, everything else free — and the call stops cleanly where the balance ends.
- Group: Email
Verifies a list synchronously under the exact single-lookup billing rules. Every address is prepared first, then all the mailbox probes that are still needed are sent to the verification provider together, so a full batch takes about as long as a handful of single lookups. `data.results[]` mirrors the single-address response per item, in input order, and each item's `meta` carries `credits_used`, `cache_hit`, and `suppressed`.
Addresses on your suppression list are served from the stored verdict at no cost and marked `suppressed: true`. Repeats inside the list are verified once. The list is walked in order and stops where the balance would run out: running out of credits mid-batch returns the paid partial results with `meta.stopped_reason: "insufficient_credits"` and `processed` lower than `requested`, never discarding billed work. Only a batch whose first lookup is refused answers the typed `402`.
A batch counts as one request against the rate limit.
## Request body
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `emails` | string[] | yes | 1 to 50 addresses; each item at most 254 characters. |
## Example request
```bash
curl -X POST https://spaw.co/api/v1/email/batch \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"emails": [
"mia@acme.com",
"info@example.org",
"deliverable@spaw.test"
]
}'
```
## Responses
### 200 — One result per processed address, plus the batch totals.
```json
{
"success": true,
"data": {
"results": [
{
"data": {
"email": "mia@acme.com",
"deliverable": "deliverable",
"reason": null,
"risk_score": 0,
"risk_level": "low"
},
"meta": {
"credits_used": 1,
"cache_hit": false,
"suppressed": false
}
},
{
"data": {
"email": "info@example.org",
"deliverable": "risky",
"reason": "role",
"risk_score": 30,
"risk_level": "medium"
},
"meta": {
"credits_used": 1,
"cache_hit": false,
"suppressed": false
}
},
{
"data": {
"email": "deliverable@spaw.test",
"deliverable": "deliverable",
"reason": null,
"risk_score": 0,
"risk_level": "low"
},
"meta": {
"credits_used": 0,
"cache_hit": false,
"suppressed": false
}
}
]
},
"meta": {
"requested": 3,
"processed": 3,
"credits_used": 2,
"credits_remaining": 7,
"stopped_reason": null,
"request_id": "req_01m1kgdrtqdvwnks99vfgx2rcw"
}
}
```
### 401 — The key is missing, malformed, or revoked.
```json
{
"success": false,
"error": {
"code": "UNAUTHENTICATED",
"message": "Provide a valid API key as a bearer token.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 402 — The balance is empty. The lookup did not run.
```json
{
"success": false,
"error": {
"code": "INSUFFICIENT_CREDITS",
"message": "Your credit balance is empty.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 422 — The request body could not be validated; `error.errors` lists the fields.
```json
{
"success": false,
"error": {
"code": "VALIDATION_FAILED",
"message": "The email field is required.",
"errors": {
"email": [
"The email field is required."
]
},
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 429 — Over 5 requests per second for the key. Retry after the limit resets.
```json
{
"success": false,
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Retry after the limit resets.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
## Error codes
- `UNAUTHENTICATED` — https://spaw.co/docs/errors/UNAUTHENTICATED
- `VALIDATION_FAILED` — https://spaw.co/docs/errors/VALIDATION_FAILED
- `INSUFFICIENT_CREDITS` — https://spaw.co/docs/errors/INSUFFICIENT_CREDITS
- `RATE_LIMITED` — https://spaw.co/docs/errors/RATE_LIMITED
---
Canonical page: https://spaw.co/docs/api/verify-email-batch · OpenAPI document: https://spaw.co/openapi.json · All endpoints: https://spaw.co/docs/api
---
# Verify an address from the browser with a publishable key
`POST /api/v1/email/public`
- Authentication: Publishable key in the body, checked against the browser Origin header
- Billing: Bills the key owner like a single lookup; capped by the key's daily credit cap when one is set.
- Group: Email
The endpoint behind the form widget (`https://spaw.co/spaw-form.js`). It is authenticated by a publishable `pk_` key in the body plus the browser's `Origin` header, which must match one of the domains the key is locked to. A missing `Origin` is rejected on purpose: servers use a secret key and `POST /api/v1/email` instead.
The lookup bills the key's owner under the normal rules and answers `{ "success", "data" }` with **no meta block**, so page visitors never see the owner's balance. Because the key sits in page source, give it a daily credit cap in the dashboard: once the cap is spent the endpoint answers `429 KEY_SPEND_CAP_REACHED` until the next day. Free answers (undeliverable, cache hits, test addresses) never count toward the cap.
For forms open to the public, store a Cloudflare Turnstile site key and secret on the publishable key. The endpoint then requires a confirmed `turnstile_token` with every lookup and answers `403 TURNSTILE_FAILED` without one, before any credit is spent. Tokens are single-use. The form widget obtains a fresh token per lookup when the script tag carries `data-turnstile-site-key`.
Throttled at 20 requests per minute per IP.
## Parameters
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Origin` | header | string | yes | Sent by browsers automatically. Its host must be on the key's allowed-domain list. |
## Request body
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `key` | string | yes | A publishable key, which starts with `pk_`. At most 64 characters. |
| `email` | string | yes | The address to verify. At most 254 characters. |
| `turnstile_token` | string | null | no | Required when the key carries a Turnstile pair. A single-use token from the Turnstile widget on the page. At most 2,048 characters. |
## Example request
```bash
curl -X POST https://spaw.co/api/v1/email/public \
-H "Origin: https://www.example.com" \
-H "Content-Type: application/json" \
-d '{
"key": "pk_live_…",
"email": "mia@acme.com"
}'
```
## Responses
### 200 — The same fields as `POST /api/v1/email`, without a meta block.
```json
{
"success": true,
"data": {
"email": "mia@acme.com",
"deliverable": "deliverable",
"reason": null,
"risk_score": 0,
"risk_level": "low",
"did_you_mean": null
}
}
```
### 401 — The publishable key does not exist or was revoked.
```json
{
"success": false,
"error": {
"code": "INVALID_PUBLISHABLE_KEY",
"message": "That publishable key does not exist or was revoked.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 402 — The balance is empty. The lookup did not run.
```json
{
"success": false,
"error": {
"code": "INSUFFICIENT_CREDITS",
"message": "Your credit balance is empty.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 403 — The page's origin is not on the key's allowed-domain list (`ORIGIN_NOT_ALLOWED`), or the key requires a Turnstile token that was missing or not confirmed (`TURNSTILE_FAILED`).
```json
{
"success": false,
"error": {
"code": "ORIGIN_NOT_ALLOWED",
"message": "This publishable key cannot be used from this origin.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 422 — The request body could not be validated; `error.errors` lists the fields.
```json
{
"success": false,
"error": {
"code": "VALIDATION_FAILED",
"message": "The email field is required.",
"errors": {
"email": [
"The email field is required."
]
},
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 429 — The key has spent its owner's daily credit cap (`KEY_SPEND_CAP_REACHED`), or the IP exceeded 20 requests per minute (`RATE_LIMITED`).
```json
{
"success": false,
"error": {
"code": "KEY_SPEND_CAP_REACHED",
"message": "This publishable key has reached its daily credit cap. The counter resets each day.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
## Error codes
- `INVALID_PUBLISHABLE_KEY` — https://spaw.co/docs/errors/INVALID_PUBLISHABLE_KEY
- `ORIGIN_NOT_ALLOWED` — https://spaw.co/docs/errors/ORIGIN_NOT_ALLOWED
- `TURNSTILE_FAILED` — https://spaw.co/docs/errors/TURNSTILE_FAILED
- `KEY_SPEND_CAP_REACHED` — https://spaw.co/docs/errors/KEY_SPEND_CAP_REACHED
- `VALIDATION_FAILED` — https://spaw.co/docs/errors/VALIDATION_FAILED
- `INSUFFICIENT_CREDITS` — https://spaw.co/docs/errors/INSUFFICIENT_CREDITS
- `RATE_LIMITED` — https://spaw.co/docs/errors/RATE_LIMITED
---
Canonical page: https://spaw.co/docs/api/verify-email-public · OpenAPI document: https://spaw.co/openapi.json · All endpoints: https://spaw.co/docs/api
---
# Inspect a domain's mail setup
`GET /api/v1/email/domain/{domain}`
- Authentication: Secret API key as a bearer token
- Billing: 1 credit only when the domain accepts mail (`mx_found` true) and the lookup is fresh; invalid or dead domains and 7-day repeats are free.
- Group: Domain
The domain-level signals of an email lookup, answered once for a whole domain: whether it can receive mail (MX records, or the RFC 5321 implicit fallback), who runs its mail, whether it publishes SPF and which DMARC policy, when it was registered, whether it is on the disposable or free-provider lists, and whether any address there has answered catch-all within the last week.
Invalid input is an answer, not a validation error: a string that is not a hostname answers `200` with `valid: false` and every other field null. A domain with no usable mail server is free. The per-account 7-day repeat cache applies.
## Parameters
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `domain` | path | string | yes | The domain to inspect. Lowercased and converted to punycode before checking. At most 254 characters. |
## Example request
```bash
curl https://spaw.co/api/v1/email/domain/acme.com \
-H "Authorization: Bearer sk_live_…"
```
## Responses
### 200 — The domain's signals; `valid: false` for input that is not a hostname.
```json
{
"success": true,
"data": {
"domain": "acme.com",
"valid": true,
"mx_found": true,
"mx_implicit": false,
"catch_all": null,
"mx_provider": "google",
"has_spf": true,
"dmarc_policy": "reject",
"domain_registered_at": "1998-03-12",
"domain_age_days": 10402,
"disposable": false,
"free_provider": false,
"sources": [
{
"dataset": "live-dns",
"version": "2026-09-03"
},
{
"dataset": "disposable-domains",
"version": "2026-09-01"
},
{
"dataset": "free-provider-domains",
"version": "2026-09-01"
},
{
"dataset": "mx-provider-patterns",
"version": "2026-08-30"
},
{
"dataset": "rdap-registration",
"version": "2026-09-03"
}
]
},
"meta": {
"credits_used": 1,
"credits_remaining": 8,
"cache_hit": false,
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 401 — The key is missing, malformed, or revoked.
```json
{
"success": false,
"error": {
"code": "UNAUTHENTICATED",
"message": "Provide a valid API key as a bearer token.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 402 — The balance is empty. The lookup did not run.
```json
{
"success": false,
"error": {
"code": "INSUFFICIENT_CREDITS",
"message": "Your credit balance is empty.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 429 — Over 5 requests per second for the key. Retry after the limit resets.
```json
{
"success": false,
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Retry after the limit resets.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
## Error codes
- `UNAUTHENTICATED` — https://spaw.co/docs/errors/UNAUTHENTICATED
- `INSUFFICIENT_CREDITS` — https://spaw.co/docs/errors/INSUFFICIENT_CREDITS
- `RATE_LIMITED` — https://spaw.co/docs/errors/RATE_LIMITED
---
Canonical page: https://spaw.co/docs/api/domain-intelligence · OpenAPI document: https://spaw.co/openapi.json · All endpoints: https://spaw.co/docs/api
---
# Queue a bulk verification run
`POST /api/v1/email/bulk`
- Authentication: Secret API key as a bearer token
- Billing: Each row bills like a single lookup as it is processed; repeats inside the run and 7-day repeats are free.
- Group: Bulk
Queues up to 100,000 addresses and processes them in the background in chunks, with every chunk's mailbox probes sent concurrently. Creation answers `202` with the job's id and status. Poll `GET /api/v1/email/bulk/{jobId}` for progress, then download the result CSV from `GET /api/v1/email/bulk/{jobId}/results`. Jobs and their files are kept for 30 days.
Rows repeating an address already in the list are verified once and counted in `duplicate_count`. Addresses at a domain already known to be catch-all, addresses on your suppression list, and addresses your account reported delivered recently answer without a probe. A run that exhausts the balance stops with `stopped_reason: "insufficient_credits"` and keeps everything verified so far.
### Completion webhook
With a `webhook_url`, the finish (`completed`, `failed`, or `cancelled`) is POSTed there as `{ "event": "bulk_email_job.finished", "job": { "id", "status", "total", "processed", "deliverable", "risky", "undeliverable", "credits_used", "stopped_reason", "finished_at" } }`. The `X-Spaw-Signature` header is the HMAC-SHA256 of the exact raw body keyed with `webhook_secret`, which this response returns once and never again. Connection errors and 5xx answers are retried three times with a short backoff.
```php
$expected = hash_hmac('sha256', $request->getContent(), $secret);
abort_unless(hash_equals($expected, $request->header('X-Spaw-Signature')), 401);
```
## Request body
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `emails` | string[] | yes | 1 to 100,000 addresses; each item at most 254 characters. |
| `webhook_url` | string | null | no | An https URL to POST when the job finishes. The response includes the signing secret — shown once, never again. At most 2,048 characters. |
## Example request
```bash
curl -X POST https://spaw.co/api/v1/email/bulk \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"emails": [
"mia@acme.com",
"info@example.org"
],
"webhook_url": "https://www.example.com/hooks/spaw"
}'
```
## Responses
### 202 — The job was queued.
```json
{
"success": true,
"data": {
"job": {
"id": 512,
"status": "queued",
"total": 2,
"duplicate_count": 0,
"processed": 0,
"deliverable": 0,
"risky": 0,
"undeliverable": 0,
"credits_used": 0,
"stopped_reason": null,
"cancel_requested": false,
"webhook_status": null,
"created_at": "2026-09-03T10:12:44+00:00",
"finished_at": null,
"webhook_secret": "8fJ2…40 characters…Qk1"
}
}
}
```
### 401 — The key is missing, malformed, or revoked.
```json
{
"success": false,
"error": {
"code": "UNAUTHENTICATED",
"message": "Provide a valid API key as a bearer token.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 422 — The request body could not be validated; `error.errors` lists the fields.
```json
{
"success": false,
"error": {
"code": "VALIDATION_FAILED",
"message": "The email field is required.",
"errors": {
"email": [
"The email field is required."
]
},
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 429 — Over 5 requests per second for the key. Retry after the limit resets.
```json
{
"success": false,
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Retry after the limit resets.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
## Error codes
- `UNAUTHENTICATED` — https://spaw.co/docs/errors/UNAUTHENTICATED
- `VALIDATION_FAILED` — https://spaw.co/docs/errors/VALIDATION_FAILED
- `RATE_LIMITED` — https://spaw.co/docs/errors/RATE_LIMITED
---
Canonical page: https://spaw.co/docs/api/create-bulk-job · OpenAPI document: https://spaw.co/openapi.json · All endpoints: https://spaw.co/docs/api
---
# Poll a bulk job
`GET /api/v1/email/bulk/{jobId}`
- Authentication: Secret API key as a bearer token
- Billing: Free.
- Group: Bulk
The job's current status and running counts. `status` moves from `queued` to `processing` and ends in `completed`, `failed`, or `cancelled`; `processed` and the verdict counts advance as chunks finish. A job that belongs to another account answers `404`.
## Parameters
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `jobId` | path | integer | yes | The id returned at creation. |
## Example request
```bash
curl https://spaw.co/api/v1/email/bulk/512 \
-H "Authorization: Bearer sk_live_…"
```
## Responses
### 200 — The job.
```json
{
"success": true,
"data": {
"job": {
"id": 512,
"status": "processing",
"total": 2000,
"duplicate_count": 12,
"processed": 900,
"deliverable": 610,
"risky": 140,
"undeliverable": 150,
"credits_used": 750,
"stopped_reason": null,
"cancel_requested": false,
"webhook_status": null,
"created_at": "2026-09-03T10:12:44+00:00",
"finished_at": null
}
}
}
```
### 401 — The key is missing, malformed, or revoked.
```json
{
"success": false,
"error": {
"code": "UNAUTHENTICATED",
"message": "Provide a valid API key as a bearer token.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 404 — No such record on this account.
```json
{
"message": "Not Found"
}
```
### 429 — Over 5 requests per second for the key. Retry after the limit resets.
```json
{
"success": false,
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Retry after the limit resets.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
## Error codes
- `UNAUTHENTICATED` — https://spaw.co/docs/errors/UNAUTHENTICATED
- `RATE_LIMITED` — https://spaw.co/docs/errors/RATE_LIMITED
---
Canonical page: https://spaw.co/docs/api/get-bulk-job · OpenAPI document: https://spaw.co/openapi.json · All endpoints: https://spaw.co/docs/api
---
# Cancel a bulk job
`POST /api/v1/email/bulk/{jobId}/cancel`
- Authentication: Secret API key as a bearer token
- Billing: Free.
- Group: Bulk
Asks a queued or running job to stop. A queued job is cancelled on the spot; a running one stops at its next checkpoint and keeps the rows already written downloadable through the results endpoint. Rows already verified stay billed. The completion webhook fires with status `cancelled` just as it does for `completed` and `failed`. A job that already reached a terminal status answers `409 JOB_ALREADY_FINISHED`.
## Parameters
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `jobId` | path | integer | yes | The id returned at creation. |
## Example request
```bash
curl -X POST https://spaw.co/api/v1/email/bulk/512/cancel \
-H "Authorization: Bearer sk_live_…"
```
## Responses
### 200 — The job, with `cancel_requested` set.
```json
{
"success": true,
"data": {
"job": {
"id": 512,
"status": "processing",
"total": 2000,
"duplicate_count": 12,
"processed": 900,
"deliverable": 610,
"risky": 140,
"undeliverable": 150,
"credits_used": 750,
"stopped_reason": null,
"cancel_requested": true,
"webhook_status": null,
"created_at": "2026-09-03T10:12:44+00:00",
"finished_at": null
}
}
}
```
### 401 — The key is missing, malformed, or revoked.
```json
{
"success": false,
"error": {
"code": "UNAUTHENTICATED",
"message": "Provide a valid API key as a bearer token.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 404 — No such record on this account.
```json
{
"message": "Not Found"
}
```
### 409 — The job already finished.
```json
{
"success": false,
"error": {
"code": "JOB_ALREADY_FINISHED",
"message": "The job has already finished and cannot be cancelled.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 429 — Over 5 requests per second for the key. Retry after the limit resets.
```json
{
"success": false,
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Retry after the limit resets.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
## Error codes
- `UNAUTHENTICATED` — https://spaw.co/docs/errors/UNAUTHENTICATED
- `JOB_ALREADY_FINISHED` — https://spaw.co/docs/errors/JOB_ALREADY_FINISHED
- `RATE_LIMITED` — https://spaw.co/docs/errors/RATE_LIMITED
---
Canonical page: https://spaw.co/docs/api/cancel-bulk-job · OpenAPI document: https://spaw.co/openapi.json · All endpoints: https://spaw.co/docs/api
---
# Download a bulk job's results as CSV
`GET /api/v1/email/bulk/{jobId}/results`
- Authentication: Secret API key as a bearer token
- Billing: Free.
- Group: Bulk
Streams the run's result file: a finished job's full results, or the partial file a mid-run cancellation kept. The file starts with the input's own columns (for an API list, a single `email` column; for a dashboard upload, every column of the uploaded file), followed by `verified_email` and the verdict fields, one row per input row in the original order:
`verified_email, deliverable, reason, risk_score, risk_level, normalized_email, is_alias, is_gibberish, disposable, role, free_provider, mx_found, mx_implicit, mx_provider, has_spf, dmarc_policy, domain_registered_at, domain_age_days, smtp_checked, smtp_checked_at, mailbox_exists, catch_all, smtp_reason, mailbox_confidence, did_you_mean, credits_used, cache_hit, suppressed`
Add `?variant=deliverable` (or `risky`, `undeliverable`) to download only those rows. A job still running answers `409 RESULTS_NOT_READY`; a job cancelled before any address was processed answers `409 JOB_CANCELLED`.
## Parameters
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `jobId` | path | integer | yes | The id returned at creation. |
| `variant` | query | string | no | Which rows to include. One of: full, deliverable, risky, undeliverable. Default: full. |
## Example request
```bash
curl https://spaw.co/api/v1/email/bulk/512/results \
-H "Authorization: Bearer sk_live_…"
```
## Responses
### 200 — The CSV, as an attachment named `spaw-bulk-{jobId}.csv` (with `-{variant}` appended for a filtered download).
```csv
email,verified_email,deliverable,reason,risk_score,risk_level,normalized_email,is_alias,is_gibberish,disposable,role,free_provider,mx_found,mx_implicit,mx_provider,has_spf,dmarc_policy,domain_registered_at,domain_age_days,smtp_checked,smtp_checked_at,mailbox_exists,catch_all,smtp_reason,mailbox_confidence,did_you_mean,credits_used,cache_hit,suppressed
mia@acme.com,mia@acme.com,deliverable,,0,low,mia@acme.com,0,0,0,0,0,1,0,google,1,reject,1998-03-12,10402,1,2026-09-03T10:12:44+00:00,1,0,,,,1,0,0
```
### 401 — The key is missing, malformed, or revoked.
```json
{
"success": false,
"error": {
"code": "UNAUTHENTICATED",
"message": "Provide a valid API key as a bearer token.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 404 — No such record on this account.
```json
{
"message": "Not Found"
}
```
### 409 — The results are not available yet (`RESULTS_NOT_READY`) or never will be (`JOB_CANCELLED`).
```json
{
"success": false,
"error": {
"code": "RESULTS_NOT_READY",
"message": "The job has not finished yet. Poll its status until it reports completed.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 422 — The `variant` query parameter is not one of the allowed values.
```json
{
"success": false,
"error": {
"code": "VALIDATION_FAILED",
"message": "variant must be one of: full, deliverable, risky, undeliverable.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 429 — Over 5 requests per second for the key. Retry after the limit resets.
```json
{
"success": false,
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Retry after the limit resets.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
## Error codes
- `UNAUTHENTICATED` — https://spaw.co/docs/errors/UNAUTHENTICATED
- `RESULTS_NOT_READY` — https://spaw.co/docs/errors/RESULTS_NOT_READY
- `JOB_CANCELLED` — https://spaw.co/docs/errors/JOB_CANCELLED
- `VALIDATION_FAILED` — https://spaw.co/docs/errors/VALIDATION_FAILED
- `RATE_LIMITED` — https://spaw.co/docs/errors/RATE_LIMITED
---
Canonical page: https://spaw.co/docs/api/download-bulk-results · OpenAPI document: https://spaw.co/openapi.json · All endpoints: https://spaw.co/docs/api
---
# List the suppression list
`GET /api/v1/email/suppressions`
- Authentication: Secret API key as a bearer token
- Billing: Free.
- Group: Suppressions
The account's suppression list, newest first, 100 entries per page. Every undeliverable verdict adds the address in normalized form (lowercased, plus-tag stripped, Gmail dots folded) with source `auto`; imports arrive with source `imported`; bounces and complaints reported through the feedback endpoints arrive with source `feedback`. Batch, bulk and monitor runs answer suppressed addresses from the stored verdict at no cost; a single lookup always re-verifies, and any verdict other than undeliverable removes the entry. Entries untouched for 90 days are re-verified instead of trusted.
## Parameters
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `page` | query | integer | no | The page to return. Default: 1. |
## Example request
```bash
curl https://spaw.co/api/v1/email/suppressions \
-H "Authorization: Bearer sk_live_…"
```
## Responses
### 200 — One page of entries.
```json
{
"success": true,
"data": {
"suppressions": [
{
"id": 9041,
"email": "old@acme.com",
"reason": "mailbox_not_found",
"source": "auto",
"added_at": "2026-09-02T08:30:00+00:00"
},
{
"id": 9040,
"email": "bounced@example.org",
"reason": null,
"source": "imported",
"added_at": "2026-09-01T17:02:11+00:00"
}
]
},
"meta": {
"page": 1,
"per_page": 100,
"total": 2
}
}
```
### 401 — The key is missing, malformed, or revoked.
```json
{
"success": false,
"error": {
"code": "UNAUTHENTICATED",
"message": "Provide a valid API key as a bearer token.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 429 — Over 5 requests per second for the key. Retry after the limit resets.
```json
{
"success": false,
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Retry after the limit resets.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
## Error codes
- `UNAUTHENTICATED` — https://spaw.co/docs/errors/UNAUTHENTICATED
- `RATE_LIMITED` — https://spaw.co/docs/errors/RATE_LIMITED
---
Canonical page: https://spaw.co/docs/api/list-suppressions · OpenAPI document: https://spaw.co/openapi.json · All endpoints: https://spaw.co/docs/api
---
# Import known-bad addresses
`POST /api/v1/email/suppressions`
- Authentication: Secret API key as a bearer token
- Billing: Free.
- Group: Suppressions
Adds up to 10,000 addresses per request to the suppression list — an email provider's bounce export, a hand-kept blocklist. Addresses are normalized first; ones that do not parse are counted as `invalid`. Entries that already exist are never overwritten, because a verified auto-suppression outranks an import, and are counted as `already_suppressed`. Imported entries are honored from day one at no credit cost: list, bulk and monitor runs answer them with reason `suppressed` and `risk_score` 100.
## Request body
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `emails` | string[] | yes | 1 to 10,000 addresses; each item at most 254 characters. |
## Example request
```bash
curl -X POST https://spaw.co/api/v1/email/suppressions \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"emails": [
"bounced@example.org",
"gone@acme.com"
]
}'
```
## Responses
### 200 — How many entries were added, already present, or unparseable.
```json
{
"success": true,
"data": {
"added": 2,
"already_suppressed": 0,
"invalid": 0
}
}
```
### 401 — The key is missing, malformed, or revoked.
```json
{
"success": false,
"error": {
"code": "UNAUTHENTICATED",
"message": "Provide a valid API key as a bearer token.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 422 — The request body could not be validated; `error.errors` lists the fields.
```json
{
"success": false,
"error": {
"code": "VALIDATION_FAILED",
"message": "The email field is required.",
"errors": {
"email": [
"The email field is required."
]
},
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 429 — Over 5 requests per second for the key. Retry after the limit resets.
```json
{
"success": false,
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Retry after the limit resets.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
## Error codes
- `UNAUTHENTICATED` — https://spaw.co/docs/errors/UNAUTHENTICATED
- `VALIDATION_FAILED` — https://spaw.co/docs/errors/VALIDATION_FAILED
- `RATE_LIMITED` — https://spaw.co/docs/errors/RATE_LIMITED
---
Canonical page: https://spaw.co/docs/api/add-suppressions · OpenAPI document: https://spaw.co/openapi.json · All endpoints: https://spaw.co/docs/api
---
# Remove a suppression entry
`DELETE /api/v1/email/suppressions/{suppressionId}`
- Authentication: Secret API key as a bearer token
- Billing: Free.
- Group: Suppressions
Deletes one entry so the address is verified fresh the next time a list, bulk or monitor run meets it. An entry that belongs to another account answers `404`.
## Parameters
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `suppressionId` | path | integer | yes | The entry's id from the list endpoint. |
## Example request
```bash
curl -X DELETE https://spaw.co/api/v1/email/suppressions/9041 \
-H "Authorization: Bearer sk_live_…"
```
## Responses
### 200 — The entry was removed.
```json
{
"success": true
}
```
### 401 — The key is missing, malformed, or revoked.
```json
{
"success": false,
"error": {
"code": "UNAUTHENTICATED",
"message": "Provide a valid API key as a bearer token.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 404 — No such record on this account.
```json
{
"message": "Not Found"
}
```
### 429 — Over 5 requests per second for the key. Retry after the limit resets.
```json
{
"success": false,
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Retry after the limit resets.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
## Error codes
- `UNAUTHENTICATED` — https://spaw.co/docs/errors/UNAUTHENTICATED
- `RATE_LIMITED` — https://spaw.co/docs/errors/RATE_LIMITED
---
Canonical page: https://spaw.co/docs/api/remove-suppression · OpenAPI document: https://spaw.co/openapi.json · All endpoints: https://spaw.co/docs/api
---
# Report delivery outcomes
`POST /api/v1/email/feedback`
- Authentication: Secret API key as a bearer token
- Billing: Free.
- Group: Feedback
Tells Spaw what actually happened to an address after you sent to it: `delivered`, `bounced` (hard bounces only), or `complained`. Each outcome is stored against the verdict you had been given for that address in the previous 90 days, which turns verdicts into measured accuracy, and it keeps your suppression list current: a bounce or complaint adds the address with source `feedback`, a delivery removes an entry Spaw created automatically. An address you reported delivered within the last 90 days also counts as a confirmed mailbox for your own later lookups (`smtp_reason: "delivered_recently"`), without a probe.
Feedback is free and never logged as a lookup. Items whose address does not parse are skipped and counted, not rejected. Outcomes are kept for 180 days.
## Request body
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `items` | object[] | yes | 1 to 1,000 outcomes per request. |
| `items[].email` | string | yes | The recipient. Normalized like a lookup; an address that does not parse is skipped and counted, not rejected. At most 254 characters. |
| `items[].outcome` | string | yes | What happened. Report hard bounces only. One of: delivered, bounced, complained. |
| `items[].reason` | string | null | no | The provider's diagnostic, e.g. the SMTP reply. Stored as-is. At most 255 characters. |
| `items[].occurred_at` | string | null | no | When it happened, any parseable date. Defaults to now. |
## Example request
```bash
curl -X POST https://spaw.co/api/v1/email/feedback \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"email": "mia@acme.com",
"outcome": "bounced",
"reason": "550 5.1.1 no such user"
},
{
"email": "sam@example.org",
"outcome": "delivered",
"occurred_at": "2026-09-03T09:00:00Z"
}
]
}'
```
## Responses
### 202 — The outcomes were recorded.
```json
{
"success": true,
"data": {
"recorded": 2,
"skipped": 0
}
}
```
### 401 — The key is missing, malformed, or revoked.
```json
{
"success": false,
"error": {
"code": "UNAUTHENTICATED",
"message": "Provide a valid API key as a bearer token.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 422 — The request body could not be validated; `error.errors` lists the fields.
```json
{
"success": false,
"error": {
"code": "VALIDATION_FAILED",
"message": "The email field is required.",
"errors": {
"email": [
"The email field is required."
]
},
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 429 — Over 5 requests per second for the key. Retry after the limit resets.
```json
{
"success": false,
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Retry after the limit resets.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
## Error codes
- `UNAUTHENTICATED` — https://spaw.co/docs/errors/UNAUTHENTICATED
- `VALIDATION_FAILED` — https://spaw.co/docs/errors/VALIDATION_FAILED
- `RATE_LIMITED` — https://spaw.co/docs/errors/RATE_LIMITED
---
Canonical page: https://spaw.co/docs/api/report-delivery-feedback · OpenAPI document: https://spaw.co/openapi.json · All endpoints: https://spaw.co/docs/api
---
# Measured accuracy
`GET /api/v1/email/feedback/summary`
- Authentication: Secret API key as a bearer token
- Billing: Free.
- Group: Feedback
Crosses the last 90 days of reported outcomes with the verdicts on file. `by_verdict` counts delivered, bounced and complained per verdict (`deliverable`, `risky`, `undeliverable`, and `unknown` for addresses with no verdict on file), and `measured.deliverable_delivery_rate` and `measured.undeliverable_bounce_rate` are the two numbers that say how right Spaw was for you. A rate is `null` until there is at least one matching outcome. The same figures appear on the History tab of the dashboard.
## Example request
```bash
curl https://spaw.co/api/v1/email/feedback/summary \
-H "Authorization: Bearer sk_live_…"
```
## Responses
### 200 — The summary.
```json
{
"success": true,
"data": {
"window_days": 90,
"total": 1240,
"outcomes": {
"delivered": 1180,
"bounced": 52,
"complained": 8
},
"by_verdict": {
"deliverable": {
"delivered": 1150,
"bounced": 9,
"complained": 6
},
"risky": {
"delivered": 30,
"bounced": 11,
"complained": 2
},
"undeliverable": {
"delivered": 0,
"bounced": 32,
"complained": 0
},
"unknown": {
"delivered": 0,
"bounced": 0,
"complained": 0
}
},
"measured": {
"deliverable_delivery_rate": 0.992,
"undeliverable_bounce_rate": 1
}
}
}
```
### 401 — The key is missing, malformed, or revoked.
```json
{
"success": false,
"error": {
"code": "UNAUTHENTICATED",
"message": "Provide a valid API key as a bearer token.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 429 — Over 5 requests per second for the key. Retry after the limit resets.
```json
{
"success": false,
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Retry after the limit resets.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
## Error codes
- `UNAUTHENTICATED` — https://spaw.co/docs/errors/UNAUTHENTICATED
- `RATE_LIMITED` — https://spaw.co/docs/errors/RATE_LIMITED
---
Canonical page: https://spaw.co/docs/api/feedback-summary · OpenAPI document: https://spaw.co/openapi.json · All endpoints: https://spaw.co/docs/api
---
# Receive bounce webhooks from an email provider
`POST /api/v1/email/feedback/{provider}/{feedbackKey}`
- Authentication: Feedback key in the URL
- Billing: Free.
- Group: Feedback
The webhook target for an email provider's bounce, delivery and complaint notifications. Providers cannot send bearer tokens, so the account's feedback key rides in the URL (generate or rotate it on the dashboard's API keys page; rotating invalidates the URLs at once) and the provider name selects the payload adapter. The body is the provider's own payload, decoded as JSON regardless of content type because Amazon SNS posts JSON as `text/plain`. Payloads are read for their documented fields and nothing else; events that say nothing definitive about the address are ignored.
| Provider | Path | Mapping |
| --- | --- | --- |
| postmark | `/api/v1/email/feedback/postmark/{key}` | Bounce (hard types) → bounced · Delivery → delivered · SpamComplaint → complained. Soft bounces are ignored. |
| ses | `/api/v1/email/feedback/ses/{key}` | SNS notifications: Bounce with bounceType Permanent → bounced · Delivery → delivered · Complaint → complained. Subscription confirmations are answered. |
| mailgun | `/api/v1/email/feedback/mailgun/{key}` | delivered → delivered · failed with severity permanent → bounced · complained → complained. Temporary failures are ignored. |
| sendgrid | `/api/v1/email/feedback/sendgrid/{key}` | delivered → delivered · bounce and dropped → bounced · spamreport → complained. Deferred events are ignored. |
Throttled at 120 requests per minute per IP. Outcomes recorded here behave exactly like ones reported through `POST /api/v1/email/feedback`.
## Parameters
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `provider` | path | string | yes | Which provider's payload format to expect. One of: postmark, ses, mailgun, sendgrid. |
| `feedbackKey` | path | string | yes | The account's feedback key, which starts with `fb_`. |
## Request body
The provider's native webhook payload.
## Example request
```bash
curl -X POST https://spaw.co/api/v1/email/feedback/postmark/fb_… \
-H "Content-Type: application/json" \
-d '{
"RecordType": "Bounce",
"Type": "HardBounce",
"Email": "mia@acme.com",
"Description": "The server was unable to deliver your message (ex. unknown user, mailbox not found).",
"BouncedAt": "2026-09-03T10:12:44Z"
}'
```
## Responses
### 200 — The outcomes the payload carried were recorded.
```json
{
"success": true,
"data": {
"recorded": 1,
"skipped": 0
}
}
```
### 401 — The feedback key does not exist or was rotated.
```json
{
"success": false,
"error": {
"code": "INVALID_FEEDBACK_KEY",
"message": "That feedback key does not exist or was rotated.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 404 — The provider path is not one of postmark, ses, mailgun, or sendgrid.
```json
{
"success": false,
"error": {
"code": "UNKNOWN_FEEDBACK_PROVIDER",
"message": "Supported providers: postmark, ses, mailgun, sendgrid.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 429 — Over 5 requests per second for the key. Retry after the limit resets.
```json
{
"success": false,
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Retry after the limit resets.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
## Error codes
- `UNKNOWN_FEEDBACK_PROVIDER` — https://spaw.co/docs/errors/UNKNOWN_FEEDBACK_PROVIDER
- `INVALID_FEEDBACK_KEY` — https://spaw.co/docs/errors/INVALID_FEEDBACK_KEY
- `RATE_LIMITED` — https://spaw.co/docs/errors/RATE_LIMITED
---
Canonical page: https://spaw.co/docs/api/esp-bounce-webhook · OpenAPI document: https://spaw.co/openapi.json · All endpoints: https://spaw.co/docs/api
---
# Read the account and credit balance
`GET /api/v1/account`
- Authentication: Secret API key as a bearer token
- Billing: Free.
- Group: Account
The account behind the presented key: its name and email, the plan, the live credit balance, and the monthly free grant. Useful for dashboards and pre-flight checks before a big run. Reading the balance applies the month's free grant if it has not been applied yet.
## Example request
```bash
curl https://spaw.co/api/v1/account \
-H "Authorization: Bearer sk_live_…"
```
## Responses
### 200 — The account.
```json
{
"success": true,
"data": {
"name": "Mia Kowalski",
"email": "mia@acme.com",
"plan": "free",
"credits": {
"balance": 9,
"monthly_grant": 10
}
}
}
```
### 401 — The key is missing, malformed, or revoked.
```json
{
"success": false,
"error": {
"code": "UNAUTHENTICATED",
"message": "Provide a valid API key as a bearer token.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 429 — Over 5 requests per second for the key. Retry after the limit resets.
```json
{
"success": false,
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Retry after the limit resets.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
## Error codes
- `UNAUTHENTICATED` — https://spaw.co/docs/errors/UNAUTHENTICATED
- `RATE_LIMITED` — https://spaw.co/docs/errors/RATE_LIMITED
---
Canonical page: https://spaw.co/docs/api/get-account · OpenAPI document: https://spaw.co/openapi.json · All endpoints: https://spaw.co/docs/api
---
# Validate and enrich a phone number
`POST /api/v1/phone`
- Authentication: Secret API key as a bearer token
- Billing: 1 credit for a fresh lookup that answers `valid` true; invalid numbers and 7-day repeats are free.
- Group: Phone
Parses a number with libphonenumber metadata and answers whether it is valid for its region, its E.164, national and international formats, the country, the line type (`mobile`, `fixed_line`, `fixed_line_or_mobile`, `voip`, `toll_free`, …), the carrier and region where the metadata knows them, and the time zones the number belongs to. Pass `country` (an ISO 3166-1 alpha-2 code) to parse numbers written without a country calling code.
The phone product page is marked "coming soon"; the endpoint itself works today under the same envelope, billing and error codes as every other lookup.
## Request body
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `phone` | string | yes | The number, in any common notation. Include the country calling code or pass `country`. At most 32 characters. |
| `country` | string | null | no | An ISO 3166-1 alpha-2 region code (`US`, `GB`, `PL`) used to parse numbers written without a calling code. |
## Example request
```bash
curl -X POST https://spaw.co/api/v1/phone \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"phone": "+1 415 555 0142"
}'
```
## Responses
### 200 — The parsed number; `valid: false` with a `reason` for numbers that do not parse.
```json
{
"success": true,
"data": {
"valid": true,
"reason": null,
"e164": "+14155550142",
"national": "(415) 555-0142",
"international": "+1 415-555-0142",
"country": "US",
"line_type": "fixed_line_or_mobile",
"carrier": null,
"region": "San Francisco, CA",
"extension": null,
"timezones": [
"America/Los_Angeles"
],
"sources": [
{
"dataset": "libphonenumber",
"version": "9.0.37"
}
]
},
"meta": {
"credits_used": 1,
"credits_remaining": 8,
"cache_hit": false,
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 401 — The key is missing, malformed, or revoked.
```json
{
"success": false,
"error": {
"code": "UNAUTHENTICATED",
"message": "Provide a valid API key as a bearer token.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 402 — The balance is empty. The lookup did not run.
```json
{
"success": false,
"error": {
"code": "INSUFFICIENT_CREDITS",
"message": "Your credit balance is empty.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 422 — The request body could not be validated; `error.errors` lists the fields.
```json
{
"success": false,
"error": {
"code": "VALIDATION_FAILED",
"message": "The email field is required.",
"errors": {
"email": [
"The email field is required."
]
},
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 429 — Over 5 requests per second for the key. Retry after the limit resets.
```json
{
"success": false,
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Retry after the limit resets.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
## Error codes
- `UNAUTHENTICATED` — https://spaw.co/docs/errors/UNAUTHENTICATED
- `VALIDATION_FAILED` — https://spaw.co/docs/errors/VALIDATION_FAILED
- `INSUFFICIENT_CREDITS` — https://spaw.co/docs/errors/INSUFFICIENT_CREDITS
- `RATE_LIMITED` — https://spaw.co/docs/errors/RATE_LIMITED
---
Canonical page: https://spaw.co/docs/api/validate-phone · OpenAPI document: https://spaw.co/openapi.json · All endpoints: https://spaw.co/docs/api
---
# Locate and risk-score an IP address
`POST /api/v1/ip`
- Authentication: Secret API key as a bearer token
- Billing: 1 credit for a fresh lookup that resolves at least one signal (a location, a network, or a positive threat flag); reserved ranges, unknown addresses and 7-day repeats are free.
- Group: IP
Locates an IPv4 or IPv6 address with the DB-IP Lite city and ASN databases and flags datacenter, Tor exit and VPN ranges from open threat lists. The flags are tri-state: `false` is a checked negative, `null` means the signal was not evaluated (list not installed, or IPv6 where a list is IPv4-only). `risk_score` sums only the signals that were checked — Tor 70, datacenter 40, VPN 30, capped at 100 — and `risk_level` buckets it with the same thresholds as email: 60 and above high, 30 and above medium. Reserved and private ranges answer `reason: "reserved_range"` with every field null.
The IP product page is marked "coming soon"; the endpoint itself works today.
## Request body
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `ip` | string | yes | An IPv4 or IPv6 address. |
## Example request
```bash
curl -X POST https://spaw.co/api/v1/ip \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"ip": "8.8.8.8"
}'
```
## Responses
### 200 — The location, network and threat flags.
```json
{
"success": true,
"data": {
"ip": "8.8.8.8",
"version": 4,
"reason": null,
"country": "US",
"region": "California",
"city": "Mountain View",
"latitude": 37.4056,
"longitude": -122.0775,
"asn": 15169,
"org": "Google LLC",
"is_datacenter": true,
"is_tor": false,
"is_vpn": false,
"risk_score": 40,
"risk_level": "medium",
"sources": [
{
"dataset": "dbip-city-lite",
"version": "2026-09"
},
{
"dataset": "dbip-asn-lite",
"version": "2026-09"
},
{
"dataset": "tor-exit-list",
"version": "2026-09-03"
},
{
"dataset": "datacenter-ranges",
"version": "2026-09-03"
},
{
"dataset": "vpn-ranges",
"version": "2026-09-03"
}
]
},
"meta": {
"credits_used": 1,
"credits_remaining": 8,
"cache_hit": false,
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 401 — The key is missing, malformed, or revoked.
```json
{
"success": false,
"error": {
"code": "UNAUTHENTICATED",
"message": "Provide a valid API key as a bearer token.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 402 — The balance is empty. The lookup did not run.
```json
{
"success": false,
"error": {
"code": "INSUFFICIENT_CREDITS",
"message": "Your credit balance is empty.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 422 — The request body could not be validated; `error.errors` lists the fields.
```json
{
"success": false,
"error": {
"code": "VALIDATION_FAILED",
"message": "The email field is required.",
"errors": {
"email": [
"The email field is required."
]
},
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
### 429 — Over 5 requests per second for the key. Retry after the limit resets.
```json
{
"success": false,
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Retry after the limit resets.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
## Error codes
- `UNAUTHENTICATED` — https://spaw.co/docs/errors/UNAUTHENTICATED
- `VALIDATION_FAILED` — https://spaw.co/docs/errors/VALIDATION_FAILED
- `INSUFFICIENT_CREDITS` — https://spaw.co/docs/errors/INSUFFICIENT_CREDITS
- `RATE_LIMITED` — https://spaw.co/docs/errors/RATE_LIMITED
---
Canonical page: https://spaw.co/docs/api/lookup-ip · OpenAPI document: https://spaw.co/openapi.json · All endpoints: https://spaw.co/docs/api
---
# Verdict reasons and mailbox-check details
Every Spaw email verification answer carries `deliverable` (deliverable, risky, or undeliverable) and, when the verdict is not deliverable, a machine-readable `reason`. `smtp_reason` adds the detail behind the mailbox handshake. Undeliverable verdicts are free; deliverable and risky answers cost 1 credit, and repeats of the same address are free for 7 days. Each value is documented at https://spaw.co/docs/reasons/{value}.
## `invalid_syntax`: the input does not parse as an email address
Even after Spaw strips display names, mailto: links, quotes and invisible characters, what is left is not a syntactically valid address, so nothing else was checked.
- Appears in: `reason`
- Verdict: undeliverable
- Billing: Free. Invalid input never costs a credit.
Spaw first pulls the address out of whatever was pasted: a display name such as Mia K , a mailto: link, spreadsheet quotes, trailing punctuation, zero-width and non-breaking characters are all removed. What remains is parsed against RFC 5322. invalid_syntax means that even after that cleanup there is no address to check: a missing @, a domain without a top-level label, spaces inside the username, or a bare word.
Because no domain could be extracted, every other field in the response is null, syntax_valid is false, and the risk score is fixed at 100.
**What to do.** Fix the input before retrying. When the value comes from a form, show the user the exact value that was checked (it is echoed in the email field) so a paste error is easy to spot.
```json
{
"email": "mia@acme",
"deliverable": "undeliverable",
"reason": "invalid_syntax",
"syntax_valid": false,
"risk_score": 100
}
```
Reference: https://spaw.co/docs/reasons/invalid_syntax
## `invalid_local_part`: the username breaks the rules its own provider enforces
The part before the @ cannot exist at the provider that would host the mailbox, so the address is rejected before any mailbox probe is spent on it.
- Appears in: `reason`
- Verdict: undeliverable
- Billing: Free. Undeliverable verdicts never cost a credit.
Large mailbox providers publish rules for usernames: Gmail allows letters, digits and dots up to 30 characters, Outlook, Yahoo, iCloud, AOL and Proton each have their own character sets and length limits. An address that violates the rule of the provider behind its domain cannot exist, and probing it would only waste a handshake.
Spaw applies character-set and maximum-length rules only. Minimum lengths are deliberately not enforced, because legacy accounts predate them.
**What to do.** Treat it like a typo in the username. If the address came from a signup form, ask the user to re-enter it; on an imported list, drop it.
```json
{
"email": "m!a@gmail.com",
"deliverable": "undeliverable",
"reason": "invalid_local_part",
"mx_provider": "google",
"smtp_checked": false
}
```
Reference: https://spaw.co/docs/reasons/invalid_local_part
## `no_mx_records`: the domain has no mail servers at all
Live DNS returned no MX record and no A or AAAA record for the domain, so no host could accept mail for it.
- Appears in: `reason`
- Verdict: undeliverable
- Billing: Free. Undeliverable verdicts never cost a credit.
A domain that publishes neither MX records nor an address record cannot receive email by any route. In practice that means a misspelled domain, a domain that was never set up for email, or one that has lapsed.
The lookup stops before the mailbox handshake: smtp_checked is false and mailbox_exists is null. When a close, real domain exists, did_you_mean carries the suggested correction, verified with its own MX check.
**What to do.** Show did_you_mean when it is present. Otherwise treat the address as unusable and do not retry automatically: DNS answers are cached and a fresh result will not appear for hours.
```json
{
"email": "mia@acme-corp.cmo",
"deliverable": "undeliverable",
"reason": "no_mx_records",
"mx_found": false,
"did_you_mean": "mia@acme-corp.com"
}
```
Reference: https://spaw.co/docs/reasons/no_mx_records
## `null_mx`: the domain declares that it never accepts mail
The domain publishes a null MX record, the standard way of saying it does not receive email.
- Appears in: `reason`
- Verdict: undeliverable
- Billing: Free. Undeliverable verdicts never cost a credit.
RFC 7505 lets a domain publish a single MX record pointing at "." with priority 0, the null MX, to state explicitly that it does not receive email. Website-only domains, brand-protection domains and parked domains use it.
It is a deliberate, authoritative statement, so Spaw does not try the address-record fallback and does not probe. mx_found is false.
**What to do.** Reject the address. There is no correction to suggest, and nothing changes unless the domain owner removes the record.
```json
{
"email": "hello@brand-holding.example",
"deliverable": "undeliverable",
"reason": "null_mx",
"mx_found": false
}
```
Reference: https://spaw.co/docs/reasons/null_mx
## `mx_unresolvable`: the mail servers the domain points at do not exist
The domain publishes MX records, but every target fails to resolve or resolves to an address that cannot receive mail from the internet.
- Appears in: `reason`
- Verdict: undeliverable
- Billing: Free. Undeliverable verdicts never cost a credit.
Every MX target either does not resolve, resolves to a loopback or private address (127.0.0.1, 10.x, 192.168.x), or resolves to nothing routable. Mail sent there bounces at the first hop. It is common on abandoned domains and on domains whose DNS was migrated without the mail records.
Spaw reports mx_found false and skips the handshake. Compare mx_unreachable, the smtp_reason for hosts that resolve but cannot be reached.
**What to do.** Treat the address as undeliverable. If it is your own domain, fix the MX targets; the answer refreshes once DNS caches expire.
```json
{
"email": "ops@dead-mail.example",
"deliverable": "undeliverable",
"reason": "mx_unresolvable",
"mx_found": false
}
```
Reference: https://spaw.co/docs/reasons/mx_unresolvable
## `mailbox_not_found`: the mail server rejected the mailbox during the handshake
The domain accepts mail, but at the RCPT TO step the server answered that this mailbox does not exist. No message was sent.
- Appears in: `reason`
- Verdict: undeliverable
- Billing: Free. Undeliverable verdicts never cost a credit.
The SMTP conversation reached the recipient check and the server rejected it, typically with a 550 5.1.1 "user unknown" reply. The handshake ends there; no DATA is ever transmitted and nothing lands in an inbox or a spam folder.
The verdict is undeliverable, mailbox_exists is false, and smtp_reason carries the detail, usually no_mailbox. The address is also added to your account's suppression list, so batch and bulk runs answer it for free until it is explicitly re-verified.
**What to do.** Remove the address from the list. A confirmed missing mailbox is the single strongest bounce predictor, and sending to it damages sender reputation.
```json
{
"email": "nobody@acme.com",
"deliverable": "undeliverable",
"reason": "mailbox_not_found",
"mailbox_exists": false,
"smtp_checked": true,
"smtp_reason": "no_mailbox"
}
```
Reference: https://spaw.co/docs/reasons/mailbox_not_found
## `mailbox_disabled`: the mailbox exists but its provider has disabled it
The server recognised the address but reported the account as disabled, suspended or deactivated. Mail to it is rejected.
- Appears in: `reason`
- Verdict: undeliverable
- Billing: Free. Undeliverable verdicts never cost a credit.
Unlike a mailbox that was never created, a disabled mailbox was real once: a closed account, a suspended user, a deprovisioned employee. The provider still rejects mail for it, so the verdict is undeliverable.
smtp_reason is disabled. Like mailbox_not_found, the address joins your suppression list.
**What to do.** Remove the address. Disabled accounts rarely come back, and when they do the owner will typically subscribe again.
```json
{
"email": "former.employee@acme.com",
"deliverable": "undeliverable",
"reason": "mailbox_disabled",
"smtp_reason": "disabled",
"mailbox_exists": false
}
```
Reference: https://spaw.co/docs/reasons/mailbox_disabled
## `likely_typo`: the domain is a known misspelling of a real mail provider
The domain is on Spaw's curated map of typo-squats such as gmail.con or hotmial.com. Mail may be accepted there, just not by your customer.
- Appears in: `reason`
- Verdict: risky
- Risk weight: +60
- Billing: 1 credit, like every deliverable or risky answer. Repeats of the same address are free for 7 days.
Spaw keeps a curated map of typo-squat domains, each paired with the domain the user meant. Many typo domains are registered by squatters and even accept mail, which is exactly why they are dangerous: the message is delivered, only not to the person who signed up.
The verdict is risky rather than undeliverable because mail may technically be accepted, and did_you_mean contains the corrected address. A typo-squat that also sits on the disposable list is reported as likely_typo, because the correction is the more useful explanation.
**What to do.** Offer the correction in the form and let the user confirm it. On imported lists, replace the address with did_you_mean only when you can confirm with the contact; otherwise drop it.
```json
{
"email": "mia@gmail.con",
"deliverable": "risky",
"reason": "likely_typo",
"did_you_mean": "mia@gmail.com",
"risk_score": 60
}
```
Reference: https://spaw.co/docs/reasons/likely_typo
## `disposable`: the domain belongs to a disposable, temporary email provider
The domain, or the mail infrastructure behind its MX hosts, is on the open disposable-provider list. The inbox exists for minutes or hours.
- Appears in: `reason`
- Verdict: risky
- Risk weight: +80
- Billing: 1 credit, like every deliverable or risky answer. Repeats of the same address are free for 7 days.
Spaw syncs the open disposable-provider list weekly and extends it with local overrides, and it also matches the operators behind a domain's MX hosts, so a fresh burner domain on a known disposable backend is caught before the list catches up. Whoever signed up with a disposable address does not expect to hear from you again.
Disposable domains are never sent to the mailbox probe, so smtp_checked is false. The risk weight of 80 puts the address in the high band by itself.
**What to do.** For signups, block the address or ask for a different one. For existing lists, do not send: even when the message is accepted nobody reads it, and these domains attract spam-trap conversions.
```json
{
"email": "x9@mailinator.com",
"deliverable": "risky",
"reason": "disposable",
"disposable": true,
"smtp_checked": false,
"risk_score": 90
}
```
Reference: https://spaw.co/docs/reasons/disposable
## `implicit_mx`: the domain has no MX record and relies on the address-record fallback
No MX record exists; by the letter of RFC 5321 mail would be delivered to the host at the domain's A record, which is almost never a mail server today.
- Appears in: `reason`
- Verdict: risky
- Risk weight: +30
- Billing: 1 credit, like every deliverable or risky answer. Repeats of the same address are free for 7 days.
RFC 5321 says that a domain without MX records receives mail at its A or AAAA address. Real mail domains stopped relying on that decades ago: a web server answering on port 80 does not accept mail. Spaw reports mx_found true and mx_implicit true, and flags the address as risky.
The mailbox handshake still runs. If the host really does accept mail, the probe can confirm or reject the mailbox and the verdict follows that answer.
**What to do.** Treat the address as probably dead unless the handshake confirmed the mailbox. On B2B lists it usually marks a company that lost its mail setup or a domain used only for a website.
```json
{
"email": "info@web-only.example",
"deliverable": "risky",
"reason": "implicit_mx",
"mx_found": true,
"mx_implicit": true
}
```
Reference: https://spaw.co/docs/reasons/implicit_mx
## `mailbox_full`: the mailbox is over its storage quota
The server accepted the address but answered that the mailbox cannot take more mail right now. The account is real and may be read again once space is cleared.
- Appears in: `reason`, `smtp_reason`
- Verdict: risky
- Risk weight: +20
- Billing: 1 credit, like every deliverable or risky answer. Repeats of the same address are free for 7 days.
The handshake reached the recipient check and the server replied with a "mailbox full" class response (a 452 or 552). The mailbox exists, it just has no room, which is why the verdict is risky rather than undeliverable.
As a reason it sets the verdict. As an smtp_reason it explains a probe result on a lookup that another signal may already have settled; the same value appears in both fields for the same address.
**What to do.** Keep the address but hold the send and retry in a few days. For transactional mail that matters, use another channel meanwhile.
```json
{
"email": "mia@acme.com",
"deliverable": "risky",
"reason": "mailbox_full",
"smtp_reason": "mailbox_full",
"mailbox_exists": true
}
```
Reference: https://spaw.co/docs/reasons/mailbox_full
## `role`: a shared role inbox such as info@ or support@ rather than a person
The username is on the open list of role addresses. The inbox is shared, monitored unevenly, and rarely read by the person who filled in your form.
- Appears in: `reason`
- Verdict: risky
- Risk weight: +30
- Billing: 1 credit, like every deliverable or risky answer. Repeats of the same address are free for 7 days.
The local part matches the open role-address list (info, support, sales, admin, billing, noreply and around a thousand more), synced weekly. Role inboxes belong to a function rather than a person, get forwarded and filtered unpredictably, and carry higher complaint rates.
The mailbox is still probed, so mailbox_exists can be true for a role address; the verdict stays risky.
**What to do.** Accept role addresses on B2B forms where a shared inbox is expected, but keep them out of marketing sends and personalised sequences.
```json
{
"email": "support@acme.com",
"deliverable": "risky",
"reason": "role",
"role": true,
"risk_score": 30
}
```
Reference: https://spaw.co/docs/reasons/role
## `catch_all`: the server accepts every address at the domain, so the mailbox cannot be confirmed
A catch-all (accept-all) server answers OK to any recipient, including ones that do not exist, so the handshake proves nothing about this particular mailbox.
- Appears in: `reason`
- Verdict: risky
- Risk weight: +30
- Billing: 1 credit, like every deliverable or risky answer. Repeats of the same address are free for 7 days.
Corporate domains behind Proofpoint or Mimecast, and many Microsoft 365 tenants, are configured to accept all recipients and sort them out later. Spaw remembers a catch-all answer per domain for seven days and does not spend further probes on that domain.
mailbox_exists stays null. mailbox_confidence gives a recomputable 0 to 100 estimate built from the domain's DMARC and SPF, its provider, registration age, the shape of the username, and your own delivery feedback for the domain.
**What to do.** Send in moderation and report outcomes through the feedback endpoint: your own delivered reports turn later lookups of that address into confirmed mailboxes, and bounces feed the confidence score for the whole domain.
```json
{
"email": "mia@acme.com",
"deliverable": "risky",
"reason": "catch_all",
"catch_all": true,
"mailbox_exists": null,
"mailbox_confidence": 72
}
```
Reference: https://spaw.co/docs/reasons/catch_all
## `suppressed`: the address is on your account's suppression list
Batch runs, bulk jobs and monitors answered the address from your suppression list without running a lookup.
- Appears in: `reason`
- Verdict: undeliverable
- Billing: Free. Suppressed answers are served from the stored verdict without running the pipeline.
Your suppression list holds every address that earned an undeliverable verdict in the last 90 days plus anything you imported. Batch, bulk and monitor runs consult it first and answer from the stored result: reason suppressed for imported entries, the original reason for verified ones, and meta.suppressed true either way.
Single lookups through the API and the dashboard deliberately ignore the list. An explicit re-verify is the only way off it: a recovered verdict removes the entry, a fresh undeliverable one renews it.
**What to do.** Nothing for sends: the address stays out. To challenge an entry, verify that single address; the fresh answer replaces or clears it.
```json
{
"email": "old@acme.com",
"deliverable": "undeliverable",
"reason": "suppressed",
"risk_score": 100
}
```
Reference: https://spaw.co/docs/reasons/suppressed
## `no_mailbox`: the server answered that the mailbox does not exist
The detail behind a mailbox_not_found verdict: the handshake reached the recipient check and the server rejected the address outright.
- Appears in: `smtp_reason`
- Billing: The verdict is undeliverable, so the lookup is free.
The most reliable negative signal SMTP can give. The server was asked whether it would accept mail for the recipient and said no, usually with a 550 5.1.1 reply. Spaw records mailbox_exists false and the verdict mailbox_not_found.
Only an explicit "no such user" answer maps to no_mailbox. Ambiguous rejections and policy blocks are reported as unverified instead of being guessed.
**What to do.** Remove the address from the list; it is on your suppression list already.
```json
{
"deliverable": "undeliverable",
"reason": "mailbox_not_found",
"smtp_reason": "no_mailbox",
"mailbox_exists": false
}
```
Reference: https://spaw.co/docs/reasons/no_mailbox
## `disabled`: the server reported the mailbox as disabled or suspended
The detail behind a mailbox_disabled verdict: the account exists in the provider's directory but is blocked from receiving mail.
- Appears in: `smtp_reason`
- Billing: The verdict is undeliverable, so the lookup is free.
Closed accounts, suspended users and deprovisioned employees answer this way at providers that distinguish "disabled" from "unknown". Mail is rejected exactly as for a missing mailbox.
**What to do.** Remove the address; it has joined your suppression list.
```json
{
"deliverable": "undeliverable",
"reason": "mailbox_disabled",
"smtp_reason": "disabled",
"mailbox_exists": false
}
```
Reference: https://spaw.co/docs/reasons/disabled
## `greylisted`: the server deferred the handshake and asked the sender to come back later
Greylisting answered the probe with a temporary failure. The mailbox is left unverified and the verdict rests on the free signals.
- Appears in: `smtp_reason`
- Billing: Billed by the verdict it produced: a deliverable answer with smtp_checked false is 1 credit. The settling re-checks are free.
Greylisting is an anti-spam technique: the first connection from an unknown sender gets a temporary 4xx failure and is expected to retry minutes later. A verification probe cannot wait that long, so smtp_checked is false and mailbox_exists is null.
The verdict comes from everything else Spaw knows about the address, usually deliverable with the unverified weight of 10 added to the risk score and a mailbox_confidence estimate attached.
**What to do.** Pass callback_url with the lookup: Spaw re-checks the address after 5 and 20 minutes and pushes the settled verdict to you at no cost. Or re-verify later; repeats inside seven days are free.
```json
{
"deliverable": "deliverable",
"smtp_checked": false,
"smtp_reason": "greylisted",
"mailbox_exists": null,
"mailbox_confidence": 65
}
```
Reference: https://spaw.co/docs/reasons/greylisted
## `timeout`: the mail server did not answer within the probe's time limit
A connection was opened but the server went silent before the recipient reply. The mailbox is left unverified.
- Appears in: `smtp_reason`
- Billing: Billed by the verdict it produced: a deliverable answer with smtp_checked false is 1 credit. The settling re-checks are free.
Some providers slow every session down on purpose to frustrate address harvesting; others are simply overloaded. Either way the probe gave up before an answer, smtp_checked is false and mailbox_exists is null.
**What to do.** Pass callback_url with the lookup: Spaw re-checks the address after 5 and 20 minutes and pushes the settled verdict to you at no cost. Or re-verify later; repeats inside seven days are free.
```json
{
"deliverable": "deliverable",
"smtp_checked": false,
"smtp_reason": "timeout",
"mailbox_exists": null
}
```
Reference: https://spaw.co/docs/reasons/timeout
## `no_connect`: no mail server accepted a connection on port 25
The MX hosts resolved but none of them accepted a connection: a firewall, a server that is down, or an MX that points at something other than a mail server.
- Appears in: `smtp_reason`
- Billing: Billed by the verdict it produced: a deliverable answer with smtp_checked false is 1 credit. The settling re-checks are free.
Different from mx_unresolvable, where the hosts do not even resolve. Here DNS is fine and the machines refuse or drop the connection. The mailbox stays unverified and the free signals decide the verdict.
**What to do.** Retry later. If no_connect persists for days on a business domain, its mail is probably broken and the addresses should be treated as risky.
```json
{
"deliverable": "deliverable",
"smtp_checked": false,
"smtp_reason": "no_connect",
"mailbox_exists": null
}
```
Reference: https://spaw.co/docs/reasons/no_connect
## `mx_unreachable`: the mail exchangers could not be reached from the verification network
The probe could not reach any of the domain's mail servers: unreachable at the network level rather than a refused connection.
- Appears in: `smtp_reason`
- Billing: Billed by the verdict it produced: a deliverable answer with smtp_checked false is 1 credit. The settling re-checks are free.
Transient routing problems and geo-restricted mail servers produce it. The answer is not cached for long, so a later lookup probes again.
**What to do.** Pass callback_url with the lookup: Spaw re-checks the address after 5 and 20 minutes and pushes the settled verdict to you at no cost. Or re-verify later; repeats inside seven days are free.
```json
{
"deliverable": "deliverable",
"smtp_checked": false,
"smtp_reason": "mx_unreachable",
"mailbox_exists": null
}
```
Reference: https://spaw.co/docs/reasons/mx_unreachable
## `delivered_recently`: your own delivery report confirmed this mailbox
Not a probe result: your account reported the address as delivered within the last 90 days, and a real delivery is better evidence than any handshake.
- Appears in: `smtp_reason`
- Billing: 1 credit as a deliverable answer; the handshake itself is skipped.
When you send delivery outcomes through the feedback endpoint or an ESP webhook, a delivered report for an address turns later lookups of it into confirmed mailboxes. The probe is skipped, mailbox_exists is true, smtp_checked_at is the delivery time, and the sources list delivery-feedback instead of the SMTP handshake.
Only your own account's reports count. Other customers' outcomes are never used for your verdicts.
**What to do.** Nothing. Keep reporting outcomes and the share of lookups that needs a paid probe keeps falling.
```json
{
"deliverable": "deliverable",
"mailbox_exists": true,
"smtp_checked": true,
"smtp_reason": "delivered_recently"
}
```
Reference: https://spaw.co/docs/reasons/delivered_recently
## `provider_unavailable`: the verification provider itself could not answer
The partner infrastructure that runs the mailbox handshake was unreachable, returned an error, or was held off by Spaw's circuit breaker. The lookup was answered from the free signals.
- Appears in: `smtp_reason`
- Billing: Billed by the verdict it produced: a deliverable answer with smtp_checked false is 1 credit. The settling re-checks are free.
After repeated provider failures Spaw stops sending probes for a few minutes and answers every mailbox check as unverified, so a provider incident never turns into a queue of slow requests. Such answers are never cached: the next lookup probes again as soon as the provider recovers.
smtp_checked is false and mailbox_exists is null; the verdict comes from syntax, DNS, the open lists and the domain signals.
**What to do.** Retry later, or pass callback_url so the settled verdict is pushed to you. If you see it across many lookups, quote a request_id to support@spaw.co.
```json
{
"deliverable": "deliverable",
"smtp_checked": false,
"smtp_reason": "provider_unavailable",
"mailbox_exists": null
}
```
Reference: https://spaw.co/docs/reasons/provider_unavailable
# API error codes
Every failed request answers `{ "success": false, "error": { "code", "message", "request_id" } }` with one of the codes below. Nothing is billed for a failed request. Each code is documented at https://spaw.co/docs/errors/{CODE}.
## `UNAUTHENTICATED` (HTTP 401): no valid API key was sent
The Authorization header is missing, malformed, or carries a key that does not exist or was revoked.
Every endpoint under /api/v1 except the browser endpoint and the bounce webhooks authenticates with a secret key sent as a bearer token: Authorization: Bearer sk_live_… . Keys are shown once at creation and stored hashed, so a key you cannot find cannot be recovered.
Revoking a key from the dashboard stops its requests immediately with this code.
**What to do.** Send the header exactly as Authorization: Bearer followed by the key. If the key is lost, create a new one on the API keys page and retire the old one.
```json
{
"success": false,
"error": {
"code": "UNAUTHENTICATED",
"message": "Provide a valid API key as a bearer token.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
Reference: https://spaw.co/docs/errors/UNAUTHENTICATED
## `VALIDATION_FAILED` (HTTP 422): the request body did not pass validation
One or more fields are missing, malformed, or over a limit. error.errors lists each field with its messages.
Typical causes: an email longer than 254 characters, more than 50 addresses in a batch or 100,000 in a bulk job, a callback_url that is not https, callback_secret missing or outside 16 to 128 characters, a bulk results variant that is not full, deliverable, risky or undeliverable, or a feedback outcome that is not delivered, bounced or complained.
Nothing runs and nothing is billed. The errors object is keyed by field name, with array items addressed as emails.3.
**What to do.** Read error.errors, fix the named fields, and resend. Validation rules are listed on each endpoint's reference page.
```json
{
"success": false,
"error": {
"code": "VALIDATION_FAILED",
"message": "The email field is required.",
"errors": {
"email": [
"The email field is required."
]
},
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
Reference: https://spaw.co/docs/errors/VALIDATION_FAILED
## `RATE_LIMITED` (HTTP 429): more requests than the limit allows
The API allows 5 requests per second per key; the browser endpoint allows 20 lookups a minute per visitor.
The response carries a Retry-After header with the seconds until the window resets. The request did not run and nothing was billed.
Verifying a list one address at a time is the usual cause. The batch endpoint takes 50 addresses per request and probes them concurrently; bulk jobs take up to 100,000.
**What to do.** Wait for Retry-After, then resend. For lists, switch to the batch or bulk endpoint instead of tightening a client-side loop.
```json
{
"success": false,
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Retry after the limit resets.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
Reference: https://spaw.co/docs/errors/RATE_LIMITED
## `INSUFFICIENT_CREDITS` (HTTP 402): the credit balance is empty
The account has no credits left, so the lookup did not run. Free credits renew at the start of each month; packs can be bought from the Billing page.
Spaw checks the balance before doing any work, so a 402 always means nothing was probed and nothing was charged. Every account receives 10 free credits each calendar month; credit packs bought through the dashboard never expire.
A batch that runs out of credits part-way answers 200 with the results paid for so far and meta.stopped_reason set to insufficient_credits; only a batch whose first lookup is refused returns this error.
**What to do.** Buy a credit pack from the Billing page, or wait for the monthly grant. GET /api/v1/account returns the live balance for pre-flight checks.
```json
{
"success": false,
"error": {
"code": "INSUFFICIENT_CREDITS",
"message": "You have run out of credits. Your free credits renew at the start of each month.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
Reference: https://spaw.co/docs/errors/INSUFFICIENT_CREDITS
## `RESULTS_NOT_READY` (HTTP 409): the bulk job has not finished yet
Results were requested for a bulk job that is still queued or running.
Bulk jobs run in the background in chunks. Until the job reports completed, the results file does not exist. The job status endpoint shows processed counts while it runs.
**What to do.** Poll GET /api/v1/email/bulk/{jobId} until status is completed, or pass webhook_url at creation and wait for the signed completion webhook instead of polling.
```json
{
"success": false,
"error": {
"code": "RESULTS_NOT_READY",
"message": "The job has not finished yet. Poll its status until it reports completed.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
Reference: https://spaw.co/docs/errors/RESULTS_NOT_READY
## `JOB_ALREADY_FINISHED` (HTTP 409): the bulk job cannot be cancelled any more
A cancel was requested for a bulk job that already reached a terminal status: completed, cancelled, or failed.
Cancellation only makes sense while a job is queued or running. A finished job keeps its counters and its results; nothing about it changes.
**What to do.** Nothing to do. Download the results if the job completed, or create a new job.
```json
{
"success": false,
"error": {
"code": "JOB_ALREADY_FINISHED",
"message": "The job has already finished and cannot be cancelled.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
Reference: https://spaw.co/docs/errors/JOB_ALREADY_FINISHED
## `JOB_CANCELLED` (HTTP 409): the bulk job was cancelled before producing results
Results were requested for a job that was cancelled before any address was processed, so there is no results file.
A job cancelled part-way keeps its partial results and serves them normally. Only a job cancelled with zero processed addresses answers with this code.
**What to do.** Create a new job with the same addresses.
```json
{
"success": false,
"error": {
"code": "JOB_CANCELLED",
"message": "The job was cancelled before any addresses were processed, so there are no results.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
Reference: https://spaw.co/docs/errors/JOB_CANCELLED
## `INVALID_PUBLISHABLE_KEY` (HTTP 401): the publishable key does not exist or was revoked
The pk_ key sent to the browser endpoint is unknown.
The browser endpoint POST /api/v1/email/public authenticates with a publishable key in the request body, not a bearer token. Publishable keys are created on the API keys page and stay visible there; revoking one stops its lookups immediately.
**What to do.** Check the key on the API keys page. The form widget reads it from its data-key attribute; a stale deploy is the usual cause.
```json
{
"success": false,
"error": {
"code": "INVALID_PUBLISHABLE_KEY",
"message": "That publishable key does not exist or was revoked.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
Reference: https://spaw.co/docs/errors/INVALID_PUBLISHABLE_KEY
## `ORIGIN_NOT_ALLOWED` (HTTP 403): the page's origin is not on the key's allowed list
The Origin header of the browser request does not match any domain the publishable key allows, or the header is missing.
Each publishable key is locked to the domains you list at creation, so a key copied out of your page source cannot be used from another site. A missing Origin header is rejected on purpose: server-side code should use a secret key.
**What to do.** Add the site's exact host to the key's allowed domains, or create a key for that site. For server-side lookups use a secret sk_live_ key against POST /api/v1/email.
```json
{
"success": false,
"error": {
"code": "ORIGIN_NOT_ALLOWED",
"message": "This publishable key cannot be used from this origin.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
Reference: https://spaw.co/docs/errors/ORIGIN_NOT_ALLOWED
## `KEY_SPEND_CAP_REACHED` (HTTP 429): the publishable key has spent its daily credit cap
The key was created with a daily credit cap and has reached it today. The counter resets each day.
A daily cap protects your balance from a form that is being abused. Only answered lookups count towards it: undeliverable verdicts, cache hits and test addresses are free and never move the counter.
**What to do.** Wait for the daily reset, or create a key with a higher cap. The form widget never blocks the visitor's submit when a lookup is refused.
```json
{
"success": false,
"error": {
"code": "KEY_SPEND_CAP_REACHED",
"message": "This publishable key has reached its daily credit cap. The counter resets each day.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
Reference: https://spaw.co/docs/errors/KEY_SPEND_CAP_REACHED
## `TURNSTILE_FAILED` (HTTP 403): the publishable key requires a Cloudflare Turnstile token
The key was created with a Turnstile site key and secret, and the lookup carried no token or one Cloudflare did not confirm.
A Turnstile-protected key needs a fresh turnstile_token with every lookup; tokens are single-use, so one cannot be reused across lookups. Nothing is billed for a refused lookup.
The form widget handles all of this when its script tag carries data-turnstile-site-key: it renders an invisible widget and fetches a token before each lookup.
**What to do.** Use the widget's data-turnstile-site-key attribute, or, with your own client, obtain a token from Turnstile before each request and send it as turnstile_token. A proxy in front of the endpoint must forward the token.
```json
{
"success": false,
"error": {
"code": "TURNSTILE_FAILED",
"message": "This publishable key requires a valid Cloudflare Turnstile token with every lookup.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
Reference: https://spaw.co/docs/errors/TURNSTILE_FAILED
## `UNKNOWN_FEEDBACK_PROVIDER` (HTTP 404): the bounce webhook path names an unsupported provider
The URL POST /api/v1/email/feedback/{provider}/{key} accepts postmark, ses, mailgun and sendgrid only.
Each provider path parses that provider's own webhook format and maps its events to delivered, bounced or complained. Any other name has no parser and is answered with this code.
**What to do.** Copy the exact webhook URL from the API keys page for your provider. For any other sending service, post normalized outcomes to POST /api/v1/email/feedback with your secret key instead.
```json
{
"success": false,
"error": {
"code": "UNKNOWN_FEEDBACK_PROVIDER",
"message": "Supported providers: postmark, ses, mailgun, sendgrid.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
Reference: https://spaw.co/docs/errors/UNKNOWN_FEEDBACK_PROVIDER
## `INVALID_FEEDBACK_KEY` (HTTP 401): the feedback key in the webhook URL is unknown
The fb_ key in a bounce webhook URL does not exist or was rotated.
Bounce webhooks authenticate through the key embedded in their URL, since sending providers cannot add custom headers. Rotating the key on the API keys page invalidates every URL that carried the old one.
**What to do.** Generate or reveal the current key on the API keys page and update the webhook URL at your sending provider.
```json
{
"success": false,
"error": {
"code": "INVALID_FEEDBACK_KEY",
"message": "That feedback key does not exist or was rotated.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}
```
Reference: https://spaw.co/docs/errors/INVALID_FEEDBACK_KEY
---
# Pricing
Spaw bills per answer. A lookup that comes back deliverable or risky costs 1 credit; undeliverable verdicts, invalid input, repeats within 7 days, and rejected requests cost nothing. Every account receives 10 free credits at the start of each month with no card on file, and unused credits accumulate.
## What costs a credit
| Outcome | Credits |
| --- | --- |
| Fresh lookup with a usable answer (deliverable or risky) | 1 credit |
| Undeliverable verdict | 0 |
| Invalid input or no match | 0 |
| Repeat of a recent lookup, within 7 days | 0 |
| Rejected or failed request | 0 |
| Monthly free grant, every account | +10 |
Batch, bulk, the dashboard, the form widget, and the MCP server all follow the same per-answer rule. A balance of zero answers a typed 402 INSUFFICIENT_CREDITS before any work runs.
## Credit packs
Packs are one-time purchases through Stripe Checkout, bought from the dashboard's Billing page. Credits never expire and there is no subscription.
| Pack | Credits | Price (USD) | Per 1,000 credits | Status |
| --- | --- | --- | --- | --- |
| Starter | 1,000 | $9 | $9.00 | available |
| Growth | 5,000 | $39 | $7.80 | available |
| Scale | 25,000 | $149 | $5.96 | available |
| Volume | 100,000 | $499 | $4.99 | available |
## Questions
**Do credits expire?** No. Purchased and free credits stay on the balance until they are spent.
**What happens at zero balance?** The API answers 402 INSUFFICIENT_CREDITS before the lookup runs, so nothing is billed behind your back.
**Is there a subscription?** No. Packs are one-time purchases; the monthly free grant needs no card.
**Do batch and bulk cost differently?** No. Every channel bills the same 1 credit per answered lookup.
Reference: https://spaw.co/pricing
---
# About Spaw
Spaw is an email verification API at spaw.co. One key, one credit balance, and a verdict that names its reasons and its data sources.
Updated: 2026-09-03
Spaw is an email verification API, launched in 2026 at spaw.co. You send an address, and the API answers whether mail to it will arrive, why, and how much to trust the answer. The same engine sits behind a dashboard, bulk uploads, a browser form widget, and an MCP server for AI agents.
## What one lookup does
Every lookup runs the same pipeline, and the response shows every step of it:
- **Syntax.** The address is parsed against RFC 5322, then against the username rules of the big providers, so `a@gmail.com` is rejected the way Gmail itself would reject it.
- **Live DNS.** MX records are resolved at query time. A domain with no MX record but an A record is reported as implicit MX, a null MX record as a domain that declines all mail, and MX hosts that point at dead or private addresses as unresolvable.
- **SPF and DMARC.** The domain's authentication records are read and the DMARC policy is reported.
- **Open lists.** The domain is matched against the open disposable-provider list, including domains whose mail is handled by a listed operator, and the local part against the open role-address list.
- **Typo-squats.** A curated map catches `gmail.con` and friends and suggests the intended domain.
- **The mailbox itself.** An SMTP handshake asks the receiving server whether the mailbox exists. The conversation ends before any message is transmitted; nothing is ever sent to an address you check.
- **A risk score you can audit.** The 0 to 100 score is a published sum of weights, one per signal, not a black box.
## How billing works
You pay for answers, not attempts.
- A lookup that comes back deliverable or risky costs 1 credit.
- Undeliverable verdicts and invalid input are free.
- Repeating a lookup within 7 days is free.
- Every account gets 10 free credits at the start of each month, with no card required, and unused credits accumulate.
## What we hold ourselves to
- **Every response names its data sources**, with a version, so you can see exactly what the answer was based on.
- **Full data everywhere.** The free tools show the same fields as the paid API.
- **No data sales.** Addresses you submit are used to answer your query and for nothing else. Verification history is deleted after 30 days. The [privacy policy](/privacy) spells this out.
## Channels
- REST API at `https://spaw.co/api/v1`, documented in the [API reference](/docs/api)
- The dashboard, with a playground, history, bulk runs, monitors, and a suppression list
- Bulk uploads of up to 100,000 addresses with signed webhooks
- A browser form widget that checks addresses as people type
- An [MCP server](/docs/mcp) that lets AI agents verify addresses with your API key
Phone validation and IP intelligence are in development.
## Open data we build on
Spaw credits the open datasets it relies on: Google's libphonenumber, DB-IP Lite, the disposable/disposable-email-domains list, the mixmaxhq role-based address list, and the Kikobeats free-email-domains list.
## Contact
Questions, partnership requests, and press: [support@spaw.co](mailto:support@spaw.co).
---
# Security and data handling
How Spaw protects API keys, what happens to the addresses you verify, how long data is kept.
Updated: 2026-09-03
Spaw is a verification API: the data you send is mostly other people's email addresses. This page states, specifically, what happens to it. It only describes measures that are in place today.
## Transport
All traffic to spaw.co, including the API, the dashboard, the form widget, and the MCP server, is served over HTTPS. Plain HTTP requests are redirected.
## API keys
- **Secret keys** (`sk_live_…`) are shown once at creation and stored hashed. Spaw cannot show a secret key again; if you lose it, create a new one and revoke the old one. Revocation is immediate.
- **Publishable keys** (`pk_…`) are made for the browser and are stored and displayed in plain text by design. They can only run email lookups, only from the origins you list, and can be limited with a daily credit cap. A key can also require a Cloudflare Turnstile token per lookup, which stops scripted abuse without ever blocking a real form submission.
- **Rate limits.** The API allows 5 requests per second per key. The browser endpoint and the free tools have their own per-visitor throttles.
- **Request ids.** Every response carries an `X-Request-Id` header, repeated inside the JSON body, so a support question can be matched to the exact request.
## The addresses you verify
- An address is used to answer your query and for nothing else. Spaw does not sell submitted data, build shared datasets from it, or contact the addresses.
- No message is ever sent to an address you check. The mailbox check is an SMTP handshake that ends before any message data is transmitted.
- Only addresses that reach the mailbox step are sent to the mailbox verification partner. Addresses at disposable domains, typo-squat domains, and domains with no working mail server are answered from DNS and open lists alone and never leave Spaw.
- The six `@spaw.test` test addresses answer fixed verdicts and are never logged.
## Retention
- **Lookup history** is kept for 30 days, then deleted automatically. Bulk job files follow the same window.
- **Repeat-lookup markers** last 7 days; they are what makes a repeat free.
- **Your suppression list and bounce feedback** are stored per account and stay until you remove them. Suppression entries older than 90 days are re-verified rather than trusted.
- **The credit ledger** is kept for the life of the account as your billing record.
- Deleting your account from the settings page removes the account, its keys, history, jobs, monitors, suppression list, and feedback.
## Reporting
Report a security issue, or ask anything about how your data is handled, at [support@spaw.co](mailto:support@spaw.co).
---
# Press and brand kit
How to describe Spaw, the facts that stay true, and the logo files. For directories, listings, articles, and anyone writing about the email verification API.
Updated: 2026-09-03
Everything on this page may be quoted as is. If a fact here disagrees with an older mention elsewhere, this page is the current one.
## One-line description
Spaw is an email verification API: one key, one credit balance, and a verdict that names its reasons and its data sources.
## Short description
Spaw verifies email addresses without sending mail. Every lookup checks RFC syntax, live DNS and MX records, SPF and DMARC, open disposable and role lists, provider username rules, and runs an SMTP mailbox handshake that never transmits a message. Verdicts are deliverable, risky, or undeliverable, each with a machine-readable reason and a 0 to 100 risk score built from published weights. Every account gets 10 free lookups a month with no card; you pay 1 credit per answered lookup and nothing for undeliverable results.
## Long description
Spaw (spaw.co) is an email verification API launched in 2026. It answers one question per address: will mail to it arrive, and how much should you trust that answer? A lookup runs the whole pipeline every time: syntax parsing after stripping display names and stray characters, live MX resolution with null-MX, implicit-MX and unresolvable-MX handling, SPF and DMARC records, the open disposable-provider and role-address lists, provider-specific username rules, a curated typo-squat map, domain registration age from RDAP, and an SMTP mailbox handshake that ends before any message is transmitted. The response carries 27 named fields, the datasets and versions each answer came from, and a risk score that is a published sum of weights rather than a black box.
Spaw bills for answers only. A deliverable or risky verdict costs 1 credit; undeliverable verdicts, invalid input, rejected requests, and repeats within 7 days cost nothing. Every account receives 10 free credits a month, and credit packs are one-time purchases that never expire. The same engine is available through a REST API with an OpenAPI 3.1 document, a dashboard with bulk uploads, a browser form widget with domain-locked publishable keys, a bounce feedback loop, scheduled list monitors, and an MCP server for AI agents. Phone validation and IP intelligence are in development.
## Facts
| Fact | Value |
| --- | --- |
| Name | Spaw |
| Website | https://spaw.co |
| Category | Email verification API; developer tools; email deliverability |
| Launched | 2026 |
| Pricing | 10 free lookups a month; 1 credit per answered lookup; packs from $9 for 1,000 credits |
| Free tools | Email checker, MX lookup, SPF and DMARC checker, disposable domain checker |
| Integrations | REST API, OpenAPI 3.1, Python, Node.js and PHP clients, browser widget, WordPress plugin, MCP server |
| Documentation | https://spaw.co/docs |
| Contact | support@spaw.co |
## Naming
Write "Spaw", capital S, no article: "Spaw verifies…", not "the Spaw". The domain is spaw.co. Do not describe Spaw as a proxy service, a mail sender, or a data broker; it does none of those things and never sends mail on anyone's behalf.
## Logo files
- Mark, SVG: https://spaw.co/logo.svg
- Icon, 512 px PNG: https://spaw.co/icons/icon-512.png
- Icon, maskable 512 px PNG: https://spaw.co/icons/icon-maskable-512.png
- Favicon, SVG: https://spaw.co/favicon.svg
The mark is a white check on an emerald tile (#059669). Keep the tile's corner radius and do not recolor the check.
## Screenshots and quotes
Screenshots of the dashboard and the free tools may be taken from spaw.co and published with a link back. For quotes, technical questions, or a walkthrough, write to support@spaw.co.
---
# Disposable email domains and how detection works
Temporary inboxes live minutes. How the open disposable-domain list is built, why matching on MX hosts catches new burners, and what Spaw returns for one.
Date: 2026-09-03
A disposable email domain belongs to a service that hands out throwaway inboxes: you pick or receive an address, read the one message you were waiting for, and the mailbox expires minutes or hours later. Detection is a list problem with a DNS twist. An open list catches the domains people already know about, and matching a domain's mail servers against the same list catches the new ones, because a fresh burner domain still routes mail through an operator that is already listed.
## What is a disposable email domain?
Services such as Mailinator, Guerrilla Mail, 10 Minute Mail and hundreds of smaller clones run thousands of domains whose only purpose is to receive mail nobody intends to keep. Some inboxes are public: anyone who guesses the address can read it. Most expire on a timer. None of them belong to a customer who wants to hear from you.
The same infrastructure also powers legitimate uses: testing a signup flow, avoiding a newsletter you were forced to subscribe to, or protecting privacy from a site you do not trust. The address is real and accepts mail. It is simply not a relationship.
## Why do people use them?
Three motives dominate. Fraud and abuse: creating many accounts to farm free trials, coupons or votes. Privacy: not wanting a permanent address in yet another database. Friction: a site demanded an email address for something that did not need one. The third group is the largest, which is why the right response depends on what your form is for.
## How does detection work?
Spaw combines three layers, in this order.
**The open list.** The domain is matched against the `disposable/disposable-email-domains` project, an open dataset of tens of thousands of burner domains, synced weekly and unioned with a bundled seed so detection works before the first sync. Matching includes parent domains, so a subdomain a burner service hands out is caught by its parent.
**Local overrides.** Two small files in the repository correct the upstream: an allowlist for false positives, which always wins, and a denylist for burners the upstream has not caught yet. Either beats waiting for the next sync.
**MX host matching.** New burner domains appear every day, faster than any list can follow. What does not change as fast is the mail infrastructure behind them. Spaw resolves the domain's MX records and matches those hosts against the same list, so a domain registered this morning that routes mail through a listed operator's servers is caught on its first lookup.
| Layer | Catches | Misses |
| --- | --- | --- |
| Open list, with parent-domain matching | Known burner domains and their subdomains | Domains created since the last weekly sync |
| Allow and deny overrides | Corrections either way, effective immediately | Anything nobody has reported yet |
| MX host matching | New domains on known burner infrastructure | Burners on their own, unlisted mail servers |
## Why is the list never complete?
Burner operators register domains in bulk precisely to stay ahead of lists, and a domain that is on every list gets rotated out. That is why the third layer exists, and why a disposable check that only looks the domain up in a table will always run a week or more behind. It is also why an address that passed a disposable check at signup can be on the list a month later; a scheduled monitor re-verifies a list on a cadence and reports when its makeup changes.
## What does Spaw return for a disposable address?
The verdict is `risky` with `reason: "disposable"` and `disposable: true`. The mailbox is never probed, since spending a handshake on a burner would only confirm what the list already says, so `smtp_checked` is `false`. The disposable flag adds 80 to the risk score, the high band on its own.
```bash
curl https://spaw.co/api/v1/email \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{"email": "disposable@spaw.test"}'
```
```json
{
"success": true,
"data": {
"email": "disposable@spaw.test",
"deliverable": "risky",
"reason": "disposable",
"risk_score": 90,
"risk_level": "high",
"disposable": true,
"smtp_checked": false,
"mailbox_exists": null,
"// 19 more fields": "see the endpoint reference"
},
"meta": { "credits_used": 0, "credits_remaining": 10, "cache_hit": false }
}
```
That test address is free. A real disposable answer is a risky answer and costs 1 credit, because it is a usable answer: you learned the address should not go on a list. A typo-squat domain that also sits on the disposable list is reported as `likely_typo` instead, since the correction is the more useful fact.
The domain endpoint answers the same question for a whole domain, with `disposable: true` or `false`, alongside its provider, SPF and DMARC records and registration age.
## Should you block disposable addresses?
It depends on what the address is for.
| Form | Recommendation |
| --- | --- |
| Free trial, credits, coupons, anything with value per account | Block, and say why. Ask for a different address. |
| Newsletter or marketing signup | Block, or accept and never send. Nobody reads a burner twice. |
| Purchase receipt or transactional flow | Accept. The customer chose where the receipt goes, and the message must be sent. |
| Contact or support form | Accept. Replying to a burner costs you nothing, and the sender chose the risk. |
| Existing list before a campaign | Remove. Burner domains attract spam-trap conversions and never engage. |
When you block, tell the user what happened and let them correct the address. The form helper that ships with Spaw runs the check on blur and shows the verdict inline without ever blocking the submit; blocking is a decision for your server.
## Is my domain wrongly listed?
It happens: an open list occasionally includes a small privacy-focused provider or a company that once hosted mail for a burner service. The domain endpoint shows the current answer and the dataset version that produced it. Spaw's allowlist fixes a confirmed false positive immediately, ahead of any upstream change; send the domain to support@spaw.co with a line about who runs it.
## What to do next
- Check a domain in the [free disposable domain checker](/tools/disposable-email-checker); each result has a permalink you can share.
- Read the [`disposable` reason page](/docs/reasons/disposable) for the exact contract, including why the mailbox is not probed.
- Ask the [domain intelligence endpoint](/docs/api/domain-intelligence) for the domain's provider, records and age in one call.
- Schedule a list monitor from the dashboard so burners that appear on the list later are caught before the next send.
---
# Email bounce rate benchmarks and what "acceptable" means
Published averages put hard bounces near 0.2% and soft bounces under 1% for opted-in lists. What mailbox providers enforce, and how to set your own target.
Date: 2026-09-03
For opted-in marketing lists, the published averages sit around 0.2% hard bounces and 0.7% soft bounces per send, with the worst industries still under 1% hard. Anything above 2% total bounces on a regular send is a list problem, and above 5% a reputation problem in progress. The mailbox providers do not publish a bounce threshold, but they publish a spam-complaint threshold of 0.3%, and the two move together: dead addresses, recycled traps and unengaged recipients are the same list.
## What do the published benchmarks say?
The most widely cited figures come from Mailchimp's platform data as analysed by Mailerio in 2025: across billions of emails the average hard bounce rate was [0.21% and the average soft bounce rate 0.70%](https://mailerio.com/blog/email-bounce-rate-benchmark/). By industry, the same analysis reports the lowest bounce rates for daily deals and e-coupon senders at 0.17% and the highest for software and web apps at 0.93%, as of the June 2025 publication.
| Figure | Value | Source |
| --- | --- | --- |
| Average hard bounce rate | 0.21% | [Mailerio's analysis of Mailchimp data, 2025](https://mailerio.com/blog/email-bounce-rate-benchmark/) |
| Average soft bounce rate | 0.70% | same |
| Lowest industry (daily deals and e-coupons) | 0.17% | same |
| Highest industry (software and web apps) | 0.93% | same |
Those numbers describe senders on a platform that already suppresses hard bounces automatically and enforces opt-in. A list that has never been verified, was collected years ago, or came from a partner will bounce at many times these rates on its first send, which is exactly the situation a benchmark is least useful for.
Other benchmark reports circulate with higher overall bounce figures, often in the 2% to 3% range across all industries. They are quoted widely but the source pages were not reachable for verification while this guide was written, so they are described here only qualitatively: they generally count all bounces together and include less strictly opted-in traffic, which explains most of the gap.
## What do the mailbox providers enforce?
Gmail and Yahoo publish requirements rather than benchmarks, and the requirements are what get mail blocked.
Google's [sender guidelines](https://support.google.com/a/answer/81126), in force since February 1, 2024, apply to anyone sending more than 5,000 messages a day to Gmail accounts: authenticate with SPF and DKIM, publish a DMARC policy, support one-click unsubscribe, and keep the spam rate reported in Postmaster Tools below 0.10%, never reaching 0.30% or higher. Yahoo's [sender requirements](https://senders.yahooinc.com/best-practices/) match: SPF and DKIM, a DMARC policy of at least `p=none` that passes, one-click unsubscribe, a spam rate below 0.3%, and, in their words, monitoring hard and soft bounces and removing invalid recipients promptly.
Neither publishes a numeric bounce threshold, but both treat bounce behaviour as a reputation input. A sender that keeps hitting `550 5.1.1` replies at a provider is doing what an address harvester does, and providers throttle or defer such senders long before any published number is crossed.
## Which tiers are used in practice?
Deliverability teams tend to work with tiers rather than a single number. One commonly quoted framing, published by [verified.email](https://verified.email/blog/email-deliverability/email-bounce-rate-benchmark) as its own synthesis of several reports, puts total bounces under 1% as excellent, 1% to 2% as acceptable, 2% to 5% as concerning, and above 5% as dangerous. Treat the tiers as a working convention rather than a measured fact; the measured facts are the platform averages above and your own history.
| Total bounce rate on a regular send | Reading |
| --- | --- |
| Under 1% | Healthy, opted-in, verified list |
| 1% to 2% | Normal for a list that has not been verified in a while |
| 2% to 5% | Clean the list before the next send |
| Above 5% | Stop, verify everything, and expect reputation repair to take weeks |
## Why do bounce rates rise?
Addresses die at a steady rate: people change jobs, companies rename domains, consumers abandon inboxes. A list that was perfect at collection decays every month it is not checked, and the decay is invisible until the next send turns it into bounces. Three sources dominate.
Typos at signup, which a form-time check catches before they enter the list. Job changes on B2B lists, which turn a confirmed corporate mailbox into a `550 5.1.1` within a year. And old consumer addresses that mailbox providers first disable and then, in some cases, recycle into spam traps, so that the address stops bouncing and starts hurting you instead.
## How do you measure your own rate correctly?
Count hard and soft bounces separately, per send, over the delivered attempts of that send. Exclude `5.7.x` policy rejections from the address side of the ledger, since those are about your authentication and reputation rather than the recipient; they belong in a different chart. Track the rate per segment and per acquisition source, because a 0.3% overall figure can hide a partner list bouncing at 8%.
Spaw's feedback loop makes the measurement part of the verification data. Post delivered and bounced outcomes from your sending platform, or connect its bounce webhook, and the feedback summary endpoint reports your outcomes crossed with the verdicts on file over the last 90 days: how many addresses verified as deliverable actually delivered, how many bounced anyway, and where the risky segment landed.
```bash
curl https://spaw.co/api/v1/email/feedback/summary \
-H "Authorization: Bearer sk_live_…"
```
The same reports change future answers. A bounce joins your suppression list, a delivery turns later lookups of that address into a confirmed mailbox, and the per-domain bounce rate feeds the confidence estimate for catch-all addresses at that domain.
## How do you get under the benchmark?
Verify before the first send and on a schedule after it. A bulk verification run removes the addresses that would hard bounce, and undeliverable answers cost nothing, so the price of the run is the share of the list that is still good. Segment the risky verdicts rather than sending to them blindly, warm the catch-all segment in small batches, and keep a monitor on the list so decay shows up in a report instead of in a send.
Set your own target from your own history rather than from a published average. A B2B list that turns over 20% of contacts a year will never match a consumer newsletter's 0.2%, and does not need to; it needs a verification cadence that keeps each send under the tiers above.
## What to do next
- Run the list through the [cleaning checklist](/guides/email-list-cleaning-checklist) before the next campaign.
- Verify it with one [bulk job](/docs/api/create-bulk-job); undeliverable answers are free.
- Connect your bounce webhook to the [feedback endpoint](/docs/api/report-delivery-feedback) and read your measured accuracy from the [summary endpoint](/docs/api/feedback-summary).
- Learn which replies count as hard bounces in [SMTP bounce codes explained](/guides/smtp-bounce-codes-explained).
---
# Email list cleaning checklist before a campaign
Ten steps in order: normalize and dedupe, import known bounces, verify in bulk, segment by verdict, authenticate, warm up, then keep it clean with monitors.
Date: 2026-09-03
Cleaning a list means removing addresses that will bounce, segmenting the ones that carry risk, and setting up the feedback that keeps the list clean after the send. Done in the right order it takes one bulk verification run and a few filters, and the order matters because every earlier step makes the later ones cheaper: duplicates and known bounces cost nothing to verify, so remove them first.
## 1. Normalize and dedupe
Lowercase every address, strip plus-tags, fold the dots out of Gmail usernames, and unwrap the forms people paste: display names, `mailto:` links, quotes, trailing punctuation. `Mia.K+promo@gmail.com` and `miak@gmail.com` are the same inbox. Spaw does this on every lookup and returns the result as `normalized_email`; bulk uploads count repeated normalized addresses as `duplicate_count` and verify them once.
## 2. Import what you already know is bad
If your sending platform has a bounce or unsubscribe export, load the hard bounces into your suppression list before verifying anything. Imports are free, take up to 10,000 addresses per request, and a suppressed address is answered without any work in every later batch, bulk and monitor run.
```bash
curl https://spaw.co/api/v1/email/suppressions \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{"emails": ["old@acme.com", "gone@example.org"]}'
```
## 3. Verify the whole list in one bulk job
Upload the file as it is. A bulk job keeps your columns, appends the verdict columns, and answers up to 100,000 addresses per job with a signed webhook when it finishes. Undeliverable answers, duplicates and suppressed addresses are free; you pay one credit per deliverable or risky answer.
```bash
curl https://spaw.co/api/v1/email/bulk \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{"emails": ["mia@acme.com", "info@example.org"], "webhook_url": "https://www.example.com/hooks/spaw"}'
```
```json
{
"success": true,
"data": {
"job": {
"id": 512,
"status": "queued",
"total": 2,
"duplicate_count": 0,
"webhook_secret": "8fJ2…40 characters…Qk1",
"// 10 more fields": "see the endpoint reference"
}
}
}
```
Download the results as one CSV, or with `?variant=deliverable`, `risky` or `undeliverable` to get each segment as its own file.
## 4. Segment by verdict and reason
Three verdicts, and inside `risky` the reason decides the action.
| Verdict and reason | Action |
| --- | --- |
| `undeliverable`, any reason | Remove. Already on your suppression list. |
| `risky` · `disposable` | Remove from marketing sends. |
| `risky` · `likely_typo` | Fix with `did_you_mean` only if you can confirm with the contact; otherwise remove. |
| `risky` · `role` | Keep for B2B and transactional mail; exclude from promotional sequences. |
| `risky` · `catch_all` | Send in moderation; branch on `mailbox_confidence`. |
| `risky` · `mailbox_full` | Hold, retry in a few days. |
| `risky` · `implicit_mx` | Treat as probably dead unless the handshake confirmed the mailbox. |
| `deliverable`, `smtp_checked: false` | Send, but expect a few bounces; a `callback_url` re-check settles many of these for free. |
| `deliverable`, `mailbox_exists: true` | Send. |
The `risk_score` gives the same information as one number for tools that need a single threshold: 60 and above is high, 30 to 59 medium. Because the score is a published sum of weights, you can recompute it from the booleans in the CSV if your policy differs.
## 5. Decide the disposable and role policy per list
Marketing lists lose both. B2B lead lists keep role addresses and lose burners. Transactional recipients keep everything that is not undeliverable, because the message must be sent to the address the customer chose. Write the policy down; it is the difference between a clean list and a list that gets cleaned differently every quarter.
## 6. Check engagement, not just deliverability
A mailbox that exists but has not opened anything in a year is a complaint waiting to happen and, at some providers, a recycled spam trap waiting to be created. Set an engagement window that fits your cadence, move addresses past it into a re-permission segment, and stop sending to the ones that do not respond.
## 7. Authenticate before you send
SPF, DKIM and DMARC are required for bulk mail at Gmail and Yahoo, and a `550 5.7.x` reply from a receiver is about your authentication, not the recipient. Check your records before the campaign, and check the DMARC policy of your own domain is at least `p=none` with a reporting address.
## 8. Warm up the segments you are unsure about
Send the confirmed deliverable segment first, then the risky segments in small daily batches while watching bounces and complaints. A catch-all segment that bounces at 5% needs a lower confidence threshold, not a bigger batch.
## 9. Feed outcomes back
Point your sending provider's bounce webhook at Spaw, or post delivered and bounced events yourself. Hard bounces join the suppression list automatically, deliveries turn later lookups into confirmed mailboxes, and the per-domain bounce rate feeds the confidence score for every catch-all address at that domain. The dashboard shows measured accuracy: your outcomes crossed with the verdicts on file.
## 10. Keep it clean with a monitor
Lists decay. Schedule a monitor on the list from the dashboard and it re-verifies on a cadence, applies the suppression list, and alerts you when the deliverable share drops. Repeats of an address inside seven days are free, and undeliverable answers are always free, so the ongoing cost is the share of the list that is still good.
## What does it cost?
| Item | Credits |
| --- | --- |
| Duplicate of an address already in the run | 0 |
| Address on your suppression list | 0 |
| Undeliverable verdict | 0 |
| Deliverable or risky verdict | 1 |
| Repeat of a charged lookup within 7 days | 0 |
| Monthly free grant, every account | +10 |
A 50,000-address list that is 20% dead and 5% duplicates costs at most 37,500 credits to verify once, and the monitor that keeps it clean costs only for addresses whose answer changes.
## What to do next
- Create the [bulk job](/docs/api/create-bulk-job) with your file as it is; the columns come back with the verdicts appended.
- Import your platform's bounce export into the [suppression list](/docs/api/add-suppressions) first.
- Decide the segment policies with the [role](/guides/role-based-email-addresses) and [disposable](/guides/disposable-email-domains) guides.
- Set targets using the [bounce rate benchmarks](/guides/email-bounce-rate-benchmarks) and the mailbox providers' own thresholds.
---
# How Spaw computes the risk score
The risk score is a sum of published weights over documented flags, capped at 100, with undeliverable fixed at 100. Every weight, thresholds, examples.
Date: 2026-09-03
Spaw's `risk_score` is not a model. It is the sum of fixed weights over the boolean flags already in the response, capped at 100, and an undeliverable verdict is always exactly 100. You can recompute it from the fields next to it, branch on the flags instead, or change the weights in your own code if your policy differs. This page lists what the code does, weight by weight.
## Which flags carry weight?
| Signal | Field it comes from | Points |
| --- | --- | --- |
| Disposable domain | `disposable: true` | +80 |
| Known typo-squat domain | `reason: "likely_typo"` | +60 |
| Domain has no MX record, only the address-record fallback | `mx_implicit: true` | +30 |
| Role address | `role: true` | +30 |
| Catch-all server | `catch_all: true` | +30 |
| Domain registered less than 30 days ago | `domain_age_days` under 30 | +30 |
| Mailbox full | `smtp_reason: "mailbox_full"` | +20 |
| Gibberish username | `is_gibberish: true` | +20 |
| Domain registered less than 180 days ago | `domain_age_days` under 180 | +10 |
| No SPF record on a business domain | `has_spf: false` and `free_provider: false` | +10 |
| Mailbox not SMTP-verified | `smtp_checked: false` | +10 |
| Plus-tag alias | `is_alias: true` | +5 |
The age rows are exclusive: a domain under 30 days gets 30, one between 30 and 179 days gets 10, older domains and unknown ages get nothing. The SPF row only applies to business domains, because a Gmail user's risk has nothing to do with Google's SPF record. The sum is capped at 100.
## What are the levels?
| `risk_score` | `risk_level` |
| --- | --- |
| 60 and above | `high` |
| 30 to 59 | `medium` |
| 0 to 29 | `low` |
An undeliverable verdict skips the sum entirely and reports 100 and `high`.
## Worked examples
| Address and situation | Flags | Score |
| --- | --- | --- |
| `mia@acme.com`, mailbox confirmed, old domain with SPF | none | 0, low |
| `support@acme.com`, mailbox confirmed | role 30 | 30, medium |
| `mia+news@acme.com`, catch-all server | catch-all 30, alias 5 | 35, medium |
| `mia@newstartup.example`, registered 20 days ago, no SPF, greylisted | young domain 30, no SPF 10, unverified 10 | 50, medium |
| `mia@gmail.con` | typo-squat 60 | 60, high |
| `x9@mailinator.com` | disposable 80, unverified 10 | 90, high |
| `qzxvbn@acme.com`, catch-all server, no SPF | gibberish 20, catch-all 30, no SPF 10 | 60, high |
| `nobody@acme.com`, server says no such user | undeliverable | 100, high |
Disposable domains are never probed, so the unverified weight always joins the disposable one; that is why the disposable test address answers 90 rather than 80.
## What counts as gibberish?
The check is deliberately conservative, because a false positive here insults a real person. A username is only considered when it has six or more letters. Anything the name dictionary recognises, a first name, a surname, `first.last`, an initial plus a surname, or the two run together, is never gibberish. Beyond that, a username is flagged if it contains no vowel at all, with `y` counted as a vowel to protect names like Krzysztof, or if it contains a run of six consonants. Digit-only usernames are never flagged; numeric IDs at large Chinese providers are ordinary.
## How does the verdict interact with the score?
The verdict comes first and is decided by precedence, not by the score. Undeliverable reasons win over everything: null MX, unresolvable MX, no mail server, an impossible username, a disabled mailbox, a missing mailbox, in that order. Among risky reasons the order is typo-squat, disposable, implicit MX, mailbox full, role, catch-all. The score then adds up every flag that applies, so an address reported as `role` on a catch-all server carries both weights even though `reason` names only one.
```bash
curl https://spaw.co/api/v1/email \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{"email": "mia@acme.com"}'
```
```json
{
"success": true,
"data": {
"email": "mia@acme.com",
"deliverable": "deliverable",
"reason": null,
"risk_score": 0,
"risk_level": "low",
"is_alias": false,
"is_gibberish": false,
"disposable": false,
"role": false,
"catch_all": false,
"has_spf": true,
"free_provider": false,
"domain_age_days": 10402,
"smtp_checked": true,
"// 13 more fields": "see the endpoint reference"
},
"meta": { "credits_used": 1, "credits_remaining": 9, "cache_hit": false }
}
```
Every flag the score reads is in that response, which is what makes it recomputable.
## How is mailbox_confidence different?
The risk score answers "how risky is sending here". `mailbox_confidence` answers a narrower question, "does this specific mailbox exist", and only when the handshake could not say: catch-all domains and unanswered probes. It is null for confirmed mailboxes and for undeliverable verdicts.
It starts at 50 and moves by fixed amounts.
| Signal | Adjustment |
| --- | --- |
| Your own delivery outcomes for the domain, five or more reported: bounce rate 50% or higher | −20 |
| Your own delivery outcomes for the domain, five or more reported: bounce rate 10% or lower | +15 |
| DMARC policy reject or quarantine | +15 |
| DMARC policy none | +5 |
| SPF record present | +10 |
| SPF record absent | −10 |
| Mail hosted at Google, Microsoft, Proofpoint, Mimecast, Barracuda, Zoho or Fastmail | +10 |
| Username recognised as a name | +15 |
| Username flagged as gibberish | −25 |
| Role address | +5 |
| Domain registered under 90 days ago | −15 |
| Domain registered two years ago or more | +10 |
| Plus-tag alias | −10 |
The result is clamped to 0 to 100. Only your own account's delivery reports are used; nobody else's outcomes influence your answers.
## How should you use the score?
Branch on the flags when you have a policy, and on the score when you need one threshold. A typical signup form rejects undeliverable, asks for another address on disposable, and accepts the rest. A typical campaign sends to scores under 30, holds 30 to 59 for a warm-up batch, and drops 60 and above. If your bounce data says the weights are wrong for your audience, recompute the score with your own weights from the same flags; nothing about the API assumes you use its number.
## What to do next
- Read the [verdict reasons](/docs/reasons) to see which flag each reason sets and what it costs.
- Wire your bounce webhook into the [feedback endpoint](/docs/api/report-delivery-feedback) so the confidence score learns from your own sends.
- See how the catch-all case plays out in [what a catch-all address is](/guides/catch-all-email-addresses).
- Check the authentication rows against your own domain with [SPF, DKIM and DMARC for developers](/guides/spf-dkim-dmarc-for-developers).
---
# How to verify an email address without sending an email
Parse the address, resolve the domain's mail servers, then ask the server about the mailbox in an SMTP handshake that ends before any message is sent.
Date: 2026-09-03
You can verify an email address without sending anything by doing exactly what a sending mail server does up to the moment it would transmit the message: parse the address, look up the domain's mail servers in DNS, open an SMTP session, ask whether the recipient exists, and disconnect. The server's reply to that question is the verification. No message is transmitted, nothing lands in an inbox or a spam folder, and the recipient never knows.
## What happens in an SMTP handshake?
SMTP is a conversation. A sending server connects to one of the recipient domain's mail exchangers on port 25 and the two exchange a fixed sequence of commands before any content moves. The recipient check happens at the `RCPT TO` step, which is why a verifier can stop right after it.
| Step | Command | What the reply tells a verifier |
| --- | --- | --- |
| 1 | connect | Whether the host answers on port 25 at all |
| 2 | `EHLO` | Which extensions the server supports; nothing about the mailbox yet |
| 3 | `MAIL FROM` | Whether the server accepts the sender at all |
| 4 | `RCPT TO` | Whether the server accepts mail for this exact recipient: `250` yes, `550` no, `4xx` come back later |
| 5 | `QUIT` | The verifier leaves without ever sending `DATA` |
A real message only starts with `DATA`, the step a verifier never issues. Everything before it is a question, and questions do not generate bounces or deliveries.
## Why not just send a test email?
Sending a test message tells you less than a handshake and costs more. A test to a dead address produces a bounce, and bounces are the metric mailbox providers use to judge your reputation. A test to a spam trap gets your domain listed. A test to a real person is an unsolicited message. And the answer arrives asynchronously, minutes or hours later, in the form of a bounce notification you then have to parse.
The handshake answers in a second or two, synchronously, and leaves no trace on the receiving side beyond a log line.
## Which checks come before the handshake?
Most addresses are settled before any SMTP connection is opened, which is cheaper and often more decisive.
| Check | What it catches | Spaw result |
| --- | --- | --- |
| RFC 5322 syntax | Missing `@`, spaces, malformed domains | `invalid_syntax` |
| Provider username rules | Usernames Gmail, Outlook, Yahoo, iCloud, AOL or Proton would never issue | `invalid_local_part` |
| DNS: MX and address records | Domains with no mail server, a null MX, or MX targets that do not resolve | `no_mx_records`, `null_mx`, `mx_unresolvable` |
| Implicit MX | Domains with only an A record, which almost never accept mail | `implicit_mx` |
| Disposable list | Temporary inbox providers, including new burner domains routed through a listed operator's mail hosts | `disposable` |
| Role list | Shared inboxes such as `info@` or `billing@` | `role` |
| Typo-squat map | `gmail.con`, `hotmial.com` and similar, with a suggested correction | `likely_typo` |
Every one of those is a live DNS query or an open list lookup. The DNS part matters more than it looks: a domain whose MX records point at loopback or private addresses will never receive mail, and a domain publishing a null MX record has declared that it does not want any. Both are answered without a handshake, and Spaw answers them for free.
## What can the handshake not tell you?
Four things, and an honest verifier reports each of them instead of guessing.
A **catch-all server** answers `250` to every recipient, so the reply proves nothing about the specific mailbox. Corporate domains behind security gateways and many Microsoft 365 tenants work this way. Spaw reports `catch_all: true` with a risky verdict and adds `mailbox_confidence`, a recomputable estimate built from the domain's other signals.
**Greylisting** answers the first connection from an unknown sender with a temporary failure and expects a retry minutes later. A verifier cannot wait, so the mailbox stays unverified.
**Timeouts and refused connections** happen when a server slows sessions down on purpose, or when a firewall sits in front of it.
**Providers that hide the answer.** Some large providers accept every `RCPT TO` and reject later, or throttle verification traffic aggressively. Their addresses come back unverified more often than others.
In all four cases Spaw returns `smtp_checked: false`, `mailbox_exists: null` and an `smtp_reason` naming the cause, and the verdict rests on the checks above. You can ask for a second attempt: pass `callback_url` and the address is re-checked after 5 and 20 minutes, with the settled verdict pushed to you at no cost.
## How to do it with the Spaw API
One request, one credit for a deliverable or risky answer, nothing for an undeliverable one.
```bash
curl https://spaw.co/api/v1/email \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{"email": "mia@acme.com"}'
```
```json
{
"success": true,
"data": {
"email": "mia@acme.com",
"deliverable": "deliverable",
"reason": null,
"risk_score": 0,
"mx_found": true,
"mx_provider": "google",
"smtp_checked": true,
"mailbox_exists": true,
"catch_all": false,
"smtp_reason": null,
"// 16 more fields": "see the endpoint reference"
},
"meta": { "credits_used": 1, "credits_remaining": 9, "cache_hit": false }
}
```
The same lookup runs behind the free email checker, the dashboard, the batch endpoint for up to 50 addresses, and bulk jobs for up to 100,000. Six fixed addresses at `spaw.test`, such as `undeliverable@spaw.test`, answer canonical verdicts at no cost for integration tests.
## How to do it yourself
The handshake is simple enough to type by hand, which is the best way to understand it.
```text
$ nc aspmx.l.google.com 25
220 mx.google.com ESMTP
EHLO verifier.example.com
250-mx.google.com at your service
MAIL FROM:
250 2.1.0 OK
RCPT TO:
550-5.1.1 The email account that you tried to reach does not exist.
QUIT
221 2.0.0 closing connection
```
Doing this at scale is where the difficulty starts. Most cloud providers block outbound port 25 by default. Receiving servers check the connecting IP's reverse DNS and reputation, and an IP that opens thousands of sessions without ever sending mail looks like an address harvester and gets throttled or blocked. Greylisting needs retry queues, catch-all detection needs per-domain memory, and every provider has its own quirks. That is the work a verification service does for you, and it is why Spaw runs the handshake through partner infrastructure with the guards described above rather than from the same machines that serve the API.
## What to do next
- Try an address in the [free email checker](/tools/email-checker) and read the `reason` and `smtp_reason` fields against the [verdict reference](/docs/reasons).
- Verify a list before a campaign with the [batch](/docs/api/verify-email-batch) or [bulk](/docs/api/create-bulk-job) endpoint; undeliverable answers are free.
- Report delivery outcomes back through the [feedback endpoint](/docs/api/report-delivery-feedback) so catch-all and unverified addresses at your domains get settled by real deliveries.
- Read [what a catch-all address is](/guides/catch-all-email-addresses) before deciding how to treat risky verdicts.
---
# Role-based email addresses and how to treat them
info@, support@, billing@ and about a thousand more belong to a function, not a person. How Spaw detects them, why senders flag them, and when to keep them.
Date: 2026-09-03
A role-based email address is one whose username names a job or a group rather than a person: `info@`, `support@`, `sales@`, `billing@`, `admin@`, `noreply@`. The mailbox is usually real and usually monitored, but by a rotating set of people, through filters and forwarding rules you cannot see, which is why marketing senders treat it differently from `mia@`.
## What is a role-based address?
The distinction is purely about the part before the `@`. A role address is addressed to a function. Mail to `support@acme.com` goes to whoever is on the support rota this week; mail to `mia.k@acme.com` goes to Mia. The domain, the mail server and the handshake are identical for both.
Because the local part is the only signal, detection is a list lookup. Spaw uses the open role-address list maintained by Mixmax, synced weekly and unioned with a bundled seed, and matches the lowercased username exactly. The list has grown past a thousand entries, from the obvious `info` and `sales` to `abuse`, `postmaster`, `accessibility`, `hr`, `jobs`, `press`, `security` and dozens of localized variants.
| Group | Typical usernames |
| --- | --- |
| Front door | info, hello, contact, enquiries, office, mail |
| Sales and marketing | sales, marketing, partnerships, press, media |
| Support and operations | support, help, helpdesk, service, ops, it |
| Money | billing, invoices, accounts, finance, payments |
| Technical and abuse | admin, administrator, webmaster, postmaster, abuse, security, noc |
| People | hr, jobs, careers, recruiting, team |
| Machines | noreply, no-reply, donotreply, mailer-daemon, bounce, notifications |
## Why do senders treat role addresses differently?
Four practical reasons, all of them about outcomes rather than the address itself.
**Nobody opted in.** A person who types `sales@` into your form is often not the person who will read your mail, and the people who do read it did not ask for it. That is the setup for spam complaints, and complaints are the metric mailbox providers weigh most heavily.
**They churn silently.** Role inboxes are forwarded, split, auto-replied and abandoned as teams change. The address keeps accepting mail long after anyone reads it, so engagement drops without a bounce to warn you.
**They are shared.** One person unsubscribing does not stop the others from seeing the next message, and one person reporting spam counts against you regardless.
**Sending platforms police them.** Several email service providers refuse role addresses on list import, hold them for review, or exclude them from campaigns by default. A list heavy with `info@` addresses can fail an import before you send anything.
None of this means role addresses bounce more. Many are the most reliable mailboxes at a company. The problem is consent and engagement, not deliverability.
## What does Spaw return for a role address?
The verdict is `risky` with `reason: "role"` and `role: true`, and the mailbox is still probed, so `mailbox_exists` can be `true`. The role flag adds 30 to the risk score, which is the medium band on its own.
```bash
curl https://spaw.co/api/v1/email \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{"email": "role@spaw.test"}'
```
```json
{
"success": true,
"data": {
"email": "role@spaw.test",
"deliverable": "risky",
"reason": "role",
"risk_score": 30,
"risk_level": "medium",
"role": true,
"smtp_checked": true,
"mailbox_exists": true,
"// 19 more fields": "see the endpoint reference"
},
"meta": { "credits_used": 0, "credits_remaining": 10, "cache_hit": false }
}
```
`role@spaw.test` is a fixed test address and free. A real role address is billed like any risky answer: 1 credit, repeats free for seven days.
Precedence matters when several signals apply. A role address at a disposable domain is reported as `disposable`, and a role address on a catch-all server is reported as `role` because the role flag ranks above catch-all in Spaw's verdict order; both flags still appear as booleans, and both weights are added to the score.
## When should you keep role addresses?
Keep them wherever the function is the point.
| Context | Treatment |
| --- | --- |
| B2B contact and quote forms | Accept. `procurement@` is exactly who you want. |
| Support ticket systems | Accept. Role inboxes are the normal case. |
| Transactional mail: receipts, invoices, alerts | Accept. `billing@` is the correct recipient for an invoice. |
| Newsletter and marketing signups | Ask for a personal address, or accept and segment out of promotional sends. |
| Purchased or scraped lists | Drop. Role addresses on a cold list are the fastest route to complaints. |
| Cold outreach sequences | Drop or route to a manual step. Personalized sequences to `info@` read as spam. |
A useful rule for signup forms: accept the address, since blocking `info@` at a B2B company loses real leads, but mark the contact as role-based and keep it out of drip campaigns until a person replies.
## How do you find role addresses in an existing list?
Verify the list. Every answer carries `role: true` or `false`, so a bulk run gives you the segment as a column, alongside the verdict and the risk score. From the dashboard, upload the file and filter the results; from the API, create a bulk job and download the CSV with the verdict columns added to your own.
```bash
curl https://spaw.co/api/v1/email/bulk \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{"emails": ["info@acme.com", "mia@acme.com"], "webhook_url": "https://www.example.com/hooks/spaw"}'
```
The job answers `202` with an id and a signing secret shown once; the completion webhook is signed with it. Undeliverable answers in the run are free, and addresses already on your suppression list are answered without any work.
## What to do next
- Read the [`role` reason page](/docs/reasons/role) for the exact fields and precedence.
- Run your signup list through the [batch endpoint](/docs/api/verify-email-batch) and branch on `role` before the next campaign.
- Follow the [email list cleaning checklist](/guides/email-list-cleaning-checklist) to decide what else to segment out.
- Check a single address in the [free email checker](/tools/email-checker).
---
# SMTP bounce codes explained, from 550 5.1.1 to 421 4.7.0
What each SMTP reply and enhanced status code means, which are hard bounces, and how a verification handshake maps them to smtp_reason values.
Date: 2026-09-03
An SMTP bounce code is the three-digit reply a receiving mail server gives when it refuses a message, usually followed by a dotted enhanced status code that says why. `550 5.1.1` means the mailbox does not exist and is a hard bounce; `452 4.2.2` means the mailbox is full and is a soft bounce; `421 4.7.0` means the server is throttling you and says nothing about the mailbox at all. Reading the second number is what turns a bounce log into a decision.
## How are SMTP reply codes structured?
[RFC 5321](https://www.rfc-editor.org/rfc/rfc5321) defines the three-digit reply. The first digit is the verdict, the second the category, the third a detail.
| First digit | Meaning | What a sender does |
| --- | --- | --- |
| 2xx | Success | Continue |
| 3xx | More input needed (only `354` after `DATA`) | Send the content |
| 4xx | Transient failure | Queue and retry later |
| 5xx | Permanent failure | Stop and bounce |
The second digit narrows it: `x0x` syntax, `x1x` informational, `x2x` connection, `x5x` mail system. In practice the second digit of the plain code is coarse, which is why almost every modern server appends an enhanced status code.
## What are enhanced status codes?
[RFC 3463](https://www.rfc-editor.org/rfc/rfc3463) adds a `class.subject.detail` triple after the reply code, and it is the part worth parsing.
| Class | Meaning |
| --- | --- |
| 2.x.x | Success |
| 4.x.x | Persistent transient failure: retrying may succeed |
| 5.x.x | Permanent failure: retrying will not help |
| Subject | Area |
| --- | --- |
| x.0.x | Other or undefined |
| x.1.x | Addressing: the recipient or sender address |
| x.2.x | Mailbox: full, disabled, over quota |
| x.3.x | Mail system: the receiving system itself |
| x.4.x | Network and routing: cannot reach the next hop |
| x.5.x | Mail delivery protocol: bad commands or sequence |
| x.6.x | Message content or media |
| x.7.x | Security or policy: authentication, spam, rate limits |
A `5.1.1` is therefore a permanent addressing failure, and `4.7.1` a transient policy failure. The subject tells you whether the problem is the address, the mailbox, the network or your own reputation, and that is the axis a list-hygiene decision turns on.
## Which codes will you actually see?
| Reply | Meaning | Bounce type | What it says about the address |
| --- | --- | --- | --- |
| `250 2.1.5` | Recipient accepted | none | The server will take mail for it; on a catch-all server that is all it proves |
| `550 5.1.1` | Bad destination mailbox address, "user unknown" | hard | The mailbox does not exist. Remove the address |
| `550 5.1.2` | Bad destination system address | hard | The domain part is wrong or unroutable |
| `550 5.1.3` | Bad destination mailbox syntax | hard | The address itself is malformed |
| `550 5.2.1` | Mailbox disabled, not accepting messages | hard | The account exists but is closed or suspended |
| `552 5.2.2` | Mailbox full, permanent | hard | Over quota and not expected to clear; treat as risky at best |
| `452 4.2.2` | Mailbox full, transient | soft | Over quota right now; retry in days |
| `450 4.2.1` | Mailbox temporarily unavailable | soft | Often a hold or migration; retry |
| `450 4.7.1` or `451 4.7.1` | Try again later, policy | soft | Greylisting: the server wants a retry from the same sender |
| `421 4.7.0` | Too many connections or messages | soft | Throttling of the sender; nothing about the address |
| `421 4.3.2` | System not accepting network messages | soft | The server is shutting the session; retry later |
| `451 4.4.1` or `4.4.2` | Connection or transmission failure | soft | Network trouble on the receiving side |
| `550 5.7.1` | Delivery not authorized, message refused | hard for this message | A policy rejection: blocklist, reputation, missing authentication. The mailbox may be fine |
| `550 5.7.26` | Unauthenticated email is not accepted | hard for this message | The sender failed SPF or DKIM alignment; fix authentication, not the list |
Two rules of thumb follow. A `5.1.x` or `5.2.1` reply is about the address and justifies removing it. A `5.7.x` reply is about you, the sender, and removing the address would hide a problem you need to fix.
## What is the difference between a hard and a soft bounce?
A hard bounce is a permanent `5xx` failure about the recipient: the mailbox or domain does not exist. Sending platforms suppress hard-bounced addresses automatically and mailbox providers count repeated hard bounces against your reputation. A soft bounce is a transient `4xx` failure: the mailbox is full, the server is busy, the message is too large. Senders retry soft bounces for a day or two and then give up.
The line blurs at the edges. A `552 5.2.2` is permanent by class but describes a full mailbox, and a `550 5.7.1` is permanent for this message but says nothing about the recipient. Classifying purely by the first digit gets both wrong, which is why the enhanced code matters.
## How does a verification handshake use these codes?
A verifier never sends `DATA`, so it only sees replies up to `RCPT TO`. That is enough to classify the mailbox, and Spaw reports the classification in `smtp_reason` next to the verdict.
| Server reply during the handshake | `smtp_reason` | Verdict |
| --- | --- | --- |
| `5.1.1` user unknown at `RCPT TO` | `no_mailbox` | `undeliverable`, reason `mailbox_not_found` |
| `5.2.1` mailbox disabled | `disabled` | `undeliverable`, reason `mailbox_disabled` |
| `5.2.2` or `4.2.2` mailbox full | `mailbox_full` | `risky`, reason `mailbox_full` |
| `4.7.x` come back later | `greylisted` | rests on the other signals, `smtp_checked: false` |
| No reply within the time limit | `timeout` | rests on the other signals |
| Connection refused on port 25 | `no_connect` | rests on the other signals |
| Host unreachable | `mx_unreachable` | rests on the other signals |
| `5.7.x` policy rejection or any ambiguous refusal | unverified, never guessed | rests on the other signals |
Only an explicit "no such user" becomes `no_mailbox`. A policy rejection during verification is not evidence about the mailbox, so Spaw leaves `mailbox_exists` at `null` rather than turning a reputation problem on the verifier's side into a bounce prediction for you.
```bash
curl https://spaw.co/api/v1/email \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{"email": "undeliverable@spaw.test"}'
```
```json
{
"success": true,
"data": {
"email": "undeliverable@spaw.test",
"deliverable": "undeliverable",
"reason": "mailbox_not_found",
"risk_score": 100,
"smtp_checked": true,
"mailbox_exists": false,
"smtp_reason": "no_mailbox",
"// 20 more fields": "see the endpoint reference"
},
"meta": { "credits_used": 0, "credits_remaining": 10, "cache_hit": false }
}
```
Undeliverable answers are always free, and the address is added to your suppression list so batch and bulk runs answer it without another handshake.
## What should you do with bounce codes from your own sends?
Feed them back. The codes your sending platform records after a real delivery attempt are stronger evidence than any handshake, in both directions: a delivery proves the mailbox, a `5.1.1` disproves it. Post the outcome to the feedback endpoint with the diagnostic string, or point your provider's bounce webhook at Spaw and let the adapters for Postmark, Amazon SES, Mailgun and SendGrid map their event types. Hard bounces join your suppression list, deliveries turn later lookups into confirmed mailboxes, and the per-domain bounce rate feeds the confidence score for catch-all addresses.
## What to do next
- Map your bounce log against the `smtp_reason` values on the [mailbox-check details page](/docs/reasons#smtp).
- Verify addresses before sending with the [handshake described here](/guides/verify-email-without-sending); the test address `undeliverable@spaw.test` shows the exact shape of a hard-bounce answer.
- Send your provider's bounce events to the [feedback endpoint](/docs/api/report-delivery-feedback) so `5.1.1` replies from real sends suppress addresses automatically.
- Treat every `5.7.x` reply as a sender problem and check your [SPF, DKIM and DMARC](/guides/spf-dkim-dmarc-for-developers) before touching the list.
---
# SPF, DKIM and DMARC for developers
What each record proves, what it looks like in DNS, how receivers combine them, and what Gmail and Yahoo require from bulk senders since 2024.
Date: 2026-09-03
SPF says which servers may send mail for a domain, DKIM signs each message so a receiver can prove it was not altered and came from the signing domain, and DMARC tells receivers what to do when neither check lines up with the address the recipient sees. All three are DNS TXT records, all three are checked on every delivery by Gmail, Yahoo and Microsoft, and since February 2024 the large mailbox providers refuse bulk mail that lacks them.
## What does SPF prove?
SPF, defined in [RFC 7208](https://www.rfc-editor.org/rfc/rfc7208), lists the hosts allowed to send mail using a domain in the SMTP envelope sender, the `MAIL FROM` address. The record is a TXT record at the domain itself.
```text
acme.com. IN TXT "v=spf1 ip4:203.0.113.0/24 include:_spf.google.com -all"
```
| Mechanism | Matches |
| --- | --- |
| `ip4:` / `ip6:` | A literal address or range |
| `a` / `mx` | The domain's own A records or MX hosts |
| `include:` | The SPF record of another domain, typically a sending service |
| `all` | Everything else; its qualifier decides the default |
| Qualifier | Result for a non-matching sender |
| --- | --- |
| `+` (default) | pass |
| `~` | softfail: accept but mark |
| `-` | fail: reject or quarantine |
| `?` | neutral: no opinion |
Two limits catch people out. A domain may publish only one SPF record; two records mean SPF fails for everyone. And evaluation may perform at most ten DNS lookups, counting every `include:`, `a`, `mx` and `redirect`. Large stacks of third-party senders hit that limit and silently break.
SPF checks the envelope sender, not the `From:` header the recipient sees. Forwarding breaks it, because the forwarding server is not in the original domain's record. Both weaknesses are why DKIM and DMARC exist.
## What does DKIM prove?
DKIM, defined in [RFC 6376](https://www.rfc-editor.org/rfc/rfc6376), adds a cryptographic signature to each message. The sending server signs selected headers and a hash of the body with a private key, and publishes the public key in DNS under a selector.
```text
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=acme.com; s=mail2026;
h=from:to:subject:date:message-id; bh=…; b=…
```
```text
mail2026._domainkey.acme.com. IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkq…"
```
The receiver fetches `selector._domainkey.domain`, verifies the signature, and learns two things: the signed parts were not modified in transit, and whoever holds the private key for `d=acme.com` vouched for the message. DKIM survives forwarding, which SPF does not, as long as the forwarder leaves the signed headers alone. Rotate keys by publishing a new selector and switching signing to it; the old selector can be removed once mail in transit has cleared.
## What does DMARC add?
DMARC, defined in [RFC 7489](https://www.rfc-editor.org/rfc/rfc7489), ties SPF and DKIM to the visible `From:` domain and gives receivers a policy. The record lives at `_dmarc.domain`.
```text
_dmarc.acme.com. IN TXT "v=DMARC1; p=reject; rua=mailto:dmarc@acme.com; adkim=r; aspf=r; pct=100"
```
A message passes DMARC when at least one of SPF or DKIM passes **and** its domain aligns with the `From:` header domain. Relaxed alignment (`r`) accepts subdomains; strict (`s`) requires an exact match.
| Policy | Receiver action on failure |
| --- | --- |
| `p=none` | Deliver normally, send reports |
| `p=quarantine` | Deliver to spam or hold |
| `p=reject` | Refuse the message |
The `rua` address receives daily aggregate XML reports listing every source that sent mail as your domain and whether it passed. Start at `p=none`, read the reports until every legitimate sender aligns, then move to `quarantine` and `reject`. A domain that stays at `p=none` for years has told receivers it does not mind being spoofed.
## What do Gmail and Yahoo require?
Since February 1, 2024, Google's [sender guidelines](https://support.google.com/a/answer/81126) require anyone sending more than 5,000 messages a day to Gmail accounts to authenticate with SPF and DKIM, publish a DMARC policy, support one-click unsubscribe with the `List-Unsubscribe` headers, and keep the spam rate reported in Postmaster Tools below 0.10% and never at 0.30% or above. Yahoo's [sender requirements](https://senders.yahooinc.com/best-practices/) ask the same of bulk senders: both SPF and DKIM, a DMARC policy of at least `p=none` that passes, one-click unsubscribe, and a spam rate below 0.3%. Yahoo also asks senders to monitor bounces and remove invalid recipients promptly.
For a developer the practical checklist is short: every sending service you use appears in SPF, every one of them signs with DKIM under your domain, `_dmarc` exists with a policy and a reporting address, and marketing mail carries the unsubscribe headers.
## How does Spaw use these records?
Every email verification answer reports `has_spf` and `dmarc_policy` for the address's domain, and the domain endpoint returns the raw records alongside MX hosts, provider and registration age.
```bash
curl https://spaw.co/api/v1/email/domain/acme.com \
-H "Authorization: Bearer sk_live_…"
```
```json
{
"success": true,
"data": {
"domain": "acme.com",
"mx_found": true,
"mx_provider": "google",
"has_spf": true,
"dmarc_policy": "reject",
"domain_registered_at": "1998-03-12",
"// 6 more fields": "see the endpoint reference"
},
"meta": { "credits_used": 1, "credits_remaining": 8, "cache_hit": false }
}
```
The records also feed the scores. A business domain without SPF adds 10 to the risk score, since a domain nobody bothered to authenticate is less likely to be a well-run mailbox provider for real people. In the mailbox confidence estimate for catch-all domains, a DMARC policy of `reject` or `quarantine` adds 15, `none` adds 5, an SPF record adds 10 and a missing one subtracts 10. Free consumer providers are exempt from the SPF penalty, because Gmail's records say nothing about the person using it.
## Which mistakes come up most?
| Mistake | Effect | Fix |
| --- | --- | --- |
| Two SPF records | Permanent SPF failure | Merge into one record |
| More than ten DNS lookups | SPF permerror | Flatten includes or drop unused senders |
| `+all` at the end | Everyone passes; the record is useless | Use `~all` or `-all` |
| DKIM key never rotated | A leaked key signs forever | Publish a new selector each year |
| Third-party sender not aligned | DMARC fails for newsletters | Sign with your own domain at the provider |
| `p=none` forever | No protection, receivers notice | Move to `quarantine`, then `reject` |
| No `rua` address | Failures are invisible | Add a mailbox or a reporting service |
## What to do next
- Check any domain's live records in the [SPF and DMARC checker](/tools/spf-dmarc-checker); each result has a permalink you can send to whoever runs the DNS.
- Pull the records programmatically from the [domain intelligence endpoint](/docs/api/domain-intelligence).
- See exactly how SPF and DMARC move the numbers in [how Spaw computes the risk score](/guides/how-spaw-computes-the-risk-score).
- If a receiver answers `550 5.7.26`, read [SMTP bounce codes explained](/guides/smtp-bounce-codes-explained): that reply is about authentication, not the recipient.
---
# What is a catch-all email address, and how should you handle it?
A catch-all domain accepts mail for every address, so no handshake can confirm one mailbox. What Spaw reports, how the confidence score works, when to send.
Date: 2026-09-03
A catch-all email address is any address at a domain whose mail server accepts mail for every recipient, whether or not the mailbox exists. Because the server says yes to everything, an SMTP handshake cannot tell a real mailbox from a made-up one, and a verifier has to report the address as unconfirmed rather than deliverable. Roughly, catch-all means "the domain is real and receiving, the mailbox is unknown."
## What is a catch-all address?
Every domain's mail server decides, at the `RCPT TO` step of the SMTP conversation, whether it will accept mail for a recipient. A normal server checks its directory and answers `550 5.1.1` for an unknown user. A catch-all server, also called accept-all, answers `250 OK` for every recipient and sorts the mail out afterwards: it delivers what matches a mailbox, forwards the rest to a default inbox, or silently drops it.
The effect on verification is total. The handshake that settles most addresses proves nothing here, because the same `250` comes back for `mia@acme.com` and for `xq7pw@acme.com`.
## Why do catch-all servers exist?
Four common reasons, and none of them is a mistake.
| Setup | Why it accepts everything |
| --- | --- |
| Security gateways (Proofpoint, Mimecast, Barracuda) | The gateway sits in front of the real mail system and often accepts first, then filters or bounces internally |
| Microsoft 365 tenants | Directory-based recipient checking is optional; many tenants accept all recipients at the edge |
| Small businesses and agencies | One default inbox collects mail to any address at the domain so nothing is lost |
| Anti-harvesting policy | Rejecting unknown users lets an attacker enumerate valid addresses; accepting everything hides the directory |
The last reason matters: some of the most carefully run domains are catch-all on purpose.
## How does verification detect a catch-all?
The tell is a server that accepts a recipient no real person could have. Verification providers probe with an address that cannot exist and, if it is accepted, mark the domain catch-all. Spaw's mailbox provider reports that classification directly, and Spaw remembers it: a catch-all answer is stored per domain for seven days, so later addresses at that domain are answered from memory and no further handshakes are spent on a question the server will not answer.
The domain endpoint exposes the same memory. `GET /api/v1/email/domain/acme.com` returns `catch_all: true` once any address at the domain has been seen to be catch-all in the last week, and `null` when that is not known.
## What does Spaw return for a catch-all address?
The verdict is `risky` with `reason: "catch_all"`, `mailbox_exists` stays `null`, and the handshake detail explains itself.
```bash
curl https://spaw.co/api/v1/email \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{"email": "catch-all@spaw.test"}'
```
```json
{
"success": true,
"data": {
"email": "catch-all@spaw.test",
"deliverable": "risky",
"reason": "catch_all",
"risk_score": 30,
"risk_level": "medium",
"smtp_checked": true,
"mailbox_exists": null,
"catch_all": true,
"mailbox_confidence": 65,
"// 17 more fields": "see the endpoint reference"
},
"meta": { "credits_used": 0, "credits_remaining": 10, "cache_hit": false }
}
```
That request is free because `catch-all@spaw.test` is one of the six fixed test addresses. A real catch-all answer is billed like any other risky answer: 1 credit, with repeats of the same address free for seven days. The catch-all flag adds 30 to the risk score, which alone puts the address in the medium band.
## What is mailbox_confidence?
Since the handshake cannot say whether the mailbox exists, Spaw adds an estimate built only from signals already in the response, so you can recompute or ignore it. It starts at 50 and moves as follows.
| Signal | Adjustment |
| --- | --- |
| Your own delivery outcomes for the domain: bounce rate of 50% or more | −20 |
| Your own delivery outcomes for the domain: bounce rate of 10% or less | +15 |
| DMARC policy reject or quarantine | +15 |
| DMARC policy none | +5 |
| SPF record published | +10 |
| No SPF record | −10 |
| Mail hosted at an established provider (Google, Microsoft, Proofpoint, Mimecast, Barracuda, Zoho, Fastmail) | +10 |
| Username looks like a name (dictionary of first names and surnames) | +15 |
| Username looks like keyboard mash | −25 |
| Role address such as info@ | +5 |
| Domain registered less than 90 days ago | −15 |
| Domain registered two years ago or more | +10 |
| Plus-tag alias | −10 |
The result is clamped to 0 to 100. Delivery outcomes only count once you have reported five or more for the domain, and only your own account's reports are used. A domain administered with DMARC and SPF, hosted at a serious provider, with a name-shaped username, lands in the 80s; a plus-tagged mash of consonants at a domain registered last month lands near zero.
## How should you send to catch-all addresses?
Treat them as a segment, not as a verdict to act on blindly.
Send in moderation first. A catch-all address that bounces is a hard bounce like any other, so warm the segment with small volumes and watch the results before sending the whole list.
Report what happens. Post delivered and bounced outcomes to the feedback endpoint, or point your sending provider's bounce webhook at Spaw. A delivered report turns later lookups of that address into confirmed mailboxes (`smtp_reason: "delivered_recently"`, `mailbox_exists: true`), and bounces move both the suppression list and the domain's bounce rate, which feeds the confidence score for every other address at the domain.
Branch on the confidence score for the rest. A common policy is to send to catch-all addresses with a confidence of 60 or more, hold the rest for a second attempt, and drop anything under 30. Because the score is recomputable, you can tune those thresholds against your own bounce data instead of trusting a label.
## What to do next
- Check a domain's catch-all status and provider with the [domain intelligence endpoint](/docs/api/domain-intelligence); it costs 1 credit only when the domain has mail servers.
- Read the [`catch_all` reason page](/docs/reasons/catch_all) for the exact response contract.
- Wire your bounce webhook into the [feedback endpoint](/docs/api/report-delivery-feedback) so the confidence score learns from your own sends.
- See how the risk score adds up in [how Spaw computes the risk score](/guides/how-spaw-computes-the-risk-score).
---
# Why an email validation regex is not enough
A regex only rejects the impossible. It cannot say whether the domain accepts mail, the mailbox exists, or the address is a burner. What to run after it.
Date: 2026-09-03
A regular expression can reject strings that cannot be email addresses; it cannot accept strings that are. `mia@gmail.con`, `info@acme.com` and `x9@mailinator.com` all pass every regex ever written, and each one is a problem for a different reason that only DNS, a list, or the mail server itself can reveal. Use a short pattern to catch typing errors instantly, then verify the address for real.
## What does the RFC actually allow?
[RFC 5322](https://www.rfc-editor.org/rfc/rfc5322) defines the address grammar, and it is far wider than most forms expect. The local part may be a quoted string containing spaces and `@` signs. Comments in parentheses are legal in several places. The domain may be an IP address in square brackets. The regex that implements the full grammar is thousands of characters long, and matching it still tells you nothing useful, because "syntactically valid" and "a mailbox someone reads" are different questions.
The HTML specification makes the same point from the other side. The WHATWG definition of a [valid e-mail address](https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address) for `` is deliberately narrower than RFC 5322 and describes itself as a willful violation of the RFC, because the real-world addresses browsers need to accept are simpler than the grammar allows.
## What is the minimum sane pattern?
Something that rejects obvious typing errors and nothing else.
```javascript
const looksLikeEmail = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.trim());
```
It requires exactly one `@`, no whitespace, and at least one dot after the `@`. That catches the mistakes people make while typing: a missing `@`, a space, `mia@gmail`, `mia@@gmail.com`. It rejects a handful of technically legal addresses, such as a bare local domain without a dot, which is the right trade for a public signup form.
Do not tighten it further. Patterns that enumerate top-level domains break every time ICANN adds one; patterns that forbid `+` reject a common Gmail convention; patterns that limit the local part to letters reject real addresses at real companies.
## What can a regex not decide?
Every question that matters.
| Address | Regex | Reality | Spaw reason |
| --- | --- | --- | --- |
| `mia@gmail.con` | valid | A typo-squat domain that may accept mail from strangers | `likely_typo`, with `did_you_mean` |
| `mia@acme-corp.cmo` | valid | No such domain, no mail server | `no_mx_records` |
| `hello@brand-holding.example` | valid | The domain publishes a null MX: it refuses all mail | `null_mx` |
| `m!a@gmail.com` | valid | Gmail never issues usernames with `!` | `invalid_local_part` |
| `x9@mailinator.com` | valid | A disposable inbox that expires in minutes | `disposable` |
| `info@acme.com` | valid | A role inbox, not a person | `role` |
| `nobody@acme.com` | valid | The domain is real; the server says the mailbox is not | `mailbox_not_found` |
| `mia@acme.com` at a catch-all domain | valid | The server accepts everything; the mailbox is unproven | `catch_all` |
The pattern is correct on every row and useless on every row. The information lives in DNS, in open lists, in provider rules and in the SMTP handshake.
## What runs after the regex?
Spaw's pipeline, in the order it settles addresses.
**Extraction.** People paste what they have: `Mia K `, `mailto:mia@acme.com`, a value wrapped in spreadsheet quotes, an address with a trailing comma or an invisible zero-width character. Each is unwrapped before validation, and the cleaned value is echoed back so a form can show the user what was checked.
**RFC 5322 parsing.** What remains is parsed with a real grammar, not a pattern. Anything that fails is `invalid_syntax`, free, and every other field is null.
**Provider username rules.** Gmail, Outlook, Yahoo, iCloud, AOL and Proton each publish character sets and maximum lengths for usernames. An address that violates the rule of the provider behind its domain cannot exist and is rejected as `invalid_local_part` before any network work. Minimum lengths are deliberately not enforced, because legacy accounts predate them.
**DNS.** MX records, the address-record fallback, null MX and unresolvable targets settle whether the domain can receive mail at all.
**Lists and maps.** Disposable domains, role usernames and typo-squat domains.
**The handshake.** For everything still standing, an SMTP session asks the server about the mailbox and disconnects before any message.
```bash
curl https://spaw.co/api/v1/email \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{"email": "Mia K ,"}'
```
```json
{
"success": true,
"data": {
"email": "mia@gmail.con",
"syntax_valid": true,
"deliverable": "risky",
"reason": "likely_typo",
"did_you_mean": "mia@gmail.com",
"risk_score": 60,
"// 21 more fields": "see the endpoint reference"
},
"meta": { "credits_used": 1, "credits_remaining": 9, "cache_hit": false }
}
```
The display name and trailing comma were stripped, the typo-squat was recognised, and the correction is ready to show in the form.
## How should a signup form use this?
Validate in three places, each doing what it is good at.
In the browser, run the short regex on every keystroke or on blur for instant feedback, and use `` so mobile keyboards show the `@` key. Do not block submission on anything more than that.
On blur, ask the verifier. The Spaw form helper attaches to any input marked `data-spaw-email`, runs a lookup when the field loses focus, shows the verdict inline, offers the `did_you_mean` correction as a one-click fix, and never blocks the submit. It authenticates with a publishable key locked to your domain, so nothing secret ships to the page.
On the server, verify again with your secret key before creating the account, and decide policy there: reject `undeliverable`, ask for another address on `disposable` if the account has value, accept `role` on B2B forms, and store `normalized_email` so `Mia.K+news@gmail.com` and `miak@gmail.com` cannot register twice.
## What to do next
- Replace any long regex in your codebase with the short pattern above and move the real checks server-side.
- Read the [`invalid_syntax`](/docs/reasons/invalid_syntax) and [`invalid_local_part`](/docs/reasons/invalid_local_part) pages for the exact rules that run after parsing.
- Add the form helper from the [email verification guide](/docs/email-verification#widget) to your signup page.
- See what the full handshake adds in [how to verify an address without sending](/guides/verify-email-without-sending).
---
# Amazon SES and Amazon WorkMail: MX records and email verification
How domains that receive mail through Amazon SES or WorkMail look in DNS, how they answer an SMTP verification handshake, and which verdicts to expect.
Updated: 2026-09-03
Amazon Web Services receives mail in two ways. Amazon SES email receiving delivers incoming messages to S3, Lambda or SNS through rules the customer writes, and Amazon WorkMail is a hosted mailbox service for organisations that runs on the same inbound endpoints. Both publish MX records under `amazonaws.com`, and Spaw reports them as `mx_provider: "amazon-ses"`.
## How Amazon shows up in DNS
A domain receiving through SES or WorkMail publishes a single regional MX record such as `inbound-smtp.us-east-1.amazonaws.com` or `inbound-smtp.eu-west-1.amazonaws.com` at priority 10. Custom MAIL FROM subdomains used for sending publish an MX of the form `feedback-smtp.{region}.amazonses.com`, which is a different zone and is not matched by this pattern. Spaw matches the `.amazonaws.com` suffix. Amazon's own consumer-facing domains (amazon.com, audible.com) run on Amazon's internal mail platform and are classified as `other`.
Whether the domain is a mailbox host or an inbound-routing service depends on what sits behind the endpoint: WorkMail stores mail in mailboxes, SES receiving hands it to software.
## How Amazon answers a verification handshake
SES email receiving accepts every recipient that matches a receipt rule, and receipt rules commonly match a whole domain, so from the outside the domain is a catch-all and no probe can prove that a specific address is handled. WorkMail knows its mailboxes and typically rejects unknown recipients at `RCPT TO`, so WorkMail domains answer cleanly unless the organisation configured a catch-all. The inbound endpoints are shared infrastructure with conventional rate limiting; deferred probes are uncommon but possible.
## What that means for your verdicts
Domains on SES receiving answer `risky` with `reason: catch_all` and `mailbox_exists: null`; Spaw remembers the catch-all for seven days and returns `mailbox_confidence`, which without the established-provider bonus leans on the domain's DMARC and SPF, its age, the shape of the username and your own delivery outcomes. WorkMail domains mostly answer `deliverable` with `mailbox_exists: true` or `undeliverable` with `reason: mailbox_not_found`. There are no consumer username rules for these domains.
Addresses on SES-receiving domains are frequently machine endpoints: support tickets, reply tracking, notification ingestion. `is_gibberish: true` and role-shaped usernames are common there, and a `risky` catch-all verdict on such an address is a reason to leave it out of human-facing campaigns.
## SPF and DMARC at Amazon
Sending through SES uses a subdomain of `amazonses.com` as the default MAIL FROM domain, so SPF passes on the envelope without any record from the customer; customers who want alignment set up a custom MAIL FROM subdomain and publish the SPF TXT record and the single MX record that the SES console shows for their region, and SES provides DKIM through Easy DKIM CNAME records. DMARC alignment can then be met through DKIM, SPF or both, as the SES documentation describes. Spaw reports the live state of any domain in `has_spf` and `dmarc_policy`.
## Practical notes for sending to Amazon-hosted domains
WorkMail organisations behave like any business mailbox host and can be sent to normally. SES-receiving domains generally should not be on a marketing list at all: the address exists to feed an application. When such addresses appear in a signup form, they usually indicate an automated integration or a test, and the free `risky` verdict plus the domain intelligence endpoint's `mx_provider` field let you filter them without spending credits.
For a live look at any domain, run it through the free MX lookup and the SPF and DMARC checker.
---
# Barracuda Email Gateway Defense: MX records and email verification
How domains behind Barracuda look in DNS, how the gateway answers a verification handshake, and which verdicts to expect for Barracuda-protected addresses.
Updated: 2026-09-03
Barracuda Email Gateway Defense (formerly Barracuda Essentials) is a hosted security gateway used mostly by small and mid-size organisations, often sold through IT service providers. Companies point their MX at Barracuda, which filters inbound mail and hands it to the mailbox host behind it. Spaw reports such domains as `mx_provider: "barracuda"`.
## How Barracuda shows up in DNS
Domains behind the service publish a pair of MX records under `ess.barracudanetworks.com`, such as `d160221a.ess.barracudanetworks.com` and `d160221b.ess.barracudanetworks.com`, with regional variants for Europe, Australia, Canada and other data centres. Spaw matches the `.barracudanetworks.com` suffix. The target is a filtering layer, so `mx_provider` identifies the gateway rather than where the mailboxes live.
## How Barracuda answers a verification handshake
Barracuda's gateway supports recipient verification: when the customer has connected the service to their directory (Microsoft 365 or Google Workspace synchronisation, or an LDAP lookup), unknown recipients are rejected at `RCPT TO`. That integration is common among Barracuda's customer base because the same connection drives the rest of the product, so clean answers are more frequent here than behind some other gateways. Without it, the gateway accepts every recipient and the domain answers as catch-all. Barracuda also applies sender reputation checks and rate limits before the recipient step, so a probe from an unfamiliar source is occasionally deferred.
## What that means for your verdicts
Domains with recipient verification answer `deliverable` with `mailbox_exists: true` or `undeliverable` with `reason: mailbox_not_found` and `smtp_reason: no_mailbox`. The rest answer `risky` with `reason: catch_all`; Spaw remembers that per domain for seven days and returns `mailbox_confidence` for each address, with the ten-point bonus for an established provider and adjustments for DMARC, SPF, the domain's age, the username's shape and your own delivery outcomes. Deferred probes show `smtp_reason: greylisted`, and `callback_url` re-checks them after 5 and 20 minutes for free.
As with every gateway, your delivery feedback carries the most weight: a delivered report confirms the mailbox for later lookups (`smtp_reason: delivered_recently`), and bounces lower the confidence for the whole domain.
## SPF and DMARC at Barracuda
Companies that also send outbound mail through the service add `include:spf.ess.barracudanetworks.com` to their SPF record for the US instance, or a regional variant such as `include:spf.ess.uk.barracudanetworks.com`, `include:spf.ess.de.barracudanetworks.com`, `include:spf.ess.au.barracudanetworks.com` or `include:spf.ess.ca.barracudanetworks.com`, as Barracuda's documentation lists them. The inbound filter does not change the domain's own SPF and DMARC, which Spaw reports live in `has_spf` and `dmarc_policy`.
## Practical notes for sending to domains behind Barracuda
Barracuda's customer base skews to smaller organisations: schools, clinics, local government and firms with an outsourced IT provider. Their gateways are often tuned strictly, and their lists change with staff turnover, so re-verifying on a schedule pays off. A `mailbox_not_found` from a Barracuda gateway with recipient verification is as reliable as one from the mailbox host itself.
For a live look at any Barracuda-protected domain, run it through the free MX lookup and the SPF and DMARC checker.
---
# Fastmail: MX records and email verification
How Fastmail-hosted domains look in DNS, how Fastmail answers an SMTP verification handshake, and which verdicts to expect for Fastmail addresses.
Updated: 2026-09-03
Fastmail is an independent mailbox provider with a large base of custom-domain customers. Its infrastructure runs under the `messagingengine.com` name, which is why Spaw reports it as `mx_provider: "fastmail"` even though that hostname never appears in a Fastmail address.
## How Fastmail shows up in DNS
Fastmail's DNS guide asks custom domains to publish `in1-smtp.messagingengine.com` at priority 10 and `in2-smtp.messagingengine.com` at priority 20. Fastmail's own domains (fastmail.com, fastmail.fm and the many alternative domains it offers) use the same hosts. Spaw matches the `.messagingengine.com` suffix. Fastmail is a mailbox host: the MX target stores the mail, `mx_found` is true and `mx_implicit` is false.
## How Fastmail answers a verification handshake
Fastmail typically rejects unknown recipients at `RCPT TO`, so most mailbox checks settle on the first attempt. Two Fastmail features change that picture for some domains. Subdomain addressing lets any user receive mail at `anything@user.fastmail.com`, which is effectively a per-user catch-all on that subdomain. And custom domains can be configured with a catch-all address, in which case the inbound server accepts every recipient and the domain answers as catch-all.
## What that means for your verdicts
Fastmail addresses mostly come back `deliverable` with `mailbox_exists: true` or `undeliverable` with `reason: mailbox_not_found` and `smtp_reason: no_mailbox`. Catch-all domains answer `risky` with `reason: catch_all`; Spaw remembers the domain for seven days and returns `mailbox_confidence`, which includes the ten-point bonus for an established provider. Fastmail is not in the consumer username rule set, so `invalid_local_part` does not apply; plus-addressing is supported and reported as `is_alias: true`.
Fastmail users are often technical and privacy-conscious, and Fastmail's masked-email feature creates single-purpose addresses on domains such as `fastmail.com` subdomains that stop accepting mail once the user disables them. Such an address is real until it is not; a later lookup answers `mailbox_not_found` and the address joins your suppression list.
## SPF and DMARC at Fastmail
Fastmail's manual DNS guide publishes `v=spf1 include:spf.messagingengine.com ?all` for SPF, DKIM CNAME records for the `fm1`, `fm2` and `fm3` selectors, and suggests starting DMARC at `v=DMARC1; p=none;` so you can observe before enforcing. Note the neutral `?all` in Fastmail's default: it is a permissive record, and Spaw still reports `has_spf: true` for it because the record exists.
## Practical notes for sending to Fastmail users
Fastmail's filtering weighs authentication and reputation in the usual way and applies user-defined rules heavily, so a properly aligned sending domain is the main requirement. Expect masked and single-purpose addresses on lists collected from technical audiences and re-verify those lists on a schedule; a monitor in the Spaw dashboard does that for you and alerts when deliverability decays.
For a live look at any Fastmail-hosted domain, run it through the free MX lookup and the SPF and DMARC checker.
---
# GMX: MX records and email verification
How GMX domains look in DNS, how GMX answers an SMTP verification handshake, and which verdicts to expect for gmx.net, gmx.de and gmx.com addresses.
Updated: 2026-09-03
GMX is one of the two big German consumer mailbox providers, run by 1&1 Mail & Media alongside its sister brand WEB.DE. It serves gmx.net, gmx.de, gmx.at, gmx.ch and the international gmx.com, all on the same platform. Spaw reports the GMX domains as `mx_provider: "gmx"`.
## How GMX shows up in DNS
GMX domains publish MX records under `gmx.net`: gmx.net and gmx.de point at `mx00.emig.gmx.net` and `mx01.emig.gmx.net`, and gmx.com at `mx00.gmx.net` and `mx01.gmx.net`. Spaw matches the `.gmx.net` suffix. WEB.DE, despite sharing the infrastructure, uses hosts under `web.de` and classifies as `other` until the classifier learns that pattern. GMX is a mailbox host: `mx_found` is true and `mx_implicit` is false.
## How GMX answers a verification handshake
GMX rejects unknown recipients at `RCPT TO`, so a missing mailbox is confirmed in the session. The provider is strict about who it talks to at all: it enforces reverse DNS on the connecting host, applies reputation checks before the recipient step, and greylists or refuses connections from sources it does not trust. A probe from infrastructure GMX has not seen is therefore more likely than average to end with `smtp_reason: greylisted` or `no_connect` rather than an answer.
## What that means for your verdicts
Confirmed answers are `deliverable` with `mailbox_exists: true` or `undeliverable` with `reason: mailbox_not_found` and `smtp_reason: no_mailbox`. Unverified probes leave `smtp_checked: false` and `mailbox_exists: null`, the verdict rests on the free signals, ten risk points are added for the unverified state, and `mailbox_confidence` is returned; GMX is not on the established-provider list, so that estimate depends on the username's shape and your own delivery data more than anything else. GMX has no consumer username rules in Spaw's rule set, so `invalid_local_part` does not apply. Catch-all does not occur on consumer GMX domains.
For unverified single lookups, pass `callback_url` so Spaw re-checks after 5 and 20 minutes; for lists, report delivery outcomes so confirmed mailboxes carry `smtp_reason: delivered_recently` on later lookups.
## SPF and DMARC at GMX
Consumer GMX users have nothing to configure. The GMX domains publish their own SPF and DMARC records, and GMX enforces DMARC on inbound mail, so a message with a gmx.de From address sent through another service fails alignment. Spaw's `dmarc_policy` field shows the live policy for any domain; for gmx.net it reflects whatever GMX currently publishes.
## Practical notes for sending to GMX users
GMX and WEB.DE together receive a large share of German consumer mail and are known for strict inbound policies: a valid reverse DNS entry for your sending IPs, aligned SPF and DKIM, and a working postmaster address are practical requirements, and 1&1 operates a postmaster programme with a certification for bulk senders. Plus-addressing is not a standard feature on consumer GMX accounts. Addresses on these domains are typically individuals, so role-word usernames are rare and `reason: role` is uncommon here.
For a live look at any GMX domain, run it through the free MX lookup and the SPF and DMARC checker.
---
# GoDaddy Workspace Email: MX records and email verification
How domains on GoDaddy's legacy Workspace Email look in DNS, how they answer a verification handshake, and why most GoDaddy email now classifies as Microsoft.
Updated: 2026-09-03
GoDaddy sold its own mailbox product, Workspace Email, for many years alongside domain registrations and hosting. Its servers run under the `secureserver.net` name, and Spaw reports domains that still point there as `mx_provider: "godaddy"`. GoDaddy has since moved its email business to Microsoft 365 from GoDaddy, so the classifier label is shrinking.
## How GoDaddy shows up in DNS
Legacy Workspace Email domains publish MX records under `secureserver.net`, historically `smtp.secureserver.net` at priority 0 and `mailstore1.secureserver.net` at priority 10. Spaw matches the `.secureserver.net` suffix. Note that GoDaddy's own domains, including secureserver.net itself, now resolve to `mail.protection.outlook.com` hosts and classify as `microsoft`, as do all customers on Microsoft 365 from GoDaddy. If a domain you expected to be GoDaddy-hosted comes back as `microsoft`, that migration is the reason, and the Microsoft page describes how it behaves.
## How GoDaddy answers a verification handshake
The legacy Workspace Email servers typically reject unknown recipients at `RCPT TO`, so most checks settle in the session. A catch-all mailbox could be configured per domain, and domains with one answer as catch-all. The platform also rate-limits unfamiliar sources, which occasionally defers a probe.
## What that means for your verdicts
For domains still on Workspace Email, expect `deliverable` with `mailbox_exists: true`, `undeliverable` with `reason: mailbox_not_found` and `smtp_reason: no_mailbox`, or `risky` with `reason: catch_all` on domains with a catch-all mailbox. For catch-all domains Spaw returns `mailbox_confidence` without the established-provider bonus, so the domain's own DMARC and SPF, its age and the shape of the username carry the estimate. There are no consumer username rules for GoDaddy domains.
The more important verdict pattern is around the migration itself. Mailboxes that were not carried over to Microsoft 365 stop existing, so a list collected years ago from small businesses on GoDaddy email tends to produce a wave of `mailbox_not_found` answers when re-verified now. Those answers are free, and the addresses join your suppression list automatically.
## SPF and DMARC at GoDaddy
GoDaddy's help pages ask domain owners to publish `v=spf1 include:secureserver.net -all` for email on Professional Email, Microsoft 365 from GoDaddy, Linux hosting and their hosting products, adding other services' includes as needed and keeping to a single SPF record. Spaw reports the live state in `has_spf` and `dmarc_policy`; a small-business domain without SPF adds ten risk points to its addresses.
## Practical notes for sending to GoDaddy-hosted users
GoDaddy's email customers are overwhelmingly small businesses with domains registered years ago, and their lists age quickly. Re-verify before every campaign rather than once a year, and expect role addresses (`info@`, `contact@`) to dominate; Spaw flags those `risky` with `reason: role` even when they exist. Where a domain has moved to Microsoft 365, the catch-all behaviour common on Exchange Online tenants applies, and `mailbox_confidence` plus your own delivery feedback are the tools for ranking those addresses.
For a live look at any domain, run it through the free MX lookup and the SPF and DMARC checker.
---
# Google Workspace and Gmail: MX records and email verification
How Google-hosted domains look in DNS, how Gmail answers an SMTP verification handshake, and which verdicts and reasons to expect for Google addresses.
Updated: 2026-09-03
Google hosts more mailboxes than any other provider, split between consumer Gmail and Google Workspace on company domains. Both run on the same inbound infrastructure, so Spaw reports them under one `mx_provider` value: `google`.
## How Google shows up in DNS
A Google-hosted domain publishes MX records under `l.google.com` or, for newer Workspace setups, the single host `smtp.google.com`. Gmail itself resolves to `gmail-smtp-in.l.google.com` and its `alt1` to `alt4` siblings; Workspace domains set up before 2023 typically use `aspmx.l.google.com` with `alt1.aspmx.l.google.com` and so on, and Google's current setup guide recommends the one-record form, `smtp.google.com` at priority 1.
Spaw matches either pattern and returns `mx_provider: "google"`. Google is a mailbox host: the MX points at the servers that store mail, not at a filtering layer in front of somebody else's servers. `mx_found` is true, `mx_implicit` is false, and `has_spf` and `dmarc_policy` describe the domain's own records.
## How Google answers a verification handshake
Google answers the recipient check honestly and quickly. When the SMTP handshake reaches `RCPT TO` for an address that does not exist, Gmail and Workspace both reject it in the session, typically with a `550 5.1.1` reply that says the address was not found. A mailbox that exists is accepted at the same step. Greylisting and silent throttling are uncommon for a single well-formed probe, so an answer usually arrives on the first attempt.
Workspace domains can be configured as catch-all by an administrator, but that is the exception: the default rejects unknown recipients. Google also treats dots in Gmail usernames as insignificant and delivers plus-tagged addresses to the base mailbox, which is why Spaw folds both into `normalized_email` before it does anything else.
## What that means for your verdicts
For Google addresses you should mostly see clean answers: `deliverable` with `mailbox_exists: true`, or `undeliverable` with `reason: mailbox_not_found` and `smtp_reason: no_mailbox`. Catch-all answers are rare and, when they appear, come from a Workspace domain whose admin turned the option on; Spaw then remembers the domain as catch-all for seven days and returns `mailbox_confidence` instead of spending more probes on it.
Two things happen before any probe for the consumer domains gmail.com and googlemail.com. First, Google's own username rules apply: Gmail usernames contain only letters, digits and dots, and are at most 30 characters once the dots are removed. An address that breaks those rules is answered `undeliverable` with `reason: invalid_local_part` without a handshake. Second, dots and plus-tags are normalised, so `m.ia+news@gmail.com` and `mia@gmail.com` share one cache entry and one suppression entry.
A `google` provider also adds ten points to `mailbox_confidence` when a probe could not settle a mailbox, because an established provider with strict signup rules makes a plausible-looking address more likely to exist.
## SPF and DMARC at Google
Google Workspace tells domain owners to publish `v=spf1 include:_spf.google.com ~all` and to add a DKIM key from the admin console. Consumer Gmail users have nothing to configure; gmail.com itself publishes SPF and a DMARC policy. On a Workspace domain, a missing SPF record shows up in Spaw as `has_spf: false` and costs the address ten risk points; `dmarc_policy` reports `none`, `quarantine` or `reject` from the live record.
## Practical notes for sending to Google users
Google enforces sender authentication on the receiving side: since 2024 bulk senders to Gmail need aligned SPF or DKIM, a DMARC record, one-click unsubscribe and a low complaint rate, so a list full of unverified Gmail addresses is expensive in two ways. Role inboxes such as `info@` on Workspace domains exist and are probed like any other address; Spaw still flags them `risky` with `reason: role` because a shared inbox is not a person. Plus-tags are safe to send to, but Spaw reports `is_alias: true` so you can decide whether to keep the tagged form.
For a live look at any Google-hosted domain, run it through the free MX lookup and the SPF and DMARC checker.
---
# HEY: MX records and email verification
How HEY and HEY for Domains addresses look in DNS, how HEY answers a verification handshake, and which verdicts to expect for hey.com and custom domains.
Updated: 2026-09-03
HEY is the email service from 37signals, launched in 2020 with a screening-first design: mail from a new sender is held in a screener until the recipient lets it through. It serves personal accounts on hey.com and company domains through HEY for Domains. Spaw reports both as `mx_provider: "hey"`.
## How HEY shows up in DNS
hey.com publishes a single MX record, `home-mx.app.hey.com`, and HEY for Domains customers point their MX at hosts under the same `hey.com` zone, which HEY displays during setup. Spaw matches the `.hey.com` suffix. HEY is a mailbox host: the MX target is HEY's own inbound infrastructure, `mx_found` is true and `mx_implicit` is false.
## How HEY answers a verification handshake
HEY typically rejects unknown recipients at `RCPT TO`, so a missing hey.com mailbox is confirmed in the session. The screener is a feature of the inbox, not of the SMTP conversation: a message to an existing address is accepted by the server whether or not the recipient has approved the sender, and the screening happens afterwards. HEY for Domains does not expose a conventional catch-all setting, so catch-all answers are uncommon on HEY domains. Deferrals are rare but possible for sources HEY has not seen.
## What that means for your verdicts
HEY addresses mostly come back `deliverable` with `mailbox_exists: true` or `undeliverable` with `reason: mailbox_not_found` and `smtp_reason: no_mailbox`. Occasional unverified probes show `smtp_reason: greylisted` with `mailbox_confidence` returned; HEY is not on the established-provider list, so the estimate depends on the domain's own records and the username's shape. There are no consumer username rules for hey.com in Spaw's rule set, and hey.com usernames are short by design because HEY sold them on a first-come basis, so `is_gibberish` is more often true on real HEY addresses than elsewhere and should be read as a hint only.
A `deliverable` verdict on a HEY address is a statement about the mailbox, not about reach: a first message from an unknown sender lands in the screener and is only seen if the recipient chooses to see it.
## SPF and DMARC at HEY
HEY for Domains shows the SPF, DKIM and DMARC records to publish during domain setup and asks for the MX to be switched only at the final step, so existing mail keeps flowing until the switch. HEY does not publish a public DNS reference page for these values, so copy them from the setup screen rather than from third-party guides. hey.com itself publishes SPF and a DMARC policy. Spaw reports the live state of any domain in `has_spf` and `dmarc_policy`.
## Practical notes for sending to HEY users
Expect low engagement from cold outreach: the screener means unsolicited mail is rarely read, and HEY users can block a sender permanently with one click. Transactional mail from services the user signed up for is approved once and then delivered normally. HEY for Domains customers are small companies, so role addresses exist and are flagged `risky` with `reason: role` as usual. HEY supports plus-style addressing for personal accounts through its own aliasing, which a verifier sees as ordinary addresses.
For a live look at any HEY-hosted domain, run it through the free MX lookup and the SPF and DMARC checker.
---
# Mailgun inbound routes: MX records and email verification
How domains that route inbound mail through Mailgun look in DNS, why Mailgun routes accept every recipient, and which verdicts to expect for addresses on them.
Updated: 2026-09-03
Mailgun is a sending service for applications, but it also receives mail: a domain whose MX records point at Mailgun delivers every incoming message to Mailgun's inbound routes, which store it, forward it or post it to a webhook. Such domains are usually application domains (a support system, a reply-tracking subdomain, a notifications domain), not places where people read mail in an inbox. Spaw reports them as `mx_provider: "mailgun"`.
## How Mailgun shows up in DNS
Domains receiving through Mailgun publish two MX records, `mxa.mailgun.org` and `mxb.mailgun.org`, at priority 10. Mailgun's own product domain, mailgun.com, is on Microsoft 365; it is mailgun.net, a Mailgun-operated domain, that shows the pattern in practice. Spaw matches the `.mailgun.org` suffix. Mailgun is an inbound-routing service rather than a mailbox host: the MX target accepts mail on behalf of whatever the customer's routes do with it.
## How Mailgun answers a verification handshake
Inbound routes accept every recipient by design. Mailgun does not know which addresses "exist" because routes are pattern-based (`match_recipient(".*@support.example.com")` is the canonical example), so the SMTP handshake says yes to any username and the customer's route logic decides afterwards. To a verifier, every Mailgun-receiving domain is a catch-all, and no probe can prove that a specific mailbox exists.
## What that means for your verdicts
Every syntactically valid address on a Mailgun-receiving domain answers `risky` with `reason: catch_all` and `mailbox_exists: null`. Spaw remembers the catch-all per domain for seven days, so subsequent lookups on the same domain do not spend a probe, and returns `mailbox_confidence` for each address. Mailgun is not on the established-provider list, so the estimate is driven by the domain's own DMARC and SPF, its registration age, the shape of the username and your own delivery outcomes; a name-shaped username on a young domain with no DMARC will score low, and that is the right answer for most application domains.
Consider what the address is for before you add it to a list. A reply-tracking address such as `reply+abc123@mail.example.com` is a machine endpoint, and sending marketing mail to it is pointless even though it will be accepted. `is_gibberish: true` on such addresses is a useful hint.
## SPF and DMARC at Mailgun
Mailgun's domain verification guide has customers add `include:mailgun.org` to their SPF record (`v=spf1 include:mailgun.org ~all` when Mailgun is the only sender) and publish the DKIM TXT record shown in the Mailgun dashboard, usually on a dedicated sending subdomain so the root domain's own MX and reputation stay untouched. Mailgun explicitly advises against putting its MX records on a root domain that already receives mail elsewhere. Spaw reports `has_spf` and `dmarc_policy` for whichever domain you look up, including subdomains.
## Practical notes for sending to Mailgun-receiving domains
Mail sent to a Mailgun inbound route reaches software, not a person, unless the customer forwards it to a mailbox. If you see these domains on a customer list, they usually came from automated signups, reply addresses harvested from email threads, or test data. Treat the `risky` catch-all verdict at face value and keep them out of human-facing campaigns; the feedback endpoint can confirm the few that do reach a person.
For a live look at any Mailgun-receiving domain, run it through the free MX lookup and the SPF and DMARC checker.
---
# Microsoft 365, Outlook.com and Exchange Online: MX records and email verification
How Microsoft-hosted domains look in DNS, why many Microsoft 365 tenants answer as catch-all, and which verdicts to expect for Outlook and Exchange addresses.
Updated: 2026-09-03
Microsoft runs two very different mail services on one inbound platform: consumer Outlook.com (with the older hotmail.com, live.com and msn.com domains) and Exchange Online, the mail service behind Microsoft 365 for companies. Spaw reports both as `mx_provider: "microsoft"`.
## How Microsoft shows up in DNS
Exchange Online tenants publish a single MX record shaped like `contoso-com.mail.protection.outlook.com`: the customer's domain with the dots replaced by dashes, under `mail.protection.outlook.com`. Consumer domains use the sibling `olc.protection.outlook.com` zone, for example `outlook-com.olc.protection.outlook.com`. Microsoft has begun rolling out a second naming scheme under `.mx.microsoft`, which Spaw matches as well.
Microsoft is a mailbox host, but the MX target is the Exchange Online Protection edge, a filtering layer that sits in front of every tenant's mailboxes. That edge, not the mailbox server, answers the SMTP handshake, and its behaviour depends on how the tenant is configured.
## How Microsoft answers a verification handshake
Consumer Outlook.com addresses are answered like Gmail: an unknown username is rejected at `RCPT TO`, an existing one is accepted.
Exchange Online tenants are the complicated case. Microsoft documents Directory-Based Edge Blocking, which rejects messages to recipients that are not in the tenant's directory at the network perimeter with `550 5.4.1 Recipient address rejected: Access denied`. It is in effect automatically when every recipient of the domain lives in Exchange Online. When a domain is set to Internal Relay instead, which is common during migrations and in hybrid setups with on-premises Exchange, the edge accepts every recipient and sorts the invalid ones out later with a bounce. From the outside that tenant is a catch-all, and it is a frequent configuration.
## What that means for your verdicts
Expect three patterns. Tenants with edge blocking answer cleanly: `deliverable` with `mailbox_exists: true`, or `undeliverable` with `reason: mailbox_not_found` and `smtp_reason: no_mailbox`. Internal-relay and hybrid tenants answer `risky` with `reason: catch_all`; Spaw remembers the domain as catch-all for seven days, spends no further probes on it, and returns `mailbox_confidence` instead, which for a Microsoft domain starts with the ten-point bonus for an established provider and then moves with DMARC, SPF, the shape of the username and your own delivery feedback. Disabled or de-licensed accounts commonly answer as missing, so a departed employee shows up as `mailbox_not_found` rather than `mailbox_disabled`.
For the consumer domains (outlook.com and its country variants, hotmail.com, live.com, msn.com), Microsoft's own username rules apply before any probe: letters, digits, dots, underscores and hyphens, at most 64 characters. Anything else is `undeliverable` with `reason: invalid_local_part` and costs nothing.
## SPF and DMARC at Microsoft
Microsoft's documentation for custom domains in Microsoft 365 is `v=spf1 include:spf.protection.outlook.com -all`, with separate includes for the government and 21Vianet clouds, and it recommends the hard-fail form together with DKIM and DMARC. Spaw shows the live result in `has_spf` and `dmarc_policy`; a company domain without SPF adds ten risk points to every address on it. Consumer Outlook.com users have nothing to configure.
## Practical notes for sending to Microsoft users
Because catch-all is so common on Microsoft 365, a list of business addresses often comes back with more `risky` answers than a list of consumer ones. That is not a defect of the list; it is the tenant declining to say which mailboxes exist. Use `mailbox_confidence` to rank those addresses, and report delivery outcomes back through the feedback endpoint: after a delivered report, later lookups of the same address answer `smtp_reason: delivered_recently` with a confirmed mailbox, and bounces lower the confidence for the whole domain. Plus-addressing works on Outlook.com and Exchange Online, and Spaw reports it as `is_alias: true`.
For a live look at any Microsoft-hosted domain, run it through the free MX lookup and the SPF and DMARC checker.
---
# Mimecast: MX records and email verification behind a security gateway
How domains behind Mimecast look in DNS, why a security gateway usually answers as catch-all, and which verdicts to expect for addresses protected by Mimecast.
Updated: 2026-09-03
Mimecast is an email security gateway: companies point their MX records at Mimecast, which filters inbound mail and forwards the clean messages to the real mailbox host, usually Microsoft 365 or Google Workspace. Spaw reports such domains as `mx_provider: "mimecast"`, and the gateway, not the mailbox host, is what a verification handshake talks to.
## How Mimecast shows up in DNS
Domains behind Mimecast publish MX records under `mimecast.com`, with regional inbound clusters such as `us-smtp-inbound-1.mimecast.com`, `eu-smtp-inbound-1.mimecast.com` or `service-alpha-inbound-a.mimecast.com`, normally as a pair at equal priority. Spaw matches the `.mimecast.com` suffix. Because the MX target is a filtering layer, `mx_provider` tells you who guards the domain, not where the mailboxes live; the domain intelligence endpoint reports the gateway and leaves the mailbox host unknown.
## How Mimecast answers a verification handshake
A gateway does not know which mailboxes exist unless the customer syncs its directory to it. Mimecast offers directory-based recipient validation, and customers who enable it get clean rejections for unknown recipients at `RCPT TO`. Many do not, and their gateways accept every recipient during the handshake, pass the message to the mailbox host, and only then discover that the mailbox is missing, producing a bounce after the fact. From the outside that domain is a catch-all, and it is the common answer behind Mimecast. Mimecast also greylists and rate-limits senders it has not seen, which leaves some probes unverified.
## What that means for your verdicts
Expect `risky` with `reason: catch_all` for most Mimecast-protected domains. Spaw remembers the catch-all answer per domain for seven days, spends no further probes on it, and returns `mailbox_confidence` for each address instead: Mimecast is on the established-provider list, so the estimate starts ten points up and then moves with the domain's DMARC and SPF, its registration age, whether the username looks like a name, and your own delivery outcomes for the domain. Domains with recipient validation enabled answer cleanly, `deliverable` with `mailbox_exists: true` or `undeliverable` with `reason: mailbox_not_found`. Unverified probes show `smtp_reason: greylisted` or `timeout`; pass `callback_url` to have them re-checked after 5 and 20 minutes.
Because catch-all is the norm here, your own delivery data is the strongest signal available. A delivered report through the feedback endpoint turns later lookups of that address into a confirmed mailbox with `smtp_reason: delivered_recently`, and bounces lower the confidence for the whole domain.
## SPF and DMARC at Mimecast
Companies that send outbound mail through Mimecast add a regional include to their SPF record, such as `include:us._netblocks.mimecast.com` or `include:eu._netblocks.mimecast.com`; the global `include:_netblocks.mimecast.com` record exists too, but it expands to several regional includes and counts against the ten-lookup limit accordingly. Mimecast's documentation lists the correct include for each region. Inbound filtering does not change the domain's SPF; Spaw reports `has_spf` and `dmarc_policy` from the live records as usual.
## Practical notes for sending to domains behind Mimecast
Mimecast-protected companies are typically mid-size and enterprise organisations with strict policies: attachments, links and unauthenticated senders are inspected aggressively, and a sender without aligned SPF and DKIM is more likely to be held or rejected here than at a consumer provider. Treat a `risky` catch-all answer as "the gateway would not say", rank those addresses by `mailbox_confidence`, and feed outcomes back so the ranking improves.
For a live look at any Mimecast-protected domain, run it through the free MX lookup and the SPF and DMARC checker.
---
# Proofpoint: MX records and email verification behind a security gateway
How domains behind Proofpoint Essentials and Proofpoint Protection look in DNS, why the gateway usually answers as catch-all, and which verdicts to expect.
Updated: 2026-09-03
Proofpoint runs two hosted email security services: Proofpoint Protection for enterprises, whose MX hosts live under `pphosted.com`, and Proofpoint Essentials for smaller companies through partners, under `ppe-hosted.com`. Both sit in front of the real mailbox host, and Spaw reports both as `mx_provider: "proofpoint"`.
## How Proofpoint shows up in DNS
Enterprise customers publish MX records shaped like `mxa-00148501.gslb.pphosted.com` and `mxb-00148501.gslb.pphosted.com`, where the number identifies the customer; Essentials customers use regional hosts under `ppe-hosted.com`. Spaw matches both suffixes. The MX target is a filtering gateway, so `mx_provider` names the guard at the door and the mailbox host behind it, typically Microsoft 365 or Google Workspace, stays out of view.
## How Proofpoint answers a verification handshake
Proofpoint can validate recipients against the customer's directory when the customer sets that up, and those gateways reject unknown recipients in the session. Without recipient validation, the gateway accepts every recipient at `RCPT TO`, forwards the message and lets the mailbox host bounce it later, which makes the domain a catch-all to any verifier. In practice, catch-all is the common answer behind Proofpoint. Enterprise gateways also throttle unfamiliar sources and can drop a connection mid-session, which surfaces as `smtp_reason: timeout` or `no_connect`.
## What that means for your verdicts
Most Proofpoint-protected domains answer `risky` with `reason: catch_all`. Spaw remembers the catch-all per domain for seven days and returns `mailbox_confidence` per address: Proofpoint is on the established-provider list, so the estimate starts ten points up, then moves with DMARC and SPF, the domain's age, the shape of the username, and your own delivery feedback. Domains with recipient validation answer cleanly, `deliverable` with `mailbox_exists: true` or `undeliverable` with `reason: mailbox_not_found`. A timed-out probe leaves the address unverified with the free signals deciding the verdict; `callback_url` schedules re-checks after 5 and 20 minutes at no cost.
Feed delivery outcomes back. Behind a catch-all gateway, a delivered report is the only way to turn an address into a confirmed mailbox (`smtp_reason: delivered_recently`), and a bounce rate above half for the domain removes twenty points from every confidence estimate on it.
## SPF and DMARC at Proofpoint
Companies that also send through Proofpoint add the include their console shows. Proofpoint Essentials documents regional values such as `include:_spf-us.ppe-hosted.com` and `include:_spf-eu.ppe-hosted.com`; enterprise (pphosted.com) customers receive per-customer authorized hosts from Proofpoint rather than one universal include, so the exact value should be copied from the admin console. Inbound filtering leaves the domain's own SPF and DMARC untouched, and Spaw reports them live in `has_spf` and `dmarc_policy`.
## Practical notes for sending to domains behind Proofpoint
Proofpoint customers are mostly large organisations, and the gateway applies reputation, URL rewriting and attachment sandboxing before anything reaches a mailbox. Authentication alignment matters more than at any consumer provider. Expect a high share of `risky` catch-all answers on B2B lists, rank them by `mailbox_confidence`, and keep role addresses out of personalised sequences even when the gateway accepts them.
For a live look at any Proofpoint-protected domain, run it through the free MX lookup and the SPF and DMARC checker.
---
# Proton Mail: MX records and email verification
How Proton-hosted domains look in DNS, how Proton Mail answers a verification handshake, and which verdicts to expect for proton.me and custom domains.
Updated: 2026-09-03
Proton Mail is the encrypted mailbox service from Proton, used both on its own domains (proton.me, protonmail.com, protonmail.ch, pm.me) and on custom domains for paid plans. Spaw reports every Proton-hosted domain as `mx_provider: "proton"`.
## How Proton shows up in DNS
Proton domains publish two MX records, `mail.protonmail.ch` at priority 10 and `mailsec.protonmail.ch` at priority 20. Custom domains on Proton use the same two hosts. Spaw matches the `.protonmail.ch` suffix; Proton is a mailbox host, so the MX target is Proton's own inbound server and `mx_implicit` is false.
## How Proton answers a verification handshake
Proton typically rejects unknown recipients during the handshake, so unknown usernames on proton.me answer `mailbox_not_found` with `smtp_reason: no_mailbox` on the first attempt. Custom domains on Proton can have a catch-all address configured by the domain's administrator; those domains answer as catch-all. Proton's inbound servers are conservative with unfamiliar senders and occasionally defer a first connection, which shows up as an unverified answer with `smtp_reason: greylisted`.
## What that means for your verdicts
For Proton's own consumer domains, Spaw applies Proton's username rules before any probe: letters, digits, dots, underscores and hyphens. An address that breaks them is answered `undeliverable` with `reason: invalid_local_part` at no cost. Valid addresses are then probed and come back `deliverable` with `mailbox_exists: true` or `undeliverable` with `reason: mailbox_not_found`.
Custom domains behave like any business domain: a clean reject or accept when catch-all is off, `risky` with `reason: catch_all` and a `mailbox_confidence` estimate when it is on. Proton is not on Spaw's list of established providers for the confidence bonus, so the estimate leans on the domain's DMARC and SPF records, its registration age and the shape of the username.
## SPF and DMARC at Proton
Proton's anti-spoofing guide tells custom-domain owners to add `include:_spf.protonmail.ch` to their SPF record, to add the DKIM CNAME records from the Proton dashboard, and to publish DMARC starting with `p=quarantine`, moving to `p=reject` once legitimate mail passes. Proton's own domains publish strict policies. Spaw reports the live state of a custom domain in `has_spf` and `dmarc_policy`.
## Practical notes for sending to Proton users
Proton supports plus-addressing on its own domains, reported by Spaw as `is_alias: true`. Because Proton is chosen by privacy-conscious users, complaint rates on unsolicited mail tend to be high and Proton's filtering weighs sender reputation heavily, so authenticate your sending domain properly and avoid sending to addresses you have not verified. Proton's address book aliases (`pm.me` for paid users) resolve to the same account and are handled like any other Proton domain.
For a live look at any Proton-hosted domain, run it through the free MX lookup and the SPF and DMARC checker.
---
# Rackspace Email: MX records and email verification
How Rackspace-hosted domains look in DNS, how Rackspace Email answers an SMTP verification handshake, and which verdicts to expect for addresses on it.
Updated: 2026-09-03
Rackspace Email is a hosted mailbox service for small businesses, sold directly and through many web hosts and resellers. Its infrastructure runs under the `emailsrvr.com` name, so Spaw reports these domains as `mx_provider: "rackspace"` even though rackspace.com itself, the company's corporate domain, is hosted on Microsoft 365.
## How Rackspace shows up in DNS
Rackspace Email asks customers to publish exactly two MX records, `mx1.emailsrvr.com` at priority 10 and `mx2.emailsrvr.com` at priority 20, and no others. Spaw matches the `.emailsrvr.com` suffix. Rackspace is a mailbox host: the MX target stores the mail, `mx_found` is true and `mx_implicit` is false. Hosted Exchange customers at Rackspace use different records and are classified by whatever their MX points at.
## How Rackspace answers a verification handshake
Rackspace Email typically rejects unknown recipients at `RCPT TO`, so most checks settle on the first attempt. The admin control panel lets a domain owner configure a catch-all mailbox, and domains with one answer as catch-all. Rackspace's inbound servers also apply rate limits and reputation checks, and a probe from a source they have not seen is occasionally deferred.
## What that means for your verdicts
Expect `deliverable` with `mailbox_exists: true` or `undeliverable` with `reason: mailbox_not_found` and `smtp_reason: no_mailbox` for most domains, and `risky` with `reason: catch_all` for the ones with a catch-all mailbox. For catch-all domains Spaw returns `mailbox_confidence`; Rackspace is not on the established-provider list, so the estimate depends on the domain's DMARC and SPF, its age, the username's shape and your own delivery data. There are no consumer username rules for Rackspace domains, since every Rackspace domain is a customer's own domain.
Rackspace's customers are small businesses, and their address lists carry many role inboxes: `info@`, `office@`, `sales@`. Spaw flags them `risky` with `reason: role` even when the handshake confirms they exist. Staff turnover shows up as `mailbox_not_found` once the account is deleted, and the address then joins your suppression list.
## SPF and DMARC at Rackspace
Rackspace's documentation asks domain owners to publish `v=spf1 include:emailsrvr.com ~all` when Rackspace Email is the only sender, and to enable DKIM from the control panel; any other service sending as the domain needs its own include. Spaw reports the live state in `has_spf` and `dmarc_policy`, and a company domain without SPF adds ten risk points to its addresses.
## Practical notes for sending to Rackspace users
Because most Rackspace domains belong to small companies that manage DNS through a web host or reseller, SPF and DMARC are frequently missing or misconfigured on the receiving side, which affects their own deliverability but not yours. On your side, an aligned sending domain and a clean list are enough; Rackspace's filtering is conventional. Re-verify these lists on a schedule, since small-business staff and role inboxes change often.
For a live look at any Rackspace-hosted domain, run it through the free MX lookup and the SPF and DMARC checker.
---
# Tuta (Tutanota): MX records and email verification
How Tuta-hosted domains look in DNS, how Tuta answers a verification handshake, and which verdicts to expect for tuta.com, tutanota.com and custom domains.
Updated: 2026-09-03
Tuta, known as Tutanota until its 2023 rebrand, is an end-to-end encrypted mailbox service run by Tutao GmbH in Germany. It serves its own domains (tuta.com, tuta.io, tutanota.com, tutanota.de, tutamail.com, keemail.me) and custom domains on paid plans, all from infrastructure under the `tutanota.de` name. Spaw reports them as `mx_provider: "tutanota"`.
## How Tuta shows up in DNS
Every Tuta-hosted domain publishes a single MX record, `mail.tutanota.de`. Spaw matches the `.tutanota.de` suffix. Tuta is a mailbox host: the MX target is Tuta's own inbound server, which encrypts incoming mail for the recipient's key on arrival. `mx_found` is true and `mx_implicit` is false for a correctly configured domain.
## How Tuta answers a verification handshake
Tuta typically rejects unknown recipients at `RCPT TO`, so a missing mailbox is confirmed in the session. Custom domains on Tuta can configure a catch-all address from the settings, and those domains answer as catch-all. Tuta's inbound server is conservative with unfamiliar sources, so occasional deferrals show up as `smtp_reason: greylisted`.
## What that means for your verdicts
Most Tuta addresses come back `deliverable` with `mailbox_exists: true` or `undeliverable` with `reason: mailbox_not_found` and `smtp_reason: no_mailbox`. Custom domains with catch-all enabled answer `risky` with `reason: catch_all`; Spaw remembers the catch-all for seven days and returns `mailbox_confidence`, which without the established-provider bonus leans on the domain's own records, its age and the shape of the username. Tuta is not in the consumer username rule set, so `invalid_local_part` does not apply.
One point deserves care. Because Tuta offers free, anonymous, encrypted accounts, some disposable-domain lists include Tuta's public domains, and a lookup can then answer `risky` with `reason: disposable` for a real, long-lived mailbox. Spaw's disposable allowlist exists for exactly this kind of false positive; if you see Tuta's public domains flagged as disposable on your lists, treat the flag with suspicion and check the domain on the free disposable checker, which shows which list produced the match.
## SPF and DMARC at Tuta
Tuta supports SPF, DKIM and DMARC on custom domains and shows the exact records to publish in its domain setup wizard; the SPF value in common use is `v=spf1 include:spf.tutanota.de -all`, and the wizard also provides the DKIM CNAME records and a suggested DMARC record. Tuta's own domains publish strict policies. Spaw reports the live state of a custom domain in `has_spf` and `dmarc_policy`.
## Practical notes for sending to Tuta users
Tuta users chose an encrypted provider on purpose, and Tuta's spam filtering and its users' tolerance for marketing mail are both strict. Send only what was asked for, authenticate properly, and expect complaint-driven blocks to be quick. Tuta does not offer Gmail-style plus-addressing on its own domains; users create separate aliases instead, which look like ordinary addresses to a verifier and answer individually.
For a live look at any Tuta-hosted domain, run it through the free MX lookup and the SPF and DMARC checker.
---
# Yahoo Mail and AOL: MX records and email verification
How Yahoo and AOL domains look in DNS, why their servers often leave a verification handshake unverified, and which verdicts to expect for those addresses.
Updated: 2026-09-03
Yahoo Mail and AOL Mail run on the same infrastructure since Yahoo acquired AOL, and the merged platform also hosts a long list of country domains (yahoo.co.uk, yahoo.fr, ymail.com, rocketmail.com and more). Spaw reports all of them as `mx_provider: "yahoo"`.
## How Yahoo shows up in DNS
Yahoo domains publish MX records under `yahoodns.net`: yahoo.com points at hosts such as `mta5.am0.yahoodns.net`, `mta6.am0.yahoodns.net` and `mta7.am0.yahoodns.net`, and aol.com at `mx-aol.mail.gm0.yahoodns.net`. Spaw matches the `.yahoodns.net` suffix. Yahoo is a mailbox host, but a large consumer one with an aggressive perimeter, and that perimeter is what a verification handshake talks to.
## How Yahoo answers a verification handshake
Yahoo's inbound servers are the most frequent source of unverified answers among the big consumer providers. They commonly defer a first connection from an unfamiliar sender with a temporary failure, apply per-connection rate limits, and sometimes accept a recipient at `RCPT TO` only to decide later. A verification probe cannot wait the minutes a deferral asks for, so many Yahoo lookups end with `smtp_checked: false` and an `smtp_reason` of `greylisted` or `timeout`. Clear rejections do happen, and when they do they are reliable: an address Yahoo says does not exist really does not.
## What that means for your verdicts
Expect three kinds of answers. A confirmed mailbox gives `deliverable` with `mailbox_exists: true`. A confirmed missing mailbox gives `undeliverable` with `reason: mailbox_not_found`. And a large share of lookups gives `deliverable` with `smtp_checked: false`, `mailbox_exists: null` and a `mailbox_confidence` estimate; the unverified state adds ten risk points, so these addresses usually land in the low band rather than at zero.
Before any probe, Yahoo's username rules apply to yahoo.com, aol.com and their listed variants: letters, digits, dots, underscores and hyphens. Anything else is `undeliverable` with `reason: invalid_local_part` at no cost. Yahoo is not on Spaw's established-provider list for the confidence bonus, because a consumer perimeter that defers probes says little about individual mailboxes.
The most effective way to firm up Yahoo answers is your own delivery data. Report delivered and bounced outcomes through the feedback endpoint or an ESP webhook: a delivered report turns later lookups of that address into a confirmed mailbox with `smtp_reason: delivered_recently`, and the bounce rate of the domain feeds every confidence estimate on it. For an unverified single lookup, pass `callback_url` so Spaw re-checks after 5 and 20 minutes and pushes the settled verdict.
## SPF and DMARC at Yahoo
Consumer Yahoo and AOL users have nothing to configure; yahoo.com and aol.com publish their own SPF and a DMARC policy of `p=reject`. That policy has a practical consequence for senders: mail that claims a yahoo.com or aol.com From address but is sent through another service fails DMARC and is rejected. Spaw's `dmarc_policy` field shows the live policy for any domain.
## Practical notes for sending to Yahoo users
Yahoo joined Google in requiring aligned SPF or DKIM, a DMARC record, one-click unsubscribe and a low complaint rate from bulk senders in 2024, and it recycles abandoned addresses into spam traps more readily than most providers. Old Yahoo addresses on a list are therefore worth re-verifying before every campaign, and a `mailbox_not_found` answer on one should be treated as final. Yahoo does not support plus-addressing in the Gmail sense; it uses a separate disposable-address feature with a hyphen, which looks like a normal address to a verifier.
For a live look at any Yahoo-hosted domain, run it through the free MX lookup and the SPF and DMARC checker.
---
# Yandex 360 and Yandex Mail: MX records and email verification
How domains on Yandex 360 for Business look in DNS, how Yandex answers an SMTP verification handshake, and which verdicts to expect for Yandex-hosted addresses.
Updated: 2026-09-03
Yandex runs the largest mailbox service in the Russian-speaking world: consumer Yandex Mail on yandex.ru and its sister domains, and Yandex 360 for Business for company domains. Business domains point their MX at `mx.yandex.net` and Yandex's own consumer domains at `mx.yandex.ru`; Spaw matches both and reports `mx_provider: "yandex"`.
## How Yandex shows up in DNS
Yandex 360 for Business asks domain owners to publish one MX record, `mx.yandex.net` at priority 10, or to delegate the domain to Yandex's DNS, which configures it automatically. Spaw matches the `.yandex.net` suffix, and the `.yandex.ru` suffix that Yandex's own consumer domains, including yandex.com and yandex.ru, currently resolve to. Yandex is a mailbox host: `mx_found` is true and `mx_implicit` is false for a correctly configured business domain.
## How Yandex answers a verification handshake
Yandex typically rejects unknown recipients at `RCPT TO`, so most business-domain checks settle on the first attempt. Its inbound servers are cautious with senders they have not seen and can defer a first connection, which leaves some probes unverified with `smtp_reason: greylisted`. A Yandex 360 administrator can configure a catch-all mailbox per domain, and those domains answer as catch-all.
## What that means for your verdicts
Expect `deliverable` with `mailbox_exists: true`, `undeliverable` with `reason: mailbox_not_found` and `smtp_reason: no_mailbox`, or `risky` with `reason: catch_all` on domains with a catch-all mailbox. For catch-all domains Spaw returns `mailbox_confidence`, which without the established-provider bonus is driven by the domain's DMARC and SPF, its registration age, the shape of the username and your own delivery outcomes. There are no consumer username rules for Yandex domains in Spaw's rule set, so `invalid_local_part` does not apply; the RFC syntax check still does, and Cyrillic domain names are converted to their punycode form before any lookup.
## SPF and DMARC at Yandex
Yandex 360's documentation gives `v=spf1 redirect=_spf.yandex.net` as the SPF record when Yandex is the only sender, or `v=spf1 ip4:... include:_spf.yandex.net ~all` when other servers send too, and it provides a DKIM key from the admin console. Delegated domains get these records automatically. Spaw reports the live state in `has_spf` and `dmarc_policy`; a business domain without SPF adds ten risk points to its addresses.
## Practical notes for sending to Yandex users
Yandex's filtering leans on sender authentication and on its own reputation data, and it expects List-Unsubscribe headers from bulk senders in the same way the large western providers do. Many Yandex-hosted business domains use Cyrillic or transliterated usernames; Spaw's name dictionary is English-centric, so `is_gibberish` is more likely to be wrong on these lists and should be treated as a hint rather than a rule. Plus-addressing is supported and reported as `is_alias: true`.
For a live look at any Yandex-hosted domain, run it through the free MX lookup and the SPF and DMARC checker.
---
# Zoho Mail: MX records and email verification
How Zoho-hosted domains look in DNS, how Zoho answers an SMTP verification handshake, and which verdicts to expect for Zoho Mail addresses.
Updated: 2026-09-03
Zoho Mail is the mailbox service inside the Zoho suite, popular with small companies that already run Zoho CRM or Books. Spaw reports it as `mx_provider: "zoho"`.
## How Zoho shows up in DNS
A Zoho-hosted domain publishes three MX records: `mx.zoho.com`, `mx2.zoho.com` and `mx3.zoho.com` at priorities 10, 20 and 50. Customers on the European data centre use `mx.zoho.eu` and its siblings, and Zoho also runs regional endpoints under `zoho.in`, `zoho.com.au` and others. Spaw matches the `.zoho.com` and `.zoho.eu` suffixes, which cover the two largest regions; a domain on another regional endpoint currently classifies as `other`, which the classifier will pick up in a later revision of its patterns.
Zoho is a mailbox host: the MX points at Zoho's own inbound servers, which store the mail. `mx_found` is true and `mx_implicit` is false for a correctly configured domain.
## How Zoho answers a verification handshake
Zoho typically rejects unknown recipients at `RCPT TO`, so the mailbox check settles on the first attempt for most domains. Administrators can enable a catch-all address per domain from the Zoho admin console, and when they do the inbound server accepts every recipient and the domain answers as catch-all. Greylisting is not the norm for a single probe, though Zoho does rate-limit senders it has not seen before, which occasionally leaves an answer unverified with `smtp_reason: greylisted` or `timeout`.
## What that means for your verdicts
Most Zoho addresses come back `deliverable` with `mailbox_exists: true` or `undeliverable` with `reason: mailbox_not_found`. Domains with a catch-all configured answer `risky` with `reason: catch_all`; Spaw remembers that for seven days and returns `mailbox_confidence`, which for Zoho includes the ten-point bonus for an established provider. Zoho has no consumer username rules in Spaw's rule set, so `invalid_local_part` does not apply to it; the RFC syntax check and the domain checks still run.
Small companies on Zoho often route several role inboxes to one person. Spaw still flags `info@`, `sales@` and similar as `risky` with `reason: role`, and the handshake usually confirms that the inbox exists, so `mailbox_exists` can be true on a risky answer. That combination means the address is real but shared.
## SPF and DMARC at Zoho
Zoho's SPF documentation asks domain owners to publish `v=spf1 include:zohomail.com -all`, or `v=spf1 include:one.zoho.com -all` when several Zoho services send on the domain's behalf, and to add the DKIM selector from the admin console. Older setups still carry `include:zoho.com`, which continues to resolve. Spaw reports the live state in `has_spf` and `dmarc_policy`; a Zoho-hosted company domain without SPF adds ten risk points to its addresses.
## Practical notes for sending to Zoho users
Zoho supports plus-addressing, which Spaw reports as `is_alias: true`, and it applies its own spam filtering aggressively to new senders, so a warmed-up sending domain with aligned SPF and DKIM matters more here than at the large consumer providers. Because Zoho customers are mostly businesses, expect a higher share of role addresses and treat `mailbox_exists: true` on a role address as "real but shared".
For a live look at any Zoho-hosted domain, run it through the free MX lookup and the SPF and DMARC checker.
---
# iCloud Mail: MX records and email verification
How iCloud Mail and iCloud custom domains look in DNS, how Apple's servers answer a verification handshake, and which verdicts to expect.
Updated: 2026-09-03
iCloud Mail serves Apple's consumer accounts on icloud.com, together with the legacy me.com and mac.com addresses that still deliver to the same mailboxes, and since 2021 it also hosts custom domains for iCloud+ subscribers. Spaw reports all of them as `mx_provider: "icloud"`.
## How iCloud shows up in DNS
Every iCloud-hosted domain publishes the same pair of MX records, `mx01.mail.icloud.com` and `mx02.mail.icloud.com`, both at priority 10. Apple's setup guide for custom domains uses exactly these two hosts. Spaw matches the `.mail.icloud.com` suffix. iCloud is a mailbox host: the MX target is Apple's own inbound server, `mx_found` is true and `mx_implicit` is false.
## How iCloud answers a verification handshake
Apple's inbound servers typically reject unknown recipients at `RCPT TO`, so a missing icloud.com mailbox is confirmed in the session. They are also strict about sender reputation and will defer or drop connections from sources they distrust, which occasionally leaves a probe unverified with `smtp_reason: greylisted` or `no_connect`. Custom domains on iCloud+ can enable a catch-all address from the iCloud settings, and those domains then answer as catch-all.
## What that means for your verdicts
For icloud.com, me.com and mac.com, Apple's username rules apply before any probe: letters, digits, dots, underscores and hyphens. Anything else is `undeliverable` with `reason: invalid_local_part` without a handshake. Valid addresses are probed and mostly come back `deliverable` with `mailbox_exists: true` or `undeliverable` with `reason: mailbox_not_found` and `smtp_reason: no_mailbox`.
Hide My Email, Apple's relay feature, generates single-purpose addresses on `privaterelay.appleid.com` rather than on icloud.com, so they classify under a different provider; they exist until the user deactivates them, after which they bounce. Custom domains with catch-all enabled answer `risky` with `reason: catch_all` and a `mailbox_confidence` estimate; iCloud is not on the established-provider list, so the estimate relies on the domain's own records, its age and the shape of the username.
## SPF and DMARC at iCloud
For custom domains, Apple's guide publishes `v=spf1 include:icloud.com ~all` for SPF, a DKIM CNAME with the selector shown during setup, and a DMARC record. icloud.com itself publishes SPF and a DMARC policy, so From addresses at icloud.com sent through other services fail alignment. Spaw reports the live state of any domain in `has_spf` and `dmarc_policy`.
## Practical notes for sending to iCloud users
Apple's Mail Privacy Protection loads remote content through Apple's proxies, which makes open rates for iCloud users unreliable and makes engagement data a poor substitute for verification. Legacy me.com and mac.com addresses are still live for the accounts that had them, so do not discard them on age alone; verify them. iCloud does not use Gmail-style plus-addressing for consumer accounts, and role-style usernames on icloud.com are individual accounts rather than shared inboxes, though Spaw still applies its role-word list to them.
For a live look at any iCloud-hosted domain, run it through the free MX lookup and the SPF and DMARC checker.
---
# Spaw vs Abstract API: email verification API comparison
How Spaw and Abstract API compare on pricing, billing rules, verification depth and limits, from Abstract API's public pages checked on 2026-09-03. Corrections: support@spaw.co.
| | Spaw | Abstract API |
| --- | --- | --- |
| Pricing model | Pay-as-you-go credit packs bought through Stripe Checkout; no subscription, credits never expire | Monthly subscription tiers with a request slider from 5,000 to 150,000 requests a month; Starter from $17, Standard from $37, Professional from $39 a month |
| Free tier | 10 free credits every month, no card required | 100 requests a month |
| Price per 1,000 at about 10k | $7.80 (Growth pack: 5,000 credits for $39) | not published |
| Price per 1,000 at about 100k | $4.99 (Volume pack: 100,000 credits for $499) | not published |
| Unknown or catch-all results billed | Yes, by verdict: a deliverable or risky answer costs 1 credit even when the mailbox is unconfirmed; the automatic re-checks are free | Yes |
| Undeliverable results billed | No, undeliverable verdicts are always free | Yes |
| SMTP mailbox check | Yes | Yes |
| Catch-all handling | A catch-all answer is remembered per domain for 7 days, and every unconfirmed mailbox carries a published 0 to 100 mailbox_confidence estimate | Catch-all detection on all plans; every request counts against the quota |
| Bulk limit | 100,000 addresses per bulk job, with an HMAC-signed completion webhook | Bulk CSV upload from the Starter plan up |
| Batch API | 50 addresses per synchronous request, probes sent concurrently | not published |
| Rate limit | 5 requests per second per key | 3 requests a second on paid tiers |
| Official SDKs | Python, Node.js, PHP | not published |
| Data retention | Lookup history kept for 30 days, then pruned; test addresses are never logged | not published |
## When Abstract API fits better
Developers who want one vendor for several small utility APIs under a monthly quota.
- Quality score, risk assessment, breach detection and role detection are Professional-plan features
- SOC 2 Type II and GDPR, CCPA statements
- A commercial license is listed under the Enterprise plan
## When Spaw fits better
Developers who want one flat price per answered lookup, a transparent risk score, and an API that documents every field.
- Abstract's quality score and role detection sit on its Professional plan; Spaw returns all 27 fields, including risk_score and role, on every lookup
- Abstract counts every request against the quota; Spaw bills per answered lookup
- Abstract's stated rate limit is 3 requests a second; Spaw's is 5
- Spaw's bulk jobs take 100,000 addresses through the API with a signed webhook
## Questions
**Does Spaw charge for catch-all or unknown results?** Spaw bills per answer, not per probe outcome: a lookup that comes back deliverable or risky costs 1 credit even when the mailbox could not be confirmed, for example on a catch-all domain or after greylisting. Undeliverable verdicts, invalid input, repeats within 7 days and the automatic re-checks behind callback_url are free. Abstract API counts them according to its public pages.
**Can I move a list from Abstract API to Spaw?** Yes. Upload the CSV to the dashboard or POST it to the bulk endpoint; Spaw keeps your columns, adds the verdict columns, and lets you download only the deliverable, risky or undeliverable rows. Addresses that come back undeliverable join your suppression list so later runs skip them for free.
**Is there a free tier?** Every Spaw account receives 10 free credits at the start of each month with no card on file, and unused credits accumulate. Abstract API offers: 100 requests a month.
## Sources (checked 2026-09-03)
- [Abstract API email validation page](https://www.abstractapi.com/api/email-verification-validation-api)
Abstract API is a trademark of its owner. Spaw is not affiliated with it.
Reference: https://spaw.co/compare/abstract
---
# Abstract API alternatives for email verification
Email verification APIs to consider instead of Abstract API, with the pricing model, free tier and price per 1,000 verifications each vendor publishes. Figures come from public pricing pages checked on 2026-09-03.
| Service | Pricing model | Free tier | Price per 1,000 at about 10k |
| --- | --- | --- | --- |
| [Spaw](https://spaw.co) | Pay-as-you-go credit packs bought through Stripe Checkout; no subscription, credits never expire | 10 free credits every month, no card required | $7.80 (Growth pack: 5,000 credits for $39) |
| [Bouncer](https://www.usebouncer.com) | Pay-as-you-go credits that never expire (minimum 1,000 credits for $8), auto-refill, plus a Deliverability Kit subscription at $25 to $250 a month | 100 free credits, no card required | $6.00 (10,000 credits for $60) |
| [Emailable](https://emailable.com) | Pay-as-you-go credits that never expire, or a monthly subscription at a 15% discount; minimum purchase 5,000 credits | 250 free credits at signup | $7.60 (pay-as-you-go at $0.0076 a credit; a 10K monthly subscription costs $32.30) |
| [Hunter](https://hunter.io) | Monthly or yearly subscriptions with unified credits shared across search, verification and enrichment; credit packs on paid plans (valid 3 months) | Free plan with 50 credits a month; one verification costs 0.5 credit, so 100 verifications | $7.45 (Growth: $149 a month for 10,000 credits, 20,000 verifications at 0.5 credit each; $5.20 billed yearly) |
| [Kickbox](https://kickbox.com) | not published | not published | not published |
| [Mailboxlayer](https://mailboxlayer.com) | Monthly or yearly subscription with a request quota per plan and per-request overage | 100 requests a month | $3.00 (Basic: 5,000 requests for $14.99 a month, overage $0.012 a request; Professional Plus: 50,000 for $74.99, $1.50 per 1,000) |
| [NeverBounce](https://www.neverbounce.com) | not published | not published | not published |
| [Verifalia](https://verifalia.com) | Pay-as-you-go credit packs that never expire, or monthly, quarterly and yearly subscriptions; verification depth is priced as quality levels | 25 free credits a day on the Free plan; unused daily credits expire each day | not published |
| [ZeroBounce](https://www.zerobounce.net) | Pay-as-you-go credits that never expire, or the ZeroBounce ONE subscription; volume pricing shown through a calculator rather than fixed packs | 100 validation credits a month on the free account | not published |
## Spaw
Email verification API that bills per answered lookup: syntax, live MX, SPF and DMARC, disposable and role lists, and an SMTP mailbox handshake that never sends a message.
## Bouncer
Bouncer is an email verification service with pay-as-you-go credits that never expire, high-throughput batch APIs, a real-time form product (Bouncer Shield) and a Deliverability Kit subscription.
Comparison: https://spaw.co/compare/bouncer
## Emailable
Emailable is an email verification service with pay-as-you-go credits, a bulk verifier, a widget and an API, with Node.js, Ruby and Python libraries.
Comparison: https://spaw.co/compare/emailable
## Hunter
Hunter is an outreach platform whose Email Verifier shares unified plan credits with its email finder, enrichment and campaign tools.
Comparison: https://spaw.co/compare/hunter
## Kickbox
Kickbox is an email verification API that returns deliverable, undeliverable, risky or unknown plus a 0 to 1 Sendex quality score, with official libraries for PHP, Node.js, Ruby and Python.
Comparison: https://spaw.co/compare/kickbox
## Mailboxlayer
Mailboxlayer is apilayer's email validation API, sold as monthly request quotas from a free 100-request plan up to 250,000 requests a month.
Comparison: https://spaw.co/compare/mailboxlayer
## NeverBounce
NeverBounce is a ZoomInfo-owned email verification service with a bulk list-cleaning workflow, a real-time API and official client libraries in six languages.
Comparison: https://spaw.co/compare/neverbounce
## Verifalia
Verifalia is an email verification service with selectable quality levels, credit packs that never expire, subscriptions with daily free credits, and official SDKs in six languages.
Comparison: https://spaw.co/compare/verifalia
## ZeroBounce
ZeroBounce is an email validation service with a pay-as-you-go credit model and a subscription (ZeroBounce ONE) that bundles deliverability tools such as activity data, inbox placement tests and blacklist monitoring.
Comparison: https://spaw.co/compare/zerobounce
Abstract API and the other product names are trademarks of their owners. Spaw is not affiliated with them.
Reference: https://spaw.co/alternatives/abstract
---
# Spaw vs Bouncer: email verification API comparison
How Spaw and Bouncer compare on pricing, billing rules, verification depth and limits, from Bouncer's public pages checked on 2026-09-03. Corrections: support@spaw.co.
| | Spaw | Bouncer |
| --- | --- | --- |
| Pricing model | Pay-as-you-go credit packs bought through Stripe Checkout; no subscription, credits never expire | Pay-as-you-go credits that never expire (minimum 1,000 credits for $8), auto-refill, plus a Deliverability Kit subscription at $25 to $250 a month |
| Free tier | 10 free credits every month, no card required | 100 free credits, no card required |
| Price per 1,000 at about 10k | $7.80 (Growth pack: 5,000 credits for $39) | $6.00 (10,000 credits for $60) |
| Price per 1,000 at about 100k | $4.99 (Volume pack: 100,000 credits for $499) | $4.00 (100,000 credits for $400) |
| Unknown or catch-all results billed | Yes, by verdict: a deliverable or risky answer costs 1 credit even when the mailbox is unconfirmed; the automatic re-checks are free | No |
| Undeliverable results billed | No, undeliverable verdicts are always free | Yes |
| SMTP mailbox check | Yes | not published |
| Catch-all handling | A catch-all answer is remembered per domain for 7 days, and every unconfirmed mailbox carries a published 0 to 100 mailbox_confidence estimate | Returns an acceptAll domain property; unknown results and duplicates within a list are never charged |
| Bulk limit | 100,000 addresses per bulk job, with an HMAC-signed completion webhook | Up to 100,000 emails per batch (1,000 to 10,000 recommended); up to 60 batches a minute |
| Batch API | 50 addresses per synchronous request, probes sent concurrently | Synchronous batch of up to 10,000 emails at 100 requests a minute; asynchronous batches as above |
| Rate limit | 5 requests per second per key | Real-time API: 1,000 requests a minute by default |
| Official SDKs | Python, Node.js, PHP | Java |
| Data retention | Lookup history kept for 30 days, then pruned; test addresses are never logged | The batch and sync APIs keep a 24-hour cache so an address is not verified twice; a broader retention policy is not stated on the pages checked |
## When Bouncer fits better
Teams that want the lowest published per-check price with high batch throughput and a real-time form product.
- Bouncer Shield protects forms in real time
- Toxicity list checks
- Auto-refill and volume discounts
## When Spaw fits better
Developers who want one flat price per answered lookup, a transparent risk score, and an API that documents every field.
- Bouncer's published rates are $6.00 per 1,000 at 10,000 credits and $4.00 at 100,000; Spaw's are $7.80 and $4.99
- Both leave unknown results unbilled; Spaw also leaves undeliverable verdicts free
- Spaw's rate limit is 5 requests a second, 300 a minute; Bouncer's real-time API allows 1,000 a minute
- Spaw ships Python, Node.js and PHP clients plus an MCP server; Bouncer ships a Java client
## Questions
**Does Spaw charge for catch-all or unknown results?** Spaw bills per answer, not per probe outcome: a lookup that comes back deliverable or risky costs 1 credit even when the mailbox could not be confirmed, for example on a catch-all domain or after greylisting. Undeliverable verdicts, invalid input, repeats within 7 days and the automatic re-checks behind callback_url are free. Bouncer states that it does not bill unknown results.
**Can I move a list from Bouncer to Spaw?** Yes. Upload the CSV to the dashboard or POST it to the bulk endpoint; Spaw keeps your columns, adds the verdict columns, and lets you download only the deliverable, risky or undeliverable rows. Addresses that come back undeliverable join your suppression list so later runs skip them for free.
**Is there a free tier?** Every Spaw account receives 10 free credits at the start of each month with no card on file, and unused credits accumulate. Bouncer offers: 100 free credits, no card required.
## Sources (checked 2026-09-03)
- [Bouncer pricing](https://www.usebouncer.com/pricing/)
- [Bouncer batch API](https://docs.usebouncer.com/api-reference/batch/batch-create)
- [Bouncer batch sync API](https://docs.usebouncer.com/api-reference/batch-sync/batch-sync)
- [Bouncer real-time API](https://docs.usebouncer.com/api-reference/real-time/verify-email)
- [Bouncer integration guidelines](https://docs.usebouncer.com/integration-guidelines)
Bouncer is a trademark of its owner. Spaw is not affiliated with it.
Reference: https://spaw.co/compare/bouncer
---
# Bouncer alternatives for email verification
Email verification APIs to consider instead of Bouncer, with the pricing model, free tier and price per 1,000 verifications each vendor publishes. Figures come from public pricing pages checked on 2026-09-03.
| Service | Pricing model | Free tier | Price per 1,000 at about 10k |
| --- | --- | --- | --- |
| [Spaw](https://spaw.co) | Pay-as-you-go credit packs bought through Stripe Checkout; no subscription, credits never expire | 10 free credits every month, no card required | $7.80 (Growth pack: 5,000 credits for $39) |
| [Abstract API](https://www.abstractapi.com) | Monthly subscription tiers with a request slider from 5,000 to 150,000 requests a month; Starter from $17, Standard from $37, Professional from $39 a month | 100 requests a month | not published |
| [Emailable](https://emailable.com) | Pay-as-you-go credits that never expire, or a monthly subscription at a 15% discount; minimum purchase 5,000 credits | 250 free credits at signup | $7.60 (pay-as-you-go at $0.0076 a credit; a 10K monthly subscription costs $32.30) |
| [Hunter](https://hunter.io) | Monthly or yearly subscriptions with unified credits shared across search, verification and enrichment; credit packs on paid plans (valid 3 months) | Free plan with 50 credits a month; one verification costs 0.5 credit, so 100 verifications | $7.45 (Growth: $149 a month for 10,000 credits, 20,000 verifications at 0.5 credit each; $5.20 billed yearly) |
| [Kickbox](https://kickbox.com) | not published | not published | not published |
| [Mailboxlayer](https://mailboxlayer.com) | Monthly or yearly subscription with a request quota per plan and per-request overage | 100 requests a month | $3.00 (Basic: 5,000 requests for $14.99 a month, overage $0.012 a request; Professional Plus: 50,000 for $74.99, $1.50 per 1,000) |
| [NeverBounce](https://www.neverbounce.com) | not published | not published | not published |
| [Verifalia](https://verifalia.com) | Pay-as-you-go credit packs that never expire, or monthly, quarterly and yearly subscriptions; verification depth is priced as quality levels | 25 free credits a day on the Free plan; unused daily credits expire each day | not published |
| [ZeroBounce](https://www.zerobounce.net) | Pay-as-you-go credits that never expire, or the ZeroBounce ONE subscription; volume pricing shown through a calculator rather than fixed packs | 100 validation credits a month on the free account | not published |
## Spaw
Email verification API that bills per answered lookup: syntax, live MX, SPF and DMARC, disposable and role lists, and an SMTP mailbox handshake that never sends a message.
## Abstract API
Abstract API's email validation endpoint is one of a family of utility APIs sold as monthly request quotas with a free 100-request plan.
Comparison: https://spaw.co/compare/abstract
## Emailable
Emailable is an email verification service with pay-as-you-go credits, a bulk verifier, a widget and an API, with Node.js, Ruby and Python libraries.
Comparison: https://spaw.co/compare/emailable
## Hunter
Hunter is an outreach platform whose Email Verifier shares unified plan credits with its email finder, enrichment and campaign tools.
Comparison: https://spaw.co/compare/hunter
## Kickbox
Kickbox is an email verification API that returns deliverable, undeliverable, risky or unknown plus a 0 to 1 Sendex quality score, with official libraries for PHP, Node.js, Ruby and Python.
Comparison: https://spaw.co/compare/kickbox
## Mailboxlayer
Mailboxlayer is apilayer's email validation API, sold as monthly request quotas from a free 100-request plan up to 250,000 requests a month.
Comparison: https://spaw.co/compare/mailboxlayer
## NeverBounce
NeverBounce is a ZoomInfo-owned email verification service with a bulk list-cleaning workflow, a real-time API and official client libraries in six languages.
Comparison: https://spaw.co/compare/neverbounce
## Verifalia
Verifalia is an email verification service with selectable quality levels, credit packs that never expire, subscriptions with daily free credits, and official SDKs in six languages.
Comparison: https://spaw.co/compare/verifalia
## ZeroBounce
ZeroBounce is an email validation service with a pay-as-you-go credit model and a subscription (ZeroBounce ONE) that bundles deliverability tools such as activity data, inbox placement tests and blacklist monitoring.
Comparison: https://spaw.co/compare/zerobounce
Bouncer and the other product names are trademarks of their owners. Spaw is not affiliated with them.
Reference: https://spaw.co/alternatives/bouncer
---
# Spaw vs Emailable: email verification API comparison
How Spaw and Emailable compare on pricing, billing rules, verification depth and limits, from Emailable's public pages checked on 2026-09-03. Corrections: support@spaw.co.
| | Spaw | Emailable |
| --- | --- | --- |
| Pricing model | Pay-as-you-go credit packs bought through Stripe Checkout; no subscription, credits never expire | Pay-as-you-go credits that never expire, or a monthly subscription at a 15% discount; minimum purchase 5,000 credits |
| Free tier | 10 free credits every month, no card required | 250 free credits at signup |
| Price per 1,000 at about 10k | $7.80 (Growth pack: 5,000 credits for $39) | $7.60 (pay-as-you-go at $0.0076 a credit; a 10K monthly subscription costs $32.30) |
| Price per 1,000 at about 100k | $4.99 (Volume pack: 100,000 credits for $499) | not published |
| Unknown or catch-all results billed | Yes, by verdict: a deliverable or risky answer costs 1 credit even when the mailbox is unconfirmed; the automatic re-checks are free | No |
| Undeliverable results billed | No, undeliverable verdicts are always free | Yes |
| SMTP mailbox check | Yes | not published |
| Catch-all handling | A catch-all answer is remembered per domain for 7 days, and every unconfirmed mailbox carries a published 0 to 100 mailbox_confidence estimate | Returns an accept_all flag with the result; unknown results and duplicates are refunded |
| Bulk limit | 100,000 addresses per bulk job, with an HMAC-signed completion webhook | Lists of any size; about 10,000 addresses processed in 2 to 3 minutes |
| Batch API | 50 addresses per synchronous request, probes sent concurrently | not published |
| Rate limit | 5 requests per second per key | not published |
| Official SDKs | Python, Node.js, PHP | Node.js, Ruby, Python |
| Data retention | Lookup history kept for 30 days, then pruned; test addresses are never logged | All data is deleted after 30 days |
## When Emailable fits better
Marketers who want a simple per-credit price, a fast bulk cleaner and a stated 30-day deletion policy.
- SOC 2 Type II and GDPR statements
- 1 credit per verification across bulk, single, API and widget
- No long-term contracts; pays by card, PayPal, Apple Pay, Google Pay or bank transfer
## When Spaw fits better
Developers who want one flat price per answered lookup, a transparent risk score, and an API that documents every field.
- At about 10,000 lookups Emailable's pay-as-you-go rate is $7.60 per 1,000 and Spaw's Growth pack is $7.80
- Spaw bills nothing for undeliverable verdicts; Emailable bills each completed verification except unknowns and duplicates
- Both delete lookup data after 30 days
- Spaw ships an OpenAPI document, Markdown docs and an MCP server; Emailable ships Node.js, Ruby and Python libraries
## Questions
**Does Spaw charge for catch-all or unknown results?** Spaw bills per answer, not per probe outcome: a lookup that comes back deliverable or risky costs 1 credit even when the mailbox could not be confirmed, for example on a catch-all domain or after greylisting. Undeliverable verdicts, invalid input, repeats within 7 days and the automatic re-checks behind callback_url are free. Emailable states that it does not bill unknown results.
**Can I move a list from Emailable to Spaw?** Yes. Upload the CSV to the dashboard or POST it to the bulk endpoint; Spaw keeps your columns, adds the verdict columns, and lets you download only the deliverable, risky or undeliverable rows. Addresses that come back undeliverable join your suppression list so later runs skip them for free.
**Is there a free tier?** Every Spaw account receives 10 free credits at the start of each month with no card on file, and unused credits accumulate. Emailable offers: 250 free credits at signup.
## Sources (checked 2026-09-03)
- [Emailable pricing](https://emailable.com/pricing/)
- [Emailable API page](https://emailable.com/api/)
- [Emailable API documentation](https://emailable.com/docs/api/)
Emailable is a trademark of its owner. Spaw is not affiliated with it.
Reference: https://spaw.co/compare/emailable
---
# Emailable alternatives for email verification
Email verification APIs to consider instead of Emailable, with the pricing model, free tier and price per 1,000 verifications each vendor publishes. Figures come from public pricing pages checked on 2026-09-03.
| Service | Pricing model | Free tier | Price per 1,000 at about 10k |
| --- | --- | --- | --- |
| [Spaw](https://spaw.co) | Pay-as-you-go credit packs bought through Stripe Checkout; no subscription, credits never expire | 10 free credits every month, no card required | $7.80 (Growth pack: 5,000 credits for $39) |
| [Abstract API](https://www.abstractapi.com) | Monthly subscription tiers with a request slider from 5,000 to 150,000 requests a month; Starter from $17, Standard from $37, Professional from $39 a month | 100 requests a month | not published |
| [Bouncer](https://www.usebouncer.com) | Pay-as-you-go credits that never expire (minimum 1,000 credits for $8), auto-refill, plus a Deliverability Kit subscription at $25 to $250 a month | 100 free credits, no card required | $6.00 (10,000 credits for $60) |
| [Hunter](https://hunter.io) | Monthly or yearly subscriptions with unified credits shared across search, verification and enrichment; credit packs on paid plans (valid 3 months) | Free plan with 50 credits a month; one verification costs 0.5 credit, so 100 verifications | $7.45 (Growth: $149 a month for 10,000 credits, 20,000 verifications at 0.5 credit each; $5.20 billed yearly) |
| [Kickbox](https://kickbox.com) | not published | not published | not published |
| [Mailboxlayer](https://mailboxlayer.com) | Monthly or yearly subscription with a request quota per plan and per-request overage | 100 requests a month | $3.00 (Basic: 5,000 requests for $14.99 a month, overage $0.012 a request; Professional Plus: 50,000 for $74.99, $1.50 per 1,000) |
| [NeverBounce](https://www.neverbounce.com) | not published | not published | not published |
| [Verifalia](https://verifalia.com) | Pay-as-you-go credit packs that never expire, or monthly, quarterly and yearly subscriptions; verification depth is priced as quality levels | 25 free credits a day on the Free plan; unused daily credits expire each day | not published |
| [ZeroBounce](https://www.zerobounce.net) | Pay-as-you-go credits that never expire, or the ZeroBounce ONE subscription; volume pricing shown through a calculator rather than fixed packs | 100 validation credits a month on the free account | not published |
## Spaw
Email verification API that bills per answered lookup: syntax, live MX, SPF and DMARC, disposable and role lists, and an SMTP mailbox handshake that never sends a message.
## Abstract API
Abstract API's email validation endpoint is one of a family of utility APIs sold as monthly request quotas with a free 100-request plan.
Comparison: https://spaw.co/compare/abstract
## Bouncer
Bouncer is an email verification service with pay-as-you-go credits that never expire, high-throughput batch APIs, a real-time form product (Bouncer Shield) and a Deliverability Kit subscription.
Comparison: https://spaw.co/compare/bouncer
## Hunter
Hunter is an outreach platform whose Email Verifier shares unified plan credits with its email finder, enrichment and campaign tools.
Comparison: https://spaw.co/compare/hunter
## Kickbox
Kickbox is an email verification API that returns deliverable, undeliverable, risky or unknown plus a 0 to 1 Sendex quality score, with official libraries for PHP, Node.js, Ruby and Python.
Comparison: https://spaw.co/compare/kickbox
## Mailboxlayer
Mailboxlayer is apilayer's email validation API, sold as monthly request quotas from a free 100-request plan up to 250,000 requests a month.
Comparison: https://spaw.co/compare/mailboxlayer
## NeverBounce
NeverBounce is a ZoomInfo-owned email verification service with a bulk list-cleaning workflow, a real-time API and official client libraries in six languages.
Comparison: https://spaw.co/compare/neverbounce
## Verifalia
Verifalia is an email verification service with selectable quality levels, credit packs that never expire, subscriptions with daily free credits, and official SDKs in six languages.
Comparison: https://spaw.co/compare/verifalia
## ZeroBounce
ZeroBounce is an email validation service with a pay-as-you-go credit model and a subscription (ZeroBounce ONE) that bundles deliverability tools such as activity data, inbox placement tests and blacklist monitoring.
Comparison: https://spaw.co/compare/zerobounce
Emailable and the other product names are trademarks of their owners. Spaw is not affiliated with them.
Reference: https://spaw.co/alternatives/emailable
---
# Spaw vs Hunter: email verification API comparison
How Spaw and Hunter compare on pricing, billing rules, verification depth and limits, from Hunter's public pages checked on 2026-09-03. Corrections: support@spaw.co.
| | Spaw | Hunter |
| --- | --- | --- |
| Pricing model | Pay-as-you-go credit packs bought through Stripe Checkout; no subscription, credits never expire | Monthly or yearly subscriptions with unified credits shared across search, verification and enrichment; credit packs on paid plans (valid 3 months) |
| Free tier | 10 free credits every month, no card required | Free plan with 50 credits a month; one verification costs 0.5 credit, so 100 verifications |
| Price per 1,000 at about 10k | $7.80 (Growth pack: 5,000 credits for $39) | $7.45 (Growth: $149 a month for 10,000 credits, 20,000 verifications at 0.5 credit each; $5.20 billed yearly) |
| Price per 1,000 at about 100k | $4.99 (Volume pack: 100,000 credits for $499) | $5.98 (Scale: $299 a month for 25,000 credits, 50,000 verifications; larger volumes need an Enterprise quote) |
| Unknown or catch-all results billed | Yes, by verdict: a deliverable or risky answer costs 1 credit even when the mailbox is unconfirmed; the automatic re-checks are free | No |
| Undeliverable results billed | No, undeliverable verdicts are always free | Yes |
| SMTP mailbox check | Yes | not published |
| Catch-all handling | A catch-all answer is remembered per domain for 7 days, and every unconfirmed mailbox carries a published 0 to 100 mailbox_confidence estimate | accept_all is one of six statuses (valid, invalid, accept_all, webmail, disposable, unknown); a completed verification costs 0.5 credit, a failed one none |
| Bulk limit | 100,000 addresses per bulk job, with an HMAC-signed completion webhook | not published |
| Batch API | 50 addresses per synchronous request, probes sent concurrently | not published |
| Rate limit | 5 requests per second per key | Email Verifier: 10 requests a second and 300 a minute per key |
| Official SDKs | Python, Node.js, PHP | not published |
| Data retention | Lookup history kept for 30 days, then pruned; test addresses are never logged | not published |
## When Hunter fits better
Sales teams that prospect with Hunter's finder and campaigns and verify addresses as part of that workflow.
- Verification is 0.5 credit; searches and enrichment use the same credits
- Monthly plan credits expire at the end of the billing cycle; yearly plans get the year's credits upfront
- Auto-verification included on paid plans
- USD prices: Starter $49, Growth $149, Scale $299 a month, or $34, $104, $209 billed yearly
## When Spaw fits better
Developers who want one flat price per answered lookup, a transparent risk score, and an API that documents every field.
- Spaw sells verification alone as credit packs that never expire; Hunter's plan credits expire with the billing cycle
- Hunter's free plan allows 100 verifications a month against Spaw's 10 free lookups
- Both skip billing when a verification cannot be completed; Spaw also leaves undeliverable verdicts free
- Spaw returns 27 named fields and a machine-readable reason; Hunter's verifier returns one of six statuses
## Questions
**Does Spaw charge for catch-all or unknown results?** Spaw bills per answer, not per probe outcome: a lookup that comes back deliverable or risky costs 1 credit even when the mailbox could not be confirmed, for example on a catch-all domain or after greylisting. Undeliverable verdicts, invalid input, repeats within 7 days and the automatic re-checks behind callback_url are free. Hunter states that it does not bill unknown results.
**Can I move a list from Hunter to Spaw?** Yes. Upload the CSV to the dashboard or POST it to the bulk endpoint; Spaw keeps your columns, adds the verdict columns, and lets you download only the deliverable, risky or undeliverable rows. Addresses that come back undeliverable join your suppression list so later runs skip them for free.
**Is there a free tier?** Every Spaw account receives 10 free credits at the start of each month with no card on file, and unused credits accumulate. Hunter offers: Free plan with 50 credits a month; one verification costs 0.5 credit, so 100 verifications.
## Sources (checked 2026-09-03)
- [Hunter pricing](https://hunter.io/pricing)
- [Hunter plans (help center)](https://help.hunter.io/en/articles/6001212-all-in-one-outreach-platform-plans)
- [Hunter pricing FAQ](https://help.hunter.io/en/articles/11131690-pricing-plans-and-feature-faqs)
- [Hunter rate limits](https://help.hunter.io/en/articles/1971004-is-there-a-request-p-second-limit)
- [Hunter API documentation](https://hunter.io/api-documentation/v2)
Hunter is a trademark of its owner. Spaw is not affiliated with it.
Reference: https://spaw.co/compare/hunter
---
# Hunter alternatives for email verification
Email verification APIs to consider instead of Hunter, with the pricing model, free tier and price per 1,000 verifications each vendor publishes. Figures come from public pricing pages checked on 2026-09-03.
| Service | Pricing model | Free tier | Price per 1,000 at about 10k |
| --- | --- | --- | --- |
| [Spaw](https://spaw.co) | Pay-as-you-go credit packs bought through Stripe Checkout; no subscription, credits never expire | 10 free credits every month, no card required | $7.80 (Growth pack: 5,000 credits for $39) |
| [Abstract API](https://www.abstractapi.com) | Monthly subscription tiers with a request slider from 5,000 to 150,000 requests a month; Starter from $17, Standard from $37, Professional from $39 a month | 100 requests a month | not published |
| [Bouncer](https://www.usebouncer.com) | Pay-as-you-go credits that never expire (minimum 1,000 credits for $8), auto-refill, plus a Deliverability Kit subscription at $25 to $250 a month | 100 free credits, no card required | $6.00 (10,000 credits for $60) |
| [Emailable](https://emailable.com) | Pay-as-you-go credits that never expire, or a monthly subscription at a 15% discount; minimum purchase 5,000 credits | 250 free credits at signup | $7.60 (pay-as-you-go at $0.0076 a credit; a 10K monthly subscription costs $32.30) |
| [Kickbox](https://kickbox.com) | not published | not published | not published |
| [Mailboxlayer](https://mailboxlayer.com) | Monthly or yearly subscription with a request quota per plan and per-request overage | 100 requests a month | $3.00 (Basic: 5,000 requests for $14.99 a month, overage $0.012 a request; Professional Plus: 50,000 for $74.99, $1.50 per 1,000) |
| [NeverBounce](https://www.neverbounce.com) | not published | not published | not published |
| [Verifalia](https://verifalia.com) | Pay-as-you-go credit packs that never expire, or monthly, quarterly and yearly subscriptions; verification depth is priced as quality levels | 25 free credits a day on the Free plan; unused daily credits expire each day | not published |
| [ZeroBounce](https://www.zerobounce.net) | Pay-as-you-go credits that never expire, or the ZeroBounce ONE subscription; volume pricing shown through a calculator rather than fixed packs | 100 validation credits a month on the free account | not published |
## Spaw
Email verification API that bills per answered lookup: syntax, live MX, SPF and DMARC, disposable and role lists, and an SMTP mailbox handshake that never sends a message.
## Abstract API
Abstract API's email validation endpoint is one of a family of utility APIs sold as monthly request quotas with a free 100-request plan.
Comparison: https://spaw.co/compare/abstract
## Bouncer
Bouncer is an email verification service with pay-as-you-go credits that never expire, high-throughput batch APIs, a real-time form product (Bouncer Shield) and a Deliverability Kit subscription.
Comparison: https://spaw.co/compare/bouncer
## Emailable
Emailable is an email verification service with pay-as-you-go credits, a bulk verifier, a widget and an API, with Node.js, Ruby and Python libraries.
Comparison: https://spaw.co/compare/emailable
## Kickbox
Kickbox is an email verification API that returns deliverable, undeliverable, risky or unknown plus a 0 to 1 Sendex quality score, with official libraries for PHP, Node.js, Ruby and Python.
Comparison: https://spaw.co/compare/kickbox
## Mailboxlayer
Mailboxlayer is apilayer's email validation API, sold as monthly request quotas from a free 100-request plan up to 250,000 requests a month.
Comparison: https://spaw.co/compare/mailboxlayer
## NeverBounce
NeverBounce is a ZoomInfo-owned email verification service with a bulk list-cleaning workflow, a real-time API and official client libraries in six languages.
Comparison: https://spaw.co/compare/neverbounce
## Verifalia
Verifalia is an email verification service with selectable quality levels, credit packs that never expire, subscriptions with daily free credits, and official SDKs in six languages.
Comparison: https://spaw.co/compare/verifalia
## ZeroBounce
ZeroBounce is an email validation service with a pay-as-you-go credit model and a subscription (ZeroBounce ONE) that bundles deliverability tools such as activity data, inbox placement tests and blacklist monitoring.
Comparison: https://spaw.co/compare/zerobounce
Hunter and the other product names are trademarks of their owners. Spaw is not affiliated with them.
Reference: https://spaw.co/alternatives/hunter
---
# Spaw vs Kickbox: email verification API comparison
How Spaw and Kickbox compare on pricing, billing rules, verification depth and limits, from Kickbox's public pages checked on 2026-09-03. Corrections: support@spaw.co.
| | Spaw | Kickbox |
| --- | --- | --- |
| Pricing model | Pay-as-you-go credit packs bought through Stripe Checkout; no subscription, credits never expire | not published |
| Free tier | 10 free credits every month, no card required | not published |
| Price per 1,000 at about 10k | $7.80 (Growth pack: 5,000 credits for $39) | not published |
| Price per 1,000 at about 100k | $4.99 (Volume pack: 100,000 credits for $499) | not published |
| Unknown or catch-all results billed | Yes, by verdict: a deliverable or risky answer costs 1 credit even when the mailbox is unconfirmed; the automatic re-checks are free | not published |
| Undeliverable results billed | No, undeliverable verdicts are always free | not published |
| SMTP mailbox check | Yes | not published |
| Catch-all handling | A catch-all answer is remembered per domain for 7 days, and every unconfirmed mailbox carries a published 0 to 100 mailbox_confidence estimate | Results are deliverable, undeliverable, risky or unknown; how accept-all domains are billed is not published on the pages we could read |
| Bulk limit | 100,000 addresses per bulk job, with an HMAC-signed completion webhook | not published |
| Batch API | 50 addresses per synchronous request, probes sent concurrently | not published |
| Rate limit | 5 requests per second per key | not published |
| Official SDKs | Python, Node.js, PHP | PHP, Node.js, Ruby, Python |
| Data retention | Lookup history kept for 30 days, then pruned; test addresses are never logged | not published |
## When Kickbox fits better
Teams that want a single quality score next to the verdict and already build on one of Kickbox's four official libraries.
- Sendex score: a quality estimate between 0 (no quality) and 1 (perfect quality)
- Four official client libraries on GitHub
## When Spaw fits better
Developers who want one flat price per answered lookup, a transparent risk score, and an API that documents every field.
- Kickbox's website blocked automated reading when we checked, so its prices are not reproduced here; Spaw's packs and free grant are listed at spaw.co/pricing
- Spaw's risk score is a 0 to 100 sum of published weights; Kickbox's Sendex score is a 0 to 1 quality estimate
- Both report deliverable, undeliverable, risky and unknown-style outcomes; Spaw adds a machine-readable reason and 27 named fields per address
- Spaw bills nothing for undeliverable verdicts and repeats within 7 days
## Questions
**Does Spaw charge for catch-all or unknown results?** Spaw bills per answer, not per probe outcome: a lookup that comes back deliverable or risky costs 1 credit even when the mailbox could not be confirmed, for example on a catch-all domain or after greylisting. Undeliverable verdicts, invalid input, repeats within 7 days and the automatic re-checks behind callback_url are free. Kickbox does not publish how it bills them.
**Can I move a list from Kickbox to Spaw?** Yes. Upload the CSV to the dashboard or POST it to the bulk endpoint; Spaw keeps your columns, adds the verdict columns, and lets you download only the deliverable, risky or undeliverable rows. Addresses that come back undeliverable join your suppression list so later runs skip them for free.
**Is there a free tier?** Every Spaw account receives 10 free credits at the start of each month with no card on file, and unused credits accumulate. Kickbox does not publish a free tier on the pages checked.
## Sources (checked 2026-09-03)
- [Kickbox GitHub organization](https://github.com/kickboxio)
- [Kickbox Python client README](https://github.com/kickboxio/kickbox-python)
Kickbox is a trademark of its owner. Spaw is not affiliated with it.
Reference: https://spaw.co/compare/kickbox
---
# Kickbox alternatives for email verification
Email verification APIs to consider instead of Kickbox, with the pricing model, free tier and price per 1,000 verifications each vendor publishes. Figures come from public pricing pages checked on 2026-09-03.
| Service | Pricing model | Free tier | Price per 1,000 at about 10k |
| --- | --- | --- | --- |
| [Spaw](https://spaw.co) | Pay-as-you-go credit packs bought through Stripe Checkout; no subscription, credits never expire | 10 free credits every month, no card required | $7.80 (Growth pack: 5,000 credits for $39) |
| [Abstract API](https://www.abstractapi.com) | Monthly subscription tiers with a request slider from 5,000 to 150,000 requests a month; Starter from $17, Standard from $37, Professional from $39 a month | 100 requests a month | not published |
| [Bouncer](https://www.usebouncer.com) | Pay-as-you-go credits that never expire (minimum 1,000 credits for $8), auto-refill, plus a Deliverability Kit subscription at $25 to $250 a month | 100 free credits, no card required | $6.00 (10,000 credits for $60) |
| [Emailable](https://emailable.com) | Pay-as-you-go credits that never expire, or a monthly subscription at a 15% discount; minimum purchase 5,000 credits | 250 free credits at signup | $7.60 (pay-as-you-go at $0.0076 a credit; a 10K monthly subscription costs $32.30) |
| [Hunter](https://hunter.io) | Monthly or yearly subscriptions with unified credits shared across search, verification and enrichment; credit packs on paid plans (valid 3 months) | Free plan with 50 credits a month; one verification costs 0.5 credit, so 100 verifications | $7.45 (Growth: $149 a month for 10,000 credits, 20,000 verifications at 0.5 credit each; $5.20 billed yearly) |
| [Mailboxlayer](https://mailboxlayer.com) | Monthly or yearly subscription with a request quota per plan and per-request overage | 100 requests a month | $3.00 (Basic: 5,000 requests for $14.99 a month, overage $0.012 a request; Professional Plus: 50,000 for $74.99, $1.50 per 1,000) |
| [NeverBounce](https://www.neverbounce.com) | not published | not published | not published |
| [Verifalia](https://verifalia.com) | Pay-as-you-go credit packs that never expire, or monthly, quarterly and yearly subscriptions; verification depth is priced as quality levels | 25 free credits a day on the Free plan; unused daily credits expire each day | not published |
| [ZeroBounce](https://www.zerobounce.net) | Pay-as-you-go credits that never expire, or the ZeroBounce ONE subscription; volume pricing shown through a calculator rather than fixed packs | 100 validation credits a month on the free account | not published |
## Spaw
Email verification API that bills per answered lookup: syntax, live MX, SPF and DMARC, disposable and role lists, and an SMTP mailbox handshake that never sends a message.
## Abstract API
Abstract API's email validation endpoint is one of a family of utility APIs sold as monthly request quotas with a free 100-request plan.
Comparison: https://spaw.co/compare/abstract
## Bouncer
Bouncer is an email verification service with pay-as-you-go credits that never expire, high-throughput batch APIs, a real-time form product (Bouncer Shield) and a Deliverability Kit subscription.
Comparison: https://spaw.co/compare/bouncer
## Emailable
Emailable is an email verification service with pay-as-you-go credits, a bulk verifier, a widget and an API, with Node.js, Ruby and Python libraries.
Comparison: https://spaw.co/compare/emailable
## Hunter
Hunter is an outreach platform whose Email Verifier shares unified plan credits with its email finder, enrichment and campaign tools.
Comparison: https://spaw.co/compare/hunter
## Mailboxlayer
Mailboxlayer is apilayer's email validation API, sold as monthly request quotas from a free 100-request plan up to 250,000 requests a month.
Comparison: https://spaw.co/compare/mailboxlayer
## NeverBounce
NeverBounce is a ZoomInfo-owned email verification service with a bulk list-cleaning workflow, a real-time API and official client libraries in six languages.
Comparison: https://spaw.co/compare/neverbounce
## Verifalia
Verifalia is an email verification service with selectable quality levels, credit packs that never expire, subscriptions with daily free credits, and official SDKs in six languages.
Comparison: https://spaw.co/compare/verifalia
## ZeroBounce
ZeroBounce is an email validation service with a pay-as-you-go credit model and a subscription (ZeroBounce ONE) that bundles deliverability tools such as activity data, inbox placement tests and blacklist monitoring.
Comparison: https://spaw.co/compare/zerobounce
Kickbox and the other product names are trademarks of their owners. Spaw is not affiliated with them.
Reference: https://spaw.co/alternatives/kickbox
---
# Spaw vs Mailboxlayer: email verification API comparison
How Spaw and Mailboxlayer compare on pricing, billing rules, verification depth and limits, from Mailboxlayer's public pages checked on 2026-09-03. Corrections: support@spaw.co.
| | Spaw | Mailboxlayer |
| --- | --- | --- |
| Pricing model | Pay-as-you-go credit packs bought through Stripe Checkout; no subscription, credits never expire | Monthly or yearly subscription with a request quota per plan and per-request overage |
| Free tier | 10 free credits every month, no card required | 100 requests a month |
| Price per 1,000 at about 10k | $7.80 (Growth pack: 5,000 credits for $39) | $3.00 (Basic: 5,000 requests for $14.99 a month, overage $0.012 a request; Professional Plus: 50,000 for $74.99, $1.50 per 1,000) |
| Price per 1,000 at about 100k | $4.99 (Volume pack: 100,000 credits for $499) | $1.50 to $1.00 (Professional Plus: 50,000 requests for $74.99; Enterprise Plus: 250,000 for $249.99) |
| Unknown or catch-all results billed | Yes, by verdict: a deliverable or risky answer costs 1 credit even when the mailbox is unconfirmed; the automatic re-checks are free | Yes |
| Undeliverable results billed | No, undeliverable verdicts are always free | Yes |
| SMTP mailbox check | Yes | Yes |
| Catch-all handling | A catch-all answer is remembered per domain for 7 days, and every unconfirmed mailbox carries a published 0 to 100 mailbox_confidence estimate | Catch-all detection from the Basic plan up; every request counts against the quota |
| Bulk limit | 100,000 addresses per bulk job, with an HMAC-signed completion webhook | Bulk endpoint: 25 emails per request on Professional Plus, 100 on Enterprise Plus |
| Batch API | 50 addresses per synchronous request, probes sent concurrently | 25 or 100 emails per request depending on plan |
| Rate limit | 5 requests per second per key | not published |
| Official SDKs | Python, Node.js, PHP | not published |
| Data retention | Lookup history kept for 30 days, then pruned; test addresses are never logged | not published |
## When Mailboxlayer fits better
Developers who prefer a flat monthly quota and already use other apilayer APIs.
- SMTP check, MX records, syntax and typo validation, free and disposable databases on paid plans
- 256-bit HTTPS encryption listed for paid plans
- Part of the apilayer marketplace
## When Spaw fits better
Developers who want one flat price per answered lookup, a transparent risk score, and an API that documents every field.
- Mailboxlayer counts every request against a monthly quota; Spaw bills only answered lookups and leaves undeliverable verdicts free
- Spaw's bulk jobs take 100,000 addresses; mailboxlayer's bulk endpoint takes 25 or 100 per request depending on plan
- Spaw includes the SMTP handshake on every lookup including the free monthly grant; mailboxlayer lists the SMTP check on its paid plans
- At high volume mailboxlayer's quota price of $1.00 to $1.50 per 1,000 is below Spaw's $4.99
## Questions
**Does Spaw charge for catch-all or unknown results?** Spaw bills per answer, not per probe outcome: a lookup that comes back deliverable or risky costs 1 credit even when the mailbox could not be confirmed, for example on a catch-all domain or after greylisting. Undeliverable verdicts, invalid input, repeats within 7 days and the automatic re-checks behind callback_url are free. Mailboxlayer counts them according to its public pages.
**Can I move a list from Mailboxlayer to Spaw?** Yes. Upload the CSV to the dashboard or POST it to the bulk endpoint; Spaw keeps your columns, adds the verdict columns, and lets you download only the deliverable, risky or undeliverable rows. Addresses that come back undeliverable join your suppression list so later runs skip them for free.
**Is there a free tier?** Every Spaw account receives 10 free credits at the start of each month with no card on file, and unused credits accumulate. Mailboxlayer offers: 100 requests a month.
## Sources (checked 2026-09-03)
- [Mailboxlayer product and pricing](https://mailboxlayer.com/product)
Mailboxlayer is a trademark of its owner. Spaw is not affiliated with it.
Reference: https://spaw.co/compare/mailboxlayer
---
# Mailboxlayer alternatives for email verification
Email verification APIs to consider instead of Mailboxlayer, with the pricing model, free tier and price per 1,000 verifications each vendor publishes. Figures come from public pricing pages checked on 2026-09-03.
| Service | Pricing model | Free tier | Price per 1,000 at about 10k |
| --- | --- | --- | --- |
| [Spaw](https://spaw.co) | Pay-as-you-go credit packs bought through Stripe Checkout; no subscription, credits never expire | 10 free credits every month, no card required | $7.80 (Growth pack: 5,000 credits for $39) |
| [Abstract API](https://www.abstractapi.com) | Monthly subscription tiers with a request slider from 5,000 to 150,000 requests a month; Starter from $17, Standard from $37, Professional from $39 a month | 100 requests a month | not published |
| [Bouncer](https://www.usebouncer.com) | Pay-as-you-go credits that never expire (minimum 1,000 credits for $8), auto-refill, plus a Deliverability Kit subscription at $25 to $250 a month | 100 free credits, no card required | $6.00 (10,000 credits for $60) |
| [Emailable](https://emailable.com) | Pay-as-you-go credits that never expire, or a monthly subscription at a 15% discount; minimum purchase 5,000 credits | 250 free credits at signup | $7.60 (pay-as-you-go at $0.0076 a credit; a 10K monthly subscription costs $32.30) |
| [Hunter](https://hunter.io) | Monthly or yearly subscriptions with unified credits shared across search, verification and enrichment; credit packs on paid plans (valid 3 months) | Free plan with 50 credits a month; one verification costs 0.5 credit, so 100 verifications | $7.45 (Growth: $149 a month for 10,000 credits, 20,000 verifications at 0.5 credit each; $5.20 billed yearly) |
| [Kickbox](https://kickbox.com) | not published | not published | not published |
| [NeverBounce](https://www.neverbounce.com) | not published | not published | not published |
| [Verifalia](https://verifalia.com) | Pay-as-you-go credit packs that never expire, or monthly, quarterly and yearly subscriptions; verification depth is priced as quality levels | 25 free credits a day on the Free plan; unused daily credits expire each day | not published |
| [ZeroBounce](https://www.zerobounce.net) | Pay-as-you-go credits that never expire, or the ZeroBounce ONE subscription; volume pricing shown through a calculator rather than fixed packs | 100 validation credits a month on the free account | not published |
## Spaw
Email verification API that bills per answered lookup: syntax, live MX, SPF and DMARC, disposable and role lists, and an SMTP mailbox handshake that never sends a message.
## Abstract API
Abstract API's email validation endpoint is one of a family of utility APIs sold as monthly request quotas with a free 100-request plan.
Comparison: https://spaw.co/compare/abstract
## Bouncer
Bouncer is an email verification service with pay-as-you-go credits that never expire, high-throughput batch APIs, a real-time form product (Bouncer Shield) and a Deliverability Kit subscription.
Comparison: https://spaw.co/compare/bouncer
## Emailable
Emailable is an email verification service with pay-as-you-go credits, a bulk verifier, a widget and an API, with Node.js, Ruby and Python libraries.
Comparison: https://spaw.co/compare/emailable
## Hunter
Hunter is an outreach platform whose Email Verifier shares unified plan credits with its email finder, enrichment and campaign tools.
Comparison: https://spaw.co/compare/hunter
## Kickbox
Kickbox is an email verification API that returns deliverable, undeliverable, risky or unknown plus a 0 to 1 Sendex quality score, with official libraries for PHP, Node.js, Ruby and Python.
Comparison: https://spaw.co/compare/kickbox
## NeverBounce
NeverBounce is a ZoomInfo-owned email verification service with a bulk list-cleaning workflow, a real-time API and official client libraries in six languages.
Comparison: https://spaw.co/compare/neverbounce
## Verifalia
Verifalia is an email verification service with selectable quality levels, credit packs that never expire, subscriptions with daily free credits, and official SDKs in six languages.
Comparison: https://spaw.co/compare/verifalia
## ZeroBounce
ZeroBounce is an email validation service with a pay-as-you-go credit model and a subscription (ZeroBounce ONE) that bundles deliverability tools such as activity data, inbox placement tests and blacklist monitoring.
Comparison: https://spaw.co/compare/zerobounce
Mailboxlayer and the other product names are trademarks of their owners. Spaw is not affiliated with them.
Reference: https://spaw.co/alternatives/mailboxlayer
---
# Spaw vs NeverBounce: email verification API comparison
How Spaw and NeverBounce compare on pricing, billing rules, verification depth and limits, from NeverBounce's public pages checked on 2026-09-03. Corrections: support@spaw.co.
| | Spaw | NeverBounce |
| --- | --- | --- |
| Pricing model | Pay-as-you-go credit packs bought through Stripe Checkout; no subscription, credits never expire | not published |
| Free tier | 10 free credits every month, no card required | not published |
| Price per 1,000 at about 10k | $7.80 (Growth pack: 5,000 credits for $39) | not published |
| Price per 1,000 at about 100k | $4.99 (Volume pack: 100,000 credits for $499) | not published |
| Unknown or catch-all results billed | Yes, by verdict: a deliverable or risky answer costs 1 credit even when the mailbox is unconfirmed; the automatic re-checks are free | not published |
| Undeliverable results billed | No, undeliverable verdicts are always free | not published |
| SMTP mailbox check | Yes | not published |
| Catch-all handling | A catch-all answer is remembered per domain for 7 days, and every unconfirmed mailbox carries a published 0 to 100 mailbox_confidence estimate | catchall is one of the result codes alongside valid, invalid, disposable and unknown; jobs with a high rate of unknowns can be sent for a manual review |
| Bulk limit | 100,000 addresses per bulk job, with an HMAC-signed completion webhook | Lists of millions should be split into jobs of 1,000,000 addresses; 10 concurrent jobs and at most 50 runs a day per account, and no more than 10 jobs per 100,000 items an hour |
| Batch API | 50 addresses per synchronous request, probes sent concurrently | Bulk jobs through the jobs API (create, status, download) |
| Rate limit | 5 requests per second per key | Job creation limits as above; per-request limits for the single-check endpoint are not published on the pages checked |
| Official SDKs | Python, Node.js, PHP | PHP, Node.js, Python, Ruby, Go, .NET |
| Data retention | Lookup history kept for 30 days, then pruned; test addresses are never logged | not published |
## When NeverBounce fits better
Teams that clean large lists through jobs and want official wrappers in six languages plus an n8n node.
- Official n8n node
- Manual review process for jobs with many unknown results
- Owned by ZoomInfo
## When Spaw fits better
Developers who want one flat price per answered lookup, a transparent risk score, and an API that documents every field.
- NeverBounce's pricing pages blocked automated reading when we checked, so its rates are not reproduced here; Spaw's packs and free grant are listed at spaw.co/pricing
- Spaw runs the SMTP handshake on every lookup and bills nothing for undeliverable verdicts
- Spaw's bulk jobs take 100,000 addresses with a signed completion webhook; NeverBounce asks for 1,000,000-address chunks, 10 concurrent jobs and 50 runs a day
- Spaw returns 27 named fields and a machine-readable reason per address
## Questions
**Does Spaw charge for catch-all or unknown results?** Spaw bills per answer, not per probe outcome: a lookup that comes back deliverable or risky costs 1 credit even when the mailbox could not be confirmed, for example on a catch-all domain or after greylisting. Undeliverable verdicts, invalid input, repeats within 7 days and the automatic re-checks behind callback_url are free. NeverBounce does not publish how it bills them.
**Can I move a list from NeverBounce to Spaw?** Yes. Upload the CSV to the dashboard or POST it to the bulk endpoint; Spaw keeps your columns, adds the verdict columns, and lets you download only the deliverable, risky or undeliverable rows. Addresses that come back undeliverable join your suppression list so later runs skip them for free.
**Is there a free tier?** Every Spaw account receives 10 free credits at the start of each month with no card on file, and unused credits accumulate. NeverBounce does not publish a free tier on the pages checked.
## Sources (checked 2026-09-03)
- [NeverBounce usage guidelines](https://developers.neverbounce.com/reference/usage-guidelines)
- [NeverBounce verifying a list](https://developers.neverbounce.com/docs/verifying-a-list)
- [NeverBounce GitHub organization](https://github.com/NeverBounce)
NeverBounce is a trademark of its owner. Spaw is not affiliated with it.
Reference: https://spaw.co/compare/neverbounce
---
# NeverBounce alternatives for email verification
Email verification APIs to consider instead of NeverBounce, with the pricing model, free tier and price per 1,000 verifications each vendor publishes. Figures come from public pricing pages checked on 2026-09-03.
| Service | Pricing model | Free tier | Price per 1,000 at about 10k |
| --- | --- | --- | --- |
| [Spaw](https://spaw.co) | Pay-as-you-go credit packs bought through Stripe Checkout; no subscription, credits never expire | 10 free credits every month, no card required | $7.80 (Growth pack: 5,000 credits for $39) |
| [Abstract API](https://www.abstractapi.com) | Monthly subscription tiers with a request slider from 5,000 to 150,000 requests a month; Starter from $17, Standard from $37, Professional from $39 a month | 100 requests a month | not published |
| [Bouncer](https://www.usebouncer.com) | Pay-as-you-go credits that never expire (minimum 1,000 credits for $8), auto-refill, plus a Deliverability Kit subscription at $25 to $250 a month | 100 free credits, no card required | $6.00 (10,000 credits for $60) |
| [Emailable](https://emailable.com) | Pay-as-you-go credits that never expire, or a monthly subscription at a 15% discount; minimum purchase 5,000 credits | 250 free credits at signup | $7.60 (pay-as-you-go at $0.0076 a credit; a 10K monthly subscription costs $32.30) |
| [Hunter](https://hunter.io) | Monthly or yearly subscriptions with unified credits shared across search, verification and enrichment; credit packs on paid plans (valid 3 months) | Free plan with 50 credits a month; one verification costs 0.5 credit, so 100 verifications | $7.45 (Growth: $149 a month for 10,000 credits, 20,000 verifications at 0.5 credit each; $5.20 billed yearly) |
| [Kickbox](https://kickbox.com) | not published | not published | not published |
| [Mailboxlayer](https://mailboxlayer.com) | Monthly or yearly subscription with a request quota per plan and per-request overage | 100 requests a month | $3.00 (Basic: 5,000 requests for $14.99 a month, overage $0.012 a request; Professional Plus: 50,000 for $74.99, $1.50 per 1,000) |
| [Verifalia](https://verifalia.com) | Pay-as-you-go credit packs that never expire, or monthly, quarterly and yearly subscriptions; verification depth is priced as quality levels | 25 free credits a day on the Free plan; unused daily credits expire each day | not published |
| [ZeroBounce](https://www.zerobounce.net) | Pay-as-you-go credits that never expire, or the ZeroBounce ONE subscription; volume pricing shown through a calculator rather than fixed packs | 100 validation credits a month on the free account | not published |
## Spaw
Email verification API that bills per answered lookup: syntax, live MX, SPF and DMARC, disposable and role lists, and an SMTP mailbox handshake that never sends a message.
## Abstract API
Abstract API's email validation endpoint is one of a family of utility APIs sold as monthly request quotas with a free 100-request plan.
Comparison: https://spaw.co/compare/abstract
## Bouncer
Bouncer is an email verification service with pay-as-you-go credits that never expire, high-throughput batch APIs, a real-time form product (Bouncer Shield) and a Deliverability Kit subscription.
Comparison: https://spaw.co/compare/bouncer
## Emailable
Emailable is an email verification service with pay-as-you-go credits, a bulk verifier, a widget and an API, with Node.js, Ruby and Python libraries.
Comparison: https://spaw.co/compare/emailable
## Hunter
Hunter is an outreach platform whose Email Verifier shares unified plan credits with its email finder, enrichment and campaign tools.
Comparison: https://spaw.co/compare/hunter
## Kickbox
Kickbox is an email verification API that returns deliverable, undeliverable, risky or unknown plus a 0 to 1 Sendex quality score, with official libraries for PHP, Node.js, Ruby and Python.
Comparison: https://spaw.co/compare/kickbox
## Mailboxlayer
Mailboxlayer is apilayer's email validation API, sold as monthly request quotas from a free 100-request plan up to 250,000 requests a month.
Comparison: https://spaw.co/compare/mailboxlayer
## Verifalia
Verifalia is an email verification service with selectable quality levels, credit packs that never expire, subscriptions with daily free credits, and official SDKs in six languages.
Comparison: https://spaw.co/compare/verifalia
## ZeroBounce
ZeroBounce is an email validation service with a pay-as-you-go credit model and a subscription (ZeroBounce ONE) that bundles deliverability tools such as activity data, inbox placement tests and blacklist monitoring.
Comparison: https://spaw.co/compare/zerobounce
NeverBounce and the other product names are trademarks of their owners. Spaw is not affiliated with them.
Reference: https://spaw.co/alternatives/neverbounce
---
# Spaw vs Verifalia: email verification API comparison
How Spaw and Verifalia compare on pricing, billing rules, verification depth and limits, from Verifalia's public pages checked on 2026-09-03. Corrections: support@spaw.co.
| | Spaw | Verifalia |
| --- | --- | --- |
| Pricing model | Pay-as-you-go credit packs bought through Stripe Checkout; no subscription, credits never expire | Pay-as-you-go credit packs that never expire, or monthly, quarterly and yearly subscriptions; verification depth is priced as quality levels |
| Free tier | 10 free credits every month, no card required | 25 free credits a day on the Free plan; unused daily credits expire each day |
| Price per 1,000 at about 10k | $7.80 (Growth pack: 5,000 credits for $39) | not published |
| Price per 1,000 at about 100k | $4.99 (Volume pack: 100,000 credits for $499) | not published |
| Unknown or catch-all results billed | Yes, by verdict: a deliverable or risky answer costs 1 credit even when the mailbox is unconfirmed; the automatic re-checks are free | not published |
| Undeliverable results billed | No, undeliverable verdicts are always free | not published |
| SMTP mailbox check | Yes | not published |
| Catch-all handling | A catch-all answer is remembered per domain for 7 days, and every unconfirmed mailbox carries a published 0 to 100 mailbox_confidence estimate | not published |
| Bulk limit | 100,000 addresses per bulk job, with an HMAC-signed completion webhook | Lists up to 100 MB, about 72 million addresses per list |
| Batch API | 50 addresses per synchronous request, probes sent concurrently | not published |
| Rate limit | 5 requests per second per key | Configurable throttling; fixed limits not published on the pages checked |
| Official SDKs | Python, Node.js, PHP | .NET, Java, Node.js, Ruby, PHP, Go |
| Data retention | Lookup history kept for 30 days, then pruned; test addresses are never logged | Configurable from 5 minutes to 30 days, deletable any time; since October 2025 deleted jobs keep an anonymized reference for up to 24 months |
## When Verifalia fits better
Developers who want to tune verification depth per request, official SDKs in six languages, and configurable retention.
- Standard, High and Extreme quality levels cost 1, 2 and 4 credits per verification
- Subscriptions include daily free credits, from 250 a day on Starter to 25,000 a day on Ultimate
- States that data is processed in memory rather than stored persistently
## When Spaw fits better
Developers who want one flat price per answered lookup, a transparent risk score, and an API that documents every field.
- Verifalia prices depth as 1, 2 or 4 credits per verification; Spaw runs its full pipeline including the SMTP handshake for 1 credit
- Spaw bills nothing for undeliverable verdicts and repeats within 7 days
- Verifalia's daily free credits expire each day; Spaw's monthly credits accumulate
- Verifalia lists SDKs in six languages; Spaw ships Python, Node.js and PHP clients and an OpenAPI document
## Questions
**Does Spaw charge for catch-all or unknown results?** Spaw bills per answer, not per probe outcome: a lookup that comes back deliverable or risky costs 1 credit even when the mailbox could not be confirmed, for example on a catch-all domain or after greylisting. Undeliverable verdicts, invalid input, repeats within 7 days and the automatic re-checks behind callback_url are free. Verifalia does not publish how it bills them.
**Can I move a list from Verifalia to Spaw?** Yes. Upload the CSV to the dashboard or POST it to the bulk endpoint; Spaw keeps your columns, adds the verdict columns, and lets you download only the deliverable, risky or undeliverable rows. Addresses that come back undeliverable join your suppression list so later runs skip them for free.
**Is there a free tier?** Every Spaw account receives 10 free credits at the start of each month with no card on file, and unused credits accumulate. Verifalia offers: 25 free credits a day on the Free plan; unused daily credits expire each day.
## Sources (checked 2026-09-03)
- [Verifalia pricing](https://verifalia.com/pricing)
- [Verifalia developers](https://verifalia.com/developers)
Verifalia is a trademark of its owner. Spaw is not affiliated with it.
Reference: https://spaw.co/compare/verifalia
---
# Verifalia alternatives for email verification
Email verification APIs to consider instead of Verifalia, with the pricing model, free tier and price per 1,000 verifications each vendor publishes. Figures come from public pricing pages checked on 2026-09-03.
| Service | Pricing model | Free tier | Price per 1,000 at about 10k |
| --- | --- | --- | --- |
| [Spaw](https://spaw.co) | Pay-as-you-go credit packs bought through Stripe Checkout; no subscription, credits never expire | 10 free credits every month, no card required | $7.80 (Growth pack: 5,000 credits for $39) |
| [Abstract API](https://www.abstractapi.com) | Monthly subscription tiers with a request slider from 5,000 to 150,000 requests a month; Starter from $17, Standard from $37, Professional from $39 a month | 100 requests a month | not published |
| [Bouncer](https://www.usebouncer.com) | Pay-as-you-go credits that never expire (minimum 1,000 credits for $8), auto-refill, plus a Deliverability Kit subscription at $25 to $250 a month | 100 free credits, no card required | $6.00 (10,000 credits for $60) |
| [Emailable](https://emailable.com) | Pay-as-you-go credits that never expire, or a monthly subscription at a 15% discount; minimum purchase 5,000 credits | 250 free credits at signup | $7.60 (pay-as-you-go at $0.0076 a credit; a 10K monthly subscription costs $32.30) |
| [Hunter](https://hunter.io) | Monthly or yearly subscriptions with unified credits shared across search, verification and enrichment; credit packs on paid plans (valid 3 months) | Free plan with 50 credits a month; one verification costs 0.5 credit, so 100 verifications | $7.45 (Growth: $149 a month for 10,000 credits, 20,000 verifications at 0.5 credit each; $5.20 billed yearly) |
| [Kickbox](https://kickbox.com) | not published | not published | not published |
| [Mailboxlayer](https://mailboxlayer.com) | Monthly or yearly subscription with a request quota per plan and per-request overage | 100 requests a month | $3.00 (Basic: 5,000 requests for $14.99 a month, overage $0.012 a request; Professional Plus: 50,000 for $74.99, $1.50 per 1,000) |
| [NeverBounce](https://www.neverbounce.com) | not published | not published | not published |
| [ZeroBounce](https://www.zerobounce.net) | Pay-as-you-go credits that never expire, or the ZeroBounce ONE subscription; volume pricing shown through a calculator rather than fixed packs | 100 validation credits a month on the free account | not published |
## Spaw
Email verification API that bills per answered lookup: syntax, live MX, SPF and DMARC, disposable and role lists, and an SMTP mailbox handshake that never sends a message.
## Abstract API
Abstract API's email validation endpoint is one of a family of utility APIs sold as monthly request quotas with a free 100-request plan.
Comparison: https://spaw.co/compare/abstract
## Bouncer
Bouncer is an email verification service with pay-as-you-go credits that never expire, high-throughput batch APIs, a real-time form product (Bouncer Shield) and a Deliverability Kit subscription.
Comparison: https://spaw.co/compare/bouncer
## Emailable
Emailable is an email verification service with pay-as-you-go credits, a bulk verifier, a widget and an API, with Node.js, Ruby and Python libraries.
Comparison: https://spaw.co/compare/emailable
## Hunter
Hunter is an outreach platform whose Email Verifier shares unified plan credits with its email finder, enrichment and campaign tools.
Comparison: https://spaw.co/compare/hunter
## Kickbox
Kickbox is an email verification API that returns deliverable, undeliverable, risky or unknown plus a 0 to 1 Sendex quality score, with official libraries for PHP, Node.js, Ruby and Python.
Comparison: https://spaw.co/compare/kickbox
## Mailboxlayer
Mailboxlayer is apilayer's email validation API, sold as monthly request quotas from a free 100-request plan up to 250,000 requests a month.
Comparison: https://spaw.co/compare/mailboxlayer
## NeverBounce
NeverBounce is a ZoomInfo-owned email verification service with a bulk list-cleaning workflow, a real-time API and official client libraries in six languages.
Comparison: https://spaw.co/compare/neverbounce
## ZeroBounce
ZeroBounce is an email validation service with a pay-as-you-go credit model and a subscription (ZeroBounce ONE) that bundles deliverability tools such as activity data, inbox placement tests and blacklist monitoring.
Comparison: https://spaw.co/compare/zerobounce
Verifalia and the other product names are trademarks of their owners. Spaw is not affiliated with them.
Reference: https://spaw.co/alternatives/verifalia
---
# Spaw vs ZeroBounce: email verification API comparison
How Spaw and ZeroBounce compare on pricing, billing rules, verification depth and limits, from ZeroBounce's public pages checked on 2026-09-03. Corrections: support@spaw.co.
| | Spaw | ZeroBounce |
| --- | --- | --- |
| Pricing model | Pay-as-you-go credit packs bought through Stripe Checkout; no subscription, credits never expire | Pay-as-you-go credits that never expire, or the ZeroBounce ONE subscription; volume pricing shown through a calculator rather than fixed packs |
| Free tier | 10 free credits every month, no card required | 100 validation credits a month on the free account |
| Price per 1,000 at about 10k | $7.80 (Growth pack: 5,000 credits for $39) | not published |
| Price per 1,000 at about 100k | $4.99 (Volume pack: 100,000 credits for $499) | not published |
| Unknown or catch-all results billed | Yes, by verdict: a deliverable or risky answer costs 1 credit even when the mailbox is unconfirmed; the automatic re-checks are free | No |
| Undeliverable results billed | No, undeliverable verdicts are always free | not published |
| SMTP mailbox check | Yes | not published |
| Catch-all handling | A catch-all answer is remembered per domain for 7 days, and every unconfirmed mailbox carries a published 0 to 100 mailbox_confidence estimate | catch-all is one of seven statuses (valid, invalid, catch-all, unknown, spamtrap, abuse, do_not_mail) with 21 sub-statuses; only unknown results are stated to be free |
| Bulk limit | 100,000 addresses per bulk job, with an HMAC-signed completion webhook | Bulk file endpoints for larger lists; size limits not published on the pages checked |
| Batch API | 50 addresses per synchronous request, probes sent concurrently | Up to 100 emails per batch request, at most 30 requests a minute (40 on ZeroBounce ONE) |
| Rate limit | 5 requests per second per key | Single validation: 80,000 requests in 10 seconds per the docs (100,000 on ZeroBounce ONE); batch: 30 requests a minute; exceeding a limit triggers a temporary block |
| Official SDKs | Python, Node.js, PHP | PHP, Python, Java, JavaScript, Node.js, C#, Go, Ruby, Rust, Kotlin, Swift |
| Data retention | Lookup history kept for 30 days, then pruned; test addresses are never logged | not published |
## When ZeroBounce fits better
Marketing teams that want a deliverability suite around verification and prefer a subscription with bundled tools.
- Credits never expire
- Unknown results never consume a credit
- ZeroBounce ONE bundles activity data, AI scoring, an email finder, inbox placement tests, blacklist and DMARC monitoring
- 21 sub-statuses explain a result, from greylisted to possible_typo and mailbox_quota_exceeded
## When Spaw fits better
Developers who want one flat price per answered lookup, a transparent risk score, and an API that documents every field.
- Spaw lists fixed pack prices and a monthly free grant on one page; ZeroBounce prices volume through a calculator and its ONE subscription
- Both leave unknown results unbilled; Spaw also leaves every undeliverable verdict free
- Spaw's risk score is a sum of published weights on every lookup; ZeroBounce offers AI scoring as part of its ONE subscription
- Spaw batches take 50 addresses at 5 requests a second; ZeroBounce batches take 100 addresses at 30 requests a minute
## Questions
**Does Spaw charge for catch-all or unknown results?** Spaw bills per answer, not per probe outcome: a lookup that comes back deliverable or risky costs 1 credit even when the mailbox could not be confirmed, for example on a catch-all domain or after greylisting. Undeliverable verdicts, invalid input, repeats within 7 days and the automatic re-checks behind callback_url are free. ZeroBounce states that it does not bill unknown results.
**Can I move a list from ZeroBounce to Spaw?** Yes. Upload the CSV to the dashboard or POST it to the bulk endpoint; Spaw keeps your columns, adds the verdict columns, and lets you download only the deliverable, risky or undeliverable rows. Addresses that come back undeliverable join your suppression list so later runs skip them for free.
**Is there a free tier?** Every Spaw account receives 10 free credits at the start of each month with no card on file, and unused credits accumulate. ZeroBounce offers: 100 validation credits a month on the free account.
## Sources (checked 2026-09-03)
- [ZeroBounce pricing](https://www.zerobounce.net/email-validation-pricing/)
- [ZeroBounce API rate limits](https://www.zerobounce.net/docs/api-dashboard/api-rate-limits)
- [ZeroBounce batch validation docs](https://www.zerobounce.net/docs/email-validation-api-quickstart/v2-batch-validate-emails)
ZeroBounce is a trademark of its owner. Spaw is not affiliated with it.
Reference: https://spaw.co/compare/zerobounce
---
# ZeroBounce alternatives for email verification
Email verification APIs to consider instead of ZeroBounce, with the pricing model, free tier and price per 1,000 verifications each vendor publishes. Figures come from public pricing pages checked on 2026-09-03.
| Service | Pricing model | Free tier | Price per 1,000 at about 10k |
| --- | --- | --- | --- |
| [Spaw](https://spaw.co) | Pay-as-you-go credit packs bought through Stripe Checkout; no subscription, credits never expire | 10 free credits every month, no card required | $7.80 (Growth pack: 5,000 credits for $39) |
| [Abstract API](https://www.abstractapi.com) | Monthly subscription tiers with a request slider from 5,000 to 150,000 requests a month; Starter from $17, Standard from $37, Professional from $39 a month | 100 requests a month | not published |
| [Bouncer](https://www.usebouncer.com) | Pay-as-you-go credits that never expire (minimum 1,000 credits for $8), auto-refill, plus a Deliverability Kit subscription at $25 to $250 a month | 100 free credits, no card required | $6.00 (10,000 credits for $60) |
| [Emailable](https://emailable.com) | Pay-as-you-go credits that never expire, or a monthly subscription at a 15% discount; minimum purchase 5,000 credits | 250 free credits at signup | $7.60 (pay-as-you-go at $0.0076 a credit; a 10K monthly subscription costs $32.30) |
| [Hunter](https://hunter.io) | Monthly or yearly subscriptions with unified credits shared across search, verification and enrichment; credit packs on paid plans (valid 3 months) | Free plan with 50 credits a month; one verification costs 0.5 credit, so 100 verifications | $7.45 (Growth: $149 a month for 10,000 credits, 20,000 verifications at 0.5 credit each; $5.20 billed yearly) |
| [Kickbox](https://kickbox.com) | not published | not published | not published |
| [Mailboxlayer](https://mailboxlayer.com) | Monthly or yearly subscription with a request quota per plan and per-request overage | 100 requests a month | $3.00 (Basic: 5,000 requests for $14.99 a month, overage $0.012 a request; Professional Plus: 50,000 for $74.99, $1.50 per 1,000) |
| [NeverBounce](https://www.neverbounce.com) | not published | not published | not published |
| [Verifalia](https://verifalia.com) | Pay-as-you-go credit packs that never expire, or monthly, quarterly and yearly subscriptions; verification depth is priced as quality levels | 25 free credits a day on the Free plan; unused daily credits expire each day | not published |
## Spaw
Email verification API that bills per answered lookup: syntax, live MX, SPF and DMARC, disposable and role lists, and an SMTP mailbox handshake that never sends a message.
## Abstract API
Abstract API's email validation endpoint is one of a family of utility APIs sold as monthly request quotas with a free 100-request plan.
Comparison: https://spaw.co/compare/abstract
## Bouncer
Bouncer is an email verification service with pay-as-you-go credits that never expire, high-throughput batch APIs, a real-time form product (Bouncer Shield) and a Deliverability Kit subscription.
Comparison: https://spaw.co/compare/bouncer
## Emailable
Emailable is an email verification service with pay-as-you-go credits, a bulk verifier, a widget and an API, with Node.js, Ruby and Python libraries.
Comparison: https://spaw.co/compare/emailable
## Hunter
Hunter is an outreach platform whose Email Verifier shares unified plan credits with its email finder, enrichment and campaign tools.
Comparison: https://spaw.co/compare/hunter
## Kickbox
Kickbox is an email verification API that returns deliverable, undeliverable, risky or unknown plus a 0 to 1 Sendex quality score, with official libraries for PHP, Node.js, Ruby and Python.
Comparison: https://spaw.co/compare/kickbox
## Mailboxlayer
Mailboxlayer is apilayer's email validation API, sold as monthly request quotas from a free 100-request plan up to 250,000 requests a month.
Comparison: https://spaw.co/compare/mailboxlayer
## NeverBounce
NeverBounce is a ZoomInfo-owned email verification service with a bulk list-cleaning workflow, a real-time API and official client libraries in six languages.
Comparison: https://spaw.co/compare/neverbounce
## Verifalia
Verifalia is an email verification service with selectable quality levels, credit packs that never expire, subscriptions with daily free credits, and official SDKs in six languages.
Comparison: https://spaw.co/compare/verifalia
ZeroBounce and the other product names are trademarks of their owners. Spaw is not affiliated with them.
Reference: https://spaw.co/alternatives/zerobounce
---
# Changelog
What changed in Spaw, newest first. Reference: https://spaw.co/changelog
## 2026-09-03: Documentation and public pages
- The OpenAPI document is published at `/openapi.json` and `/openapi.yaml`.
- Every docs page has a Markdown twin at the same URL with `.md` appended, and `/llms-full.txt` gathers all of them.
- One reference page per endpoint, with request and response tables and code samples.
- Reference pages for every verdict reason, SMTP reason, and API error code.
- The free domain tools have permalinks: one page per domain for MX, SPF and DMARC, and disposable checks.
- New public pages: pricing, about, security, and this changelog.
## 2026-09-02: Verdict quality
- Lenient address extraction: display names, `mailto:` links, quotes, trailing punctuation, and invisible characters are stripped before validation.
- Provider username rules reject local parts that Gmail, Outlook, Yahoo, iCloud, AOL, and Proton would never issue.
- A curated typo-squat map turns `gmail.con` and similar domains into a `likely_typo` verdict with a suggestion.
- New MX sanity statuses: `implicit_mx` for domains that rely on the A-record fallback and `mx_unresolvable` for MX hosts that point nowhere usable.
- Disposable detection now also matches on the MX hosts, so a fresh burner domain hosted by a known operator is caught; local allow and deny overrides beat waiting for the upstream list.
- Role and free-provider lists sync weekly from their open sources.
- `smtp_reason` carries the provider detail: a full mailbox is risky, a disabled mailbox is undeliverable.
- The risk score adds a missing SPF record and the domain's registration age, read from RDAP.
- `mailbox_confidence` estimates the odds of an unverified mailbox.
- The response grew from 21 to 26 fields.
## 2026-09-02: Reliability, feedback loop and billing
- A shared mailbox answer cache across customers, so the same address is probed once.
- Bulk runs process in chunks, and each chunk writes its own part file before results are stitched.
- Provider balance alerts warn before the mailbox partner runs dry.
- Six `@spaw.test` test addresses answer fixed verdicts at no cost and are never logged.
- Every response carries `X-Request-Id`, repeated in `meta.request_id` and `error.request_id`.
- Publishable keys accept a daily credit cap (`429 KEY_SPEND_CAP_REACHED` once it is spent).
- A circuit breaker keeps a struggling mailbox provider from slowing every lookup.
- Batch requests send their mailbox probes concurrently.
- A bounce feedback API (`POST /v1/email/feedback`) and webhook targets for Postmark, Amazon SES, Mailgun, and SendGrid; bounces join the suppression list, deliveries clear it, and the dashboard shows measured accuracy.
- `smtp_checked_at`, the 27th response field, records when the mailbox was last probed.
- Catch-all domains are remembered for a week, and an account's own delivered addresses count as verified for 90 days.
- Credit packs are sold through Stripe Checkout from the Billing page.
- `callback_url` and `callback_secret` on a lookup re-check an unverified answer after 5 and 20 minutes and POST the settled verdict, signed.
- Bulk uploads keep the file's own columns, add the verdict columns, and offer `?variant=deliverable|risky|undeliverable` downloads.
- Publishable keys can require a Cloudflare Turnstile token per lookup.
## 2026-08-31: Batch, bulk, domains, monitors, MCP and free tools
- `POST /v1/email/batch` verifies up to 50 addresses in one request.
- `POST /v1/email/bulk` queues up to 100,000 addresses, with an HMAC-signed completion webhook, cancellation, and CSV results.
- `GET /v1/email/domain/{domain}` answers MX, provider, SPF and DMARC, registration age, and list membership for a whole domain.
- A per-account suppression list: undeliverable verdicts are remembered, and existing lists can be imported.
- Scheduled list monitors re-verify a list on a cadence and alert when its deliverability decays.
- Publishable keys (`pk_…`) power a browser endpoint locked to allowed origins.
- An MCP server lets AI agents verify addresses with an existing API key.
- Four free tools without signup: email checker, MX lookup, SPF and DMARC checker, and disposable domain checker.
- The documentation became a multi-page area with a getting-started guide, an email verification guide, and the API reference.
- The product is now Spaw at spaw.co.
## 2026-08-30: Email verification API launch
- `POST /v1/email` verifies one address: RFC syntax, live MX resolution, open disposable and role lists, typo suggestions, SPF and DMARC records, and a 0 to 100 risk score built from published weights.
- An SMTP mailbox handshake runs on every lookup whose domain accepts mail. No message is ever sent.
- MX provider classification names the infrastructure behind a domain (Google, Microsoft, Proofpoint, and others).
- Secret API keys (`sk_live_…`) are created from the dashboard, shown once, and stored hashed.
- Credits bill answers only: a deliverable or risky verdict costs 1 credit, undeliverable and invalid answers are free, and a repeat within 7 days is free.
- The dashboard playground runs the same lookups as the API.
- `spaw-form.js`, a form helper for the browser, checks addresses on blur, offers a did-you-mean correction, and never blocks a submit.