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

# Resend

> Emails, idempotency, domains, API keys, audiences and contacts, and a local inbox.

Resend email API emulation with email sending, domain management, API keys, audiences, contacts, and a local inbox for captured messages.

## At a glance

| | |
| - | - |
| Package | `@deploybaseline/resend` |
| Port | `4008` in the default set, `4000` when started alone |
| Config key | `resend` |
| HTTP operations | 32, browsable at `http://localhost:3999/docs/resend` while it runs ([API reference](/api-reference.md)) |
| Vendor spec | [resend/resend-openapi](https://github.com/resend/resend-openapi) at `69ea636` |

```bash CLI theme={null}
npx @deploybaseline/cli start --service resend
```

```ts Programmatic theme={null}
import { createEmulator } from "@deploybaseline/cli";

const resend = await createEmulator({ service: "resend" });
// resend.url is http://localhost:4000
```

```json baseline.config.json theme={null}
{
  "resend": {
    "domains": [
      {
        "name": "example.com",
        "region": "us-east-1"
      }
    ],
    "contacts": [
      {
        "email": "test@example.com",
        "first_name": "Test",
        "last_name": "User"
      }
    ]
  }
}
```

## Start

```bash theme={null}
npx @deploybaseline/cli start --service resend
```

When all services are started, Resend listens on `http://localhost:4008`; alone, it starts on `http://localhost:4000`. Point the `resend` SDK at it with `new Resend(apiKey, { baseUrl: "http://localhost:4008" })`.

## Auth

Any non-empty `Authorization: Bearer` token is accepted, and a request with no header falls back to the default user. Keys the emulator issued, from `POST /api-keys` or the `api_keys` seed, behave like Resend's: a deleted key is refused with `403 invalid_api_key`, and a `sending_access` key may only call `POST /emails` and `POST /emails/batch` (anything else is `401 restricted_api_key`). A `sending_access` key created with a `domain_id`, or seeded with a `domain`, refuses mail from any other domain with `403`. `GET /api-keys` reports each key's `last_used_at`. Where tokens are configured with no fallback user, as in `startService` tests with `tokens`, an unknown token is `403 invalid_api_key`.

## Emails

* `POST /emails` — Send single email
* `POST /emails/batch` — Send up to 100 emails
* `GET /emails` — List sent emails
* `GET /emails/:id` — Get email
* `POST /emails/:id/cancel` — Cancel scheduled email
* `PATCH /emails/:id` — Reschedule a scheduled email (`scheduled_at`)

An email's `last_event` is `delivered`, `scheduled`, or `canceled`, as Resend reports it; webhook event types keep the `email.` prefix. Creating an API key, domain, audience, or segment returns `201`.

### Idempotency

`POST /emails` and `POST /emails/batch` accept the case-insensitive `Idempotency-Key` header. Keys must be 1 to 256 characters and are retained for 24 hours. Repeating a request with the same key and validated payload returns the original response without creating another email or dispatching duplicate webhooks. Reusing a key with a different payload or endpoint returns `409 invalid_idempotent_request`. Invalid key lengths return `400 invalid_idempotency_key`. Requests without the header keep the normal non-idempotent behavior.

### Recipient restrictions and send attempts

Set `resend.allowlist` in seed config to test recipient rejection:

```json theme={null}
{
  "resend": {
    "allowlist": ["allowed@example.com", "copy@example.com"]
  }
}
```

Omitting `allowlist` accepts every recipient; `allowlist: []` rejects every recipient. Every address in `to`, `cc`, and `bcc` must be listed. Matching ignores surrounding whitespace and letter case; entries are individual addresses, with no wildcard or domain matching. This is a local testing control, independent of Resend's domain verification and account restrictions.

A rejected send returns `403 validation_error` naming the rejected addresses and configured allowlist. No email ID, email record, idempotency record, or webhook is created. A batch is rejected as a whole if any recipient is disallowed. Retrying a rejected request, even with the same idempotency key, creates a new attempt. Replaying an accepted idempotent request returns its original response even if the allowlist has since changed.

Validated sends append one record per email to the `resend.send_attempts` store collection, including emails in a rejected batch. Each record contains `from`, `to`, `cc`, `bcc`, `subject`, `idempotency_key` (or `null`), and the store's `id`, `created_at`, and `updated_at`. Message bodies are not copied into attempts. Malformed requests, invalid or conflicting idempotency keys, and idempotency replays do not append attempts.

Read attempts with `getResendStore(store).sendAttempts.all()` from `@deploybaseline/resend`, or inspect the persisted state. Shared adapter snapshots use the key `resend:resend.send_attempts`. Attempts and recipient restrictions survive saved-state restoration, including rejected sends. There is no additional Resend API endpoint for attempts. A rejected send has an attempt without an email; a send that never happened has neither.

## Domains

* `POST /domains` — Create domain
* `GET /domains` — List domains
* `GET /domains/:id` — Get domain
* `PATCH /domains/:id` — Update domain: set `open_tracking`, `click_tracking`, and `tls`
* `DELETE /domains/:id` — Remove domain
* `POST /domains/:id/verify` — Trigger domain verification

## API keys

* `POST /api-keys` — Create API key (`permission`, `domain_id`)
* `GET /api-keys` — List API keys
* `DELETE /api-keys/:id` — Delete API key

Issued keys are enforced as described under [Auth](#auth).

## Audiences and contacts

* `POST /audiences` — Create audience
* `GET /audiences` — List audiences
* `GET /audiences/:id` — Get audience
* `DELETE /audiences/:id` — Delete audience and its contacts
* `POST`, `GET /segments`, `GET`, `PATCH`, `DELETE /segments/:id` — Segments: the same records as audiences
* `POST /audiences/:audience_id/contacts` — Add contact
* `GET /audiences/:audience_id/contacts` (or `/segments/:id/contacts`) — List contacts
* `GET /audiences/:audience_id/contacts/:id_or_email` — Get contact
* `PATCH /audiences/:audience_id/contacts/:id_or_email` — Update contact: `first_name`, `last_name`, `unsubscribed`
* `DELETE /audiences/:audience_id/contacts/:id_or_email` — Delete contact

Resend renamed audiences to segments. `/segments` serves the same records as `/audiences` (the `resend` SDK v6 sends `resend.audiences.*` calls there), and `PATCH /segments/:id` renames one. Deleting an audience or segment removes its contacts. A contact under an audience is addressed by its id or its email address.

## Seed config

```json theme={null}
{
  "resend": {
    "domains": [
      {
        "name": "example.com",
        "region": "us-east-1"
      }
    ],
    "api_keys": [
      {
        "name": "ci",
        "token": "re_ci_key"
      },
      {
        "name": "sender",
        "token": "re_sender_key",
        "permission": "sending_access",
        "domain": "example.com"
      }
    ],
    "contacts": [
      {
        "email": "test@example.com",
        "first_name": "Test",
        "last_name": "User"
      }
    ],
    "allowlist": ["allowed@example.com"]
  }
}
```

* `token`: optional; generated when omitted
* `permission`: full\_access (default) or sending\_access
* `domain`: optional; a seeded domain this key may send from

| Key | Purpose |
| - | - |
| `domains` | Domains: `name` and `region` |
| `api_keys` | API keys: `name`, `token` (generated when omitted), `permission` (`full_access` by default, or `sending_access`), and `domain` (a seeded domain the key may send from) |
| `contacts` | Contacts: `email`, `first_name`, `last_name` |
| `allowlist` | Recipients that sends may reach; omit to accept everyone (see [Recipient restrictions and send attempts](#recipient-restrictions-and-send-attempts)) |

Seeded keys are checked like keys from `POST /api-keys`: a `sending_access` key may only send, and one with a `domain` only from that domain. See [Auth](#auth).

## Inbox

* `GET /inbox` — List captured emails
* `GET /inbox/:id` — View captured email

## Limits

Resend's global `/contacts` API, topics, and contact properties are not implemented yet.

## Conformance

| | |
| - | - |
| Routes | 32 supported or partial, 5 planned |
| Route gate | fail |
| Spec gate | fail, 4 allowed ([resend/resend-openapi](https://github.com/resend/resend-openapi) at `69ea636`) |
| SDKs tested | `resend@6.34.1` |
| Dual mode | not yet |

See [CONFORMANCE.md](https://github.com/deploybaseline/cli/blob/main/CONFORMANCE.md) for every provider.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.