# Email validation

> Check whether addresses can receive mail before you send, and suppress the ones that cannot.

URL: https://docs.workwae.com/guides/email-validation

`POST /v1/email-validations` checks up to 10 addresses without sending them any mail. It is available where the deployment has an email checker configured; otherwise it answers 400 `email_validation_disabled`.

## How an address is checked

For each address the checker validates the syntax, looks up the domain's mail servers and opens an SMTP conversation with the recipient's server to ask whether the mailbox exists, stopping before any message is sent. This takes seconds per address (five are checked at once), so a request with several addresses is slow: do not call it from a page a person is waiting on.

Only the address is sent to the checker. The call needs the `write` scope and is limited per tenant per minute; beyond the limit it answers 429 `rate_limited` with `Retry-After`.

```bash tab="cURL"
curl -X POST "https://api.workwae.com/v1/email-validations" \
  -H "Authorization: Bearer $WORKWAE_API_KEY" \
  -H "content-type: application/json" \
  -d '{ "addresses": ["ada@example.org", "sales@example.net"] }'
```

```ts tab="TypeScript"
const { results } = await workwae.emailValidations.create({
  addresses: ["ada@example.org", "sales@example.net"],
});
```

## Reading the result

One result per distinct address, trimmed and lowercased, in the order first given.

| `reachability` | Meaning | What to do |
| --- | --- | --- |
| `safe` | Very likely to be delivered. | Send. |
| `invalid` | Will almost certainly bounce: bad syntax, a domain that accepts no mail, or a mailbox that does not exist or is disabled. | Do not send. |
| `risky` | The mailbox exists but may bounce or draw no engagement: a disposable address, a shared mailbox such as `support@`, a catch-all domain or a full inbox. | Your call. |
| `unknown` | The recipient's server would not say. | Treat as unverified; never proof of a bad address. |

The result also carries the facts behind it: `validSyntax`, `domainAcceptsMail`, `deliverable`, `disabled`, `fullInbox`, `catchAll`, `disposable` and `roleAccount`. A fact is `null` when that step was not made or did not complete. `checkedAt` is when the checker answered, and `cached` is `true` when the result is an [earlier check's](#cache).

`disposable` is `true` when the address's domain, or the domain of one of its mail servers, is a known disposable-mail provider. Such an address is never `safe`; the checker's own verdict of `invalid` or `unknown` is kept.

> **A catch-all domain proves nothing**
>
> A domain that accepts mail for any address answers yes for every mailbox. `catchAll: true` means `deliverable` says nothing about this address, and the address is `risky`.

## Recent results are reused

An address your organization checked within the deployment's cache period (7 days by default) gets that check's result back, with `cached: true` and the original `checkedAt`, instead of being checked again. An `unknown` result is never reused. Set `"refresh": true` to check every address again, for example after a mailbox was created; later requests then reuse the new result.

A reused result still counts toward the per-minute limit, and `suppressInvalid` applies to it as to a fresh one.

## Daily limit

Your plan caps the addresses the checker examines per UTC day (`plan.limits.emailValidationsPerDay` in [usage](/guides/rate-limits#usage)). Reused results are free; every address checked again counts, including one whose check failed. A request whose addresses to check do not all fit answers 403 `plan_limit_validations`, the message saying how many are left, and nothing is checked. `current.emailValidationsToday` is the count the limit checks, and each day's `emailValidations` shows the history.

## Credits

On a billed plan that includes validation credits, every address checked again also spends one prepaid credit; a reused result is free. Each billing period grants the plan's included credits, which expire when it ends and are spent first. Packs you buy never expire and are spent after them. A request your balance cannot cover answers 402 `validation_credits_exhausted` with `balance` and `needed` in its `data`, and nothing is checked or spent. A request that fails gives its credits back. `current.validationCredits` on [usage](/guides/rate-limits#usage) is the balance: `included`, `includedExpiresAt` and `purchased`, or `null` where credits are not enforced. Buy packs or set auto-recharge under Billing in the console. A plan can sell several pack sizes, and the bigger the pack, the lower the price per 1,000 credits; auto-recharge buys the cheapest combination of packs that covers the credits you set.

## Suppress invalid addresses

With `"suppressInvalid": true`, each address found `invalid` goes on the [global suppression list](/guides/suppressions) with source `invalid` and the reason (`email validation: invalid syntax`, `domain accepts no mail`, `mailbox disabled` or `mailbox does not exist`). Sends that name a list skip it from then on.

- `risky` and `unknown` addresses are never suppressed.
- A result's `suppressed` is `true` only when this request put the address on the list.
- Release an address with `DELETE /v1/suppressions/global/entries/{address}` if the verdict was wrong.
- The suppression is recorded in the [audit log](/guides/audit-log); no `suppression.created` event is sent.

## History

`GET /v1/email-validations` lists every result a request returned, cached ones included, newest first. `address` keeps results whose address contains it, and `reachability` keeps one verdict. It needs only the `read` scope and works where the deployment has no checker. Results are kept for the deployment's retention period (90 days by default).

```bash tab="cURL"
curl "https://api.workwae.com/v1/email-validations?reachability=invalid" \
  -H "Authorization: Bearer $WORKWAE_API_KEY"
```

```ts tab="TypeScript"
const { items, nextCursor } = await workwae.emailValidations.list({ reachability: "invalid" });
```

## Failures

A check that fails fails the whole request, and nothing from it is suppressed. A failed check is not retried by Workwae.

| Reason | Status | Meaning |
| --- | --- | --- |
| `email_validation_disabled` | 400 | The deployment has no email checker. |
| `email_validation_unavailable` | 502 | The checker did not answer in time, failed or answered something unusable. Retry later, with fewer addresses. |
| `email_validation_suspended` | 502 | The checker refuses Workwae's requests; the operator has to fix it. |
| `plan_limit_validations` | 403 | The addresses to check do not fit what the [plan allows today](#limit). |
| `validation_credits_exhausted` | 402 | The [credits](#credits) do not cover the addresses to check. |

The operation is in the [Email validation reference](/reference/email-validation).
