# Add a verified recipient

Names an outside address and mails it a link to a page whose Confirm button makes it a verified recipient of this account; the link works for seven days, and opening it changes nothing on its own. Posting a pending address again mails a fresh link, at most once a minute. Posting a verified address answers 200 with `sent: false` and mails nothing. A refusal of the mail that concerns the address itself (its server refused it, or it is suppressed) is answered exactly as a sent link is. Reachable by the account's own key or a system key.

## `POST /api/v1/accounts/{accountId}/verified-recipients`

### Path parameters

- `accountId` (string, required) — Account ULID identifier

### Request body (required)

`application/json`

- `address` (string, required, 3–320 chars) — The outside address to verify

### Responses

**200** — The address's row as it now stands, and whether a link was mailed

`application/json` → VerifiedRecipientAdded

- `recipient` (VerifiedRecipient & object, required)
  - `address` (string, required) — The address, normalized as forwarding destinations are: trimmed, lowercased, its domain in A-label form
  - `createdAt` (integer <int64>, required) — When the address was added, in epoch seconds
  - `id` (string, required) — Verified recipient ULID: the handle DELETE takes, so no address is path-encoded
  - `lastSentAt` (integer | null, required) — When a confirmation link was last mailed, in epoch seconds. Each link works for seven days from its own mailing; a pending address whose newest link has expired is dropped at the next add, and posting it again mails a fresh one
  - `state` ("pending" | "verified", required) — Confirmation state; only 'verified' counts as a verified recipient
  - `verifiedAt` (integer | null, required) — When its owner pressed Confirm, in epoch seconds, or null while pending
- `sent` (boolean, required) — True when a confirmation link was mailed by this request; false when the address was already verified and nothing was sent

**400** — validation_failed, when the address is not one; or always_verified, carrying `domain`, when the address is on one of this account's own domains, which are always verified recipients and are never stored

`application/json` → ErrorOrValidation

**401** — unauthorized: the bearer credential is missing, malformed, expired, or revoked. Carries a `WWW-Authenticate: Bearer` challenge

`application/json` → ErrorBody

**404** — not_found: the account does not exist or is not accessible to this principal

`application/json` → ErrorBody

**409** — recipient_limit_reached, carrying `limit`: the account holds 100 addresses, pending and verified together, so remove one first; or pending_limit_reached, carrying `limit`: 10 addresses are awaiting their owners' answer, so wait for one, or remove one. A pending address whose newest link has expired no longer counts

`application/json` → ErrorBody

**413** — too_large: JSON request body exceeds 1 MiB or 500,000 values (object keys included), checked before parsing

`application/json` → ErrorBody

**429** — resend_cooldown, with Retry-After and `retryAfterSeconds`: a link was mailed to this address less than a minute ago; or confirmation_limit_reached, with Retry-After, `retryAfterSeconds` and `limit`: the account has mailed its 20 confirmation links for this UTC day; or rate_limited, with Retry-After: too many links from this account, or to this address, in the last minute. The last is deliberately one answer for both, since the budget per address is shared across the deployment

`application/json` → ErrorBody

**503** — recipient_verification_unavailable, with Retry-After: domain ownership is being removed and has not settled, so retry; or recipient_mail_unavailable: the link could not be mailed for a reason on the platform's side (the mail binding or the link key is unconfigured, the platform's hourly cap on its own mail is spent, or the sending service refused the message), so nothing is stored and the request may be retried. Or temporary_failure: the platform's storage could not answer just now; retry after the Retry-After window. Any operation can answer this. A write that got it may have been applied, so retry it with the same idempotency key where it takes one, or read the state back first (see Transient failures in the API description)

`application/json` → ErrorBody

### Example request

```bash
curl -X POST 'https://api.open.email/api/v1/accounts/{accountId}/verified-recipients' \
  -H 'Authorization: Bearer oek_…' \
  -H 'Content-Type: application/json' \
  -d '{
    "address": "string"
  }'
```
