> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Validate email addresses before you send

> Scrub a recipient list before a campaign and pre-flight a single address at a capture edge — syntax, MX, disposable, and role-account checks plus an aggregate risk score.

# Validate email addresses before you send

The deliverability-health guide covers what the traffic you have already
sent says about a contact. This guide is the other half: what to check about
an address **before** the first send, when you have no history to rely on.
Two endpoints cover it — a single-address check
(`POST /api/v1/email/validate`) and a batch scrub
(`POST /api/v1/email/validate/bulk`) — and this page shows where each one
belongs in a launch workflow.

## 1. Why scrub before you send

Every hard bounce you mail chips away at sender reputation, and every dead
address costs a quota slot and a delivery attempt. Scrubbing a list before
the campaign means you only pay for addresses that can actually receive
mail, and a complaint-prone row (a disposable inbox, a role mailbox) gets a
human decision instead of an automated one.

That matters most while your domain is still warming up: the ramp schedule
in the [email lifecycle guide](/guides/email-lifecycle-guide#2-a-deliverability-plan-before-volume)
is sized for engaged, working recipients. Feeding a run of hard bounces into
an early warmup day degrades the deliverability signal the ramp reads to
adjust today's cap, so you slow down exactly at the moment you were trying
to speed past. Scrub first; ramp clean traffic.

## 2. Pre-flight sweep — single addresses at the capture edge

Reject obvious typos where the address enters, not after it reaches your
CRM. Call `POST /api/v1/email/validate` from your signup form or edge
worker and decide the row before you import it.

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/email/validate" \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{"email": "alice@gamil.com"}'
```

```json theme={null}
{
  "data": {
    "email": "alice@gamil.com",
    "valid": true,
    "disposable": false,
    "role_based": false,
    "mx_found": true,
    "risk_score": 0.3,
    "risk_band": "medium",
    "suggested_correction": "alice@gmail.com",
    "reasons": [
      "Looks like a typo — did you mean \"alice@gmail.com\"?"
    ]
  },
  "meta": {
    "request_id": "req_xyz789",
    "timestamp": "2026-09-04T00:00:00Z"
  }
}
```

The verdict blocks on two signals: a `valid: false` address (a definitive
no-MX domain or broken syntax) is rejected outright, and a
`suggested_correction` either re-prompts the user with the fix or corrects
the row before it lands in the contact list. A `mx_found: null` result (a
DNS resolver timeout) is not a rejection — validation treats it as unknown
and keeps the address eligible for a retry a few seconds later.

The single-address endpoint also needs the authentication you would attach
to a form. If the capture point is a public page, run the check through your
own proxy with the API key held server-side — the same pattern your web
hooks already use.

## 3. Bulk scrub before a campaign

For an existing segment — a CSV you are importing or an audience you are
about to launch — send the whole list in one call to
`POST /api/v1/email/validate/bulk` (up to 1,000 addresses per call,
de-duplicated case-insensitively). Slot this into the send gate between the
audience selection and the suppression check described in the
[pre-flight checklist](/guides/send-gates-preflight-checklist): the scrub is
where a row fails before it costs anything.

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/email/validate/bulk" \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "emails": [
      "alice@example.com",
      "bob@gamil.com",
      "info@company.example",
      "bad@nonexistent.example"
    ]
  }'
```

```json theme={null}
{
  "data": {
    "summary": {
      "total": 4,
      "valid": 3,
      "invalid": 1,
      "risky": 2
    },
    "results": [
      {
        "email": "alice@example.com",
        "valid": true,
        "reason": "deliverable",
        "mx_verified": true,
        "risk": "low",
        "risk_score": 0,
        "disposable": false,
        "role_based": false,
        "suggested_correction": null
      },
      {
        "email": "bob@gamil.com",
        "valid": true,
        "reason": "possible_typo",
        "mx_verified": true,
        "risk": "medium",
        "risk_score": 0.3,
        "disposable": false,
        "role_based": false,
        "suggested_correction": "bob@gmail.com"
      },
      {
        "email": "info@company.example",
        "valid": true,
        "reason": "role_account",
        "mx_verified": true,
        "risk": "medium",
        "risk_score": 0.25,
        "disposable": false,
        "role_based": true,
        "suggested_correction": null
      },
      {
        "email": "bad@nonexistent.example",
        "valid": false,
        "reason": "no_mx_record",
        "mx_verified": false,
        "risk": "high",
        "risk_score": 0.6,
        "disposable": false,
        "role_based": false,
        "suggested_correction": null
      }
    ]
  },
  "meta": {
    "request_id": "req_xyz789",
    "timestamp": "2026-09-04T00:00:00Z"
  }
}
```

Read the summary first: it is the gate, not the per-row detail. A `total`
far above your import count means the list de-duplicated correctly; a
non-zero `invalid` means at least one row failed definitively; `risky`
counts deliverable-but-flagged rows (possible typo, disposable, role
account) that want a human decision. Only after the summary passes do you
walk the per-row results to fix or drop the offending rows.

Chunk longer lists into sequential calls of up to 1,000 addresses, and do
not parallelize: the batch endpoint is rate-limited per tenant (see the
error section below), so a fan of parallel requests just burns the same
quota faster. Input order is preserved across the results rows, so map them
back to your CSV one row at a time.

## 4. Validation and suppression order

Validation and the suppression list answer different questions, and the
order matters:

1. **Validate first.** The scrub reads only the address itself — it decides
   "can this destination receive mail at all?". Run it on every row you are
   about to accept.
2. **Then check suppression.** A clean verdict does not mean the address is
   sendable — the deliverable address may still be on your blocklist (a
   hard bounce, complaint, unsubscribe, or manual entry). Check the row
   against the Email → Suppressions list via
   [GET /api/v1/email/suppressions](/api-reference/endpoints/email)
   (paged `entries`, with `search` and `reason` filters), and remove what
   returns a hit before the send gate decides for itself.

The same order runs on the send path too: validation decides the destination
is reachable, then the org's blocklist is applied recipient-by-recipient.
If you skip step 1, bounce-suppression entries only accumulate — the send
gate cannot turn a dead destination into a clean list.

## 5. What the verdict actually reports

Only what the endpoint can honestly derive gets returned — these are the
verdict buckets the API really answers, and no SMTP mailbox probing means
no claim of "this mailbox exists".

* `invalid_syntax` — the address failed dot-atom validation (missing `@`,
  a malformed label, a non-2-letter TLD). Reject; a corrected suggestion
  may be lower-cost than a bounce.
* `no_mx_record` — the domain published neither a usable MX nor an A/AAAA
  fallback (RFC 5321 §5.1), or published an RFC 7505 null MX declaring it
  will not accept mail. A definitive verdict, not a guess. Drop these rows.
* `disposable_domain` — the domain is a known throwaway provider.
  Deliverable, but complaints-prone on marketing traffic. Review or
  re-permission.
* `role_account` — the local part is a group mailbox (info@, support@,
  sales@, …). Deliverable, but engagement is low and complaint risk is
  high. Review before marketing sends.
* `possible_typo` — a near-miss domain correction was found.
  `suggested_correction` carries the fix; apply or confirm it.
* `mx_unknown` — the MX lookup timed out. Validation does not treat a
  timeout as a verdict; the row stays eligible and should be retried.
* `deliverable` — none of the above. Sendable subject to the suppression
  check in section 4.

Two reading rules: `valid` is syntax-driven and only flips false on a
definitive no-MX; `mx_verified` is stricter and only flips true on a
definitive MX. A row can be `valid: true` with `mx_verified: false` when
the lookup was unknown — treat that as an honest unknown, not a failure.

## 6. Common errors

* **Empty batch → 422.** `POST /email/validate/bulk` rejects an empty
  `emails` array with `422 VALIDATION_ERROR` and an issues list pointing
  at the missing rows. Send at least one address per call.
* **Too many addresses → 422.** The array cap is 1,000 addresses per
  bulk call; longer lists must be chunked into sequential requests.
* **Rate limit → 429.** Bulk scrubs share a tight per-tenant bucket (5
  calls per minute on `POST /email/validate/bulk`, sized for list imports,
  not refresh loops). Back off when the response carries `retry_after`
  (or a `Retry-After` header, on suppression-list endpoints) and retry.
* **Suppression-list reads → 429.** The `/suppressions` page/export routes
  run on the per-route read budget; a 429 there means pause rather than
  hammering the same filter.

Details of each error code and its remediation stay in the
[error codes reference](/api-reference/error-codes).

## Next steps

* [Send-gating pre-flight checklist](/guides/send-gates-preflight-checklist)
  — where the scrub slots in between audience selection and the suppression
  check.
* [Email channel lifecycle](/guides/email-lifecycle-guide) — warmup,
  suppression, and the error catalog around this workflow.
* [Contact deliverability health](/guides/contact-deliverability-health) —
  the deliverability overlay validation never answers (consent + history).
* [Suppression management](/api-reference/endpoints/email) — page, add,
  remove, and export entries after the scrub.
