> ## 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.

# Email API

> Public List-Unsubscribe one-click handler plus recipient-level email validation (single + bulk list hygiene).

# Email API

Endpoints under the `/api/v1/email` base path. This page covers the public
one-click **unsubscribe** handler and the authenticated **recipient validation**
(list-hygiene) endpoints. Sending email goes through the unified
[Messaging API](/api-reference/endpoints/messaging).

**Base path:** `/api/v1/email`

## Unsubscribe

The public one-click unsubscribe surface that the `List-Unsubscribe` and
`List-Unsubscribe-Post` headers in outbound email reference. RFC 8058 compliant.

**Authentication:** HMAC-token (embedded in URL). **Not** Clerk-authenticated —
these URLs are designed to be opened by the recipient's mail client without any
session.

| Method | Path                                | Purpose                                  |
| ------ | ----------------------------------- | ---------------------------------------- |
| `GET`  | `/api/v1/email/unsubscribe/{token}` | Render the unsubscribe page              |
| `POST` | `/api/v1/email/unsubscribe/{token}` | Process one-click unsubscribe (RFC 8058) |

You don't typically call these endpoints from your own application — Orbit
injects the URLs into outbound email automatically.

## Recipient validation

Real-time recipient list-hygiene checks (SendGrid Email Validation API parity).
Score an address before you send to it: syntax, disposable-domain and
role-account detection, MX presence, a likely-typo correction, and an aggregate
`0`–`1` risk score with a coarse `low`/`medium`/`high` band. Pure DNS +
heuristics — no SMTP mailbox probing and no third-party feed. Addresses are
**not** persisted (transient validation only).

**Authentication:** standard workspace auth (`X-API-Key` or dashboard session).
Both endpoints require an authenticated workspace, but the auth level differs:
single-address `POST /email/validate` additionally requires an **`owner` or
`admin`** role, while `POST /email/validate/bulk` is open to any authenticated
workspace member. A non-admin caller hitting `POST /email/validate` gets a `403`.

| Method | Path                          | Auth                   | Purpose                                             |
| ------ | ----------------------------- | ---------------------- | --------------------------------------------------- |
| `POST` | `/api/v1/email/validate`      | `owner` / `admin` role | Validate a single recipient address                 |
| `POST` | `/api/v1/email/validate/bulk` | Any workspace member   | Validate a list of addresses in one call (max 1000) |

### POST /api/v1/email/validate

Requires an **`owner` or `admin`** role. A workspace member without one of these
roles receives a `403`.

Request body:

```json theme={null}
{ "email": "user@example.com" }
```

Response `data`:

| Field                  | Type            | Description                                                                              |
| ---------------------- | --------------- | ---------------------------------------------------------------------------------------- |
| `email`                | string          | The input, lower-cased + trimmed                                                         |
| `valid`                | boolean         | Syntactically sound **and** able to receive mail (a definitive no-MX flips this `false`) |
| `disposable`           | boolean         | Known disposable / throwaway provider                                                    |
| `role_based`           | boolean         | Role/group mailbox (`info@`, `support@`, …) rather than a person                         |
| `mx_found`             | boolean \| null | Domain publishes a usable MX. `null` = lookup timed out (unknown, not a verdict)         |
| `risk_score`           | number          | Aggregate `0`–`1` deliverability-risk score (higher = riskier)                           |
| `risk_band`            | string          | `low` \| `medium` \| `high`, derived from `risk_score`                                   |
| `suggested_correction` | string \| null  | A corrected address for a likely typo (e.g. `user@gmial.con` → `user@gmail.com`)         |
| `reasons`              | string\[]       | Short, human-readable notes explaining the verdict                                       |

### POST /api/v1/email/validate/bulk

Available to **any authenticated workspace member** — no `owner`/`admin` role
required.

Validate up to 1000 addresses in one call. Input is de-duplicated
case-insensitively before scoring. Request body:

```json theme={null}
{ "emails": ["alice@example.com", "support@example.com"] }
```

Response `data`:

```json theme={null}
{
  "summary": { "total": 2, "valid": 2, "invalid": 0, "risky": 1 },
  "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
    }
  ]
}
```

Each row carries a single machine-readable `reason` code — one of
`deliverable`, `invalid_syntax`, `no_mx_record`, `disposable_domain`,
`role_account`, `possible_typo`, or `mx_unknown` — so importers can branch
deterministically. The `summary.risky` count is the number of deliverable but
flagged rows (disposable / role / typo / non-`low` band) a sender should review
before importing.

## See also

* [Messaging API → Send email](/api-reference/endpoints/messaging)
* [Channels → Email](/channels/email)
* [Compliance → Preference Center](/api-reference/endpoints/contacts) (for richer per-channel + per-topic preferences)
