---
title: "Send a transactional email"
method: POST
path: "/api/v1/emails"
tags: ["Emails"]
---

# Send a transactional email

`POST /api/v1/emails`

## Request

- Base URL: `https://api.sendly.now`
- URL: `https://api.sendly.now/api/v1/emails`
- Auth: one of:
  - HTTP bearer
  - API key in cookie `better-auth.session_token`
  - OAuth 2 (authorization code, authorization URL `https://app.sendly.now/api/auth/oauth2/authorize`, token URL `https://app.sendly.now/api/auth/oauth2/token`, scopes: `emails:send`)

Send one transactional email and receive its delivery status in the same response. Accepts a `template` id or an inline `subject` + `body`.

This is the send to reach for when you need to know what happened. The legacy `POST /api/emails` answers with row ids and no status, so telling an accepted send from a refused one costs a second request; here the receipt carries `status`, and `from` reports the sender actually used — which is worth reading, since a template may have supplied it.

Exactly ONE recipient. A single receipt cannot describe a fan-out, so `to` takes one address: use `cc`/`bcc` to copy others on the same message, and `POST /api/emails/batch` to send different ones.

`202 Accepted` is the success answer, and `PENDING` the usual `status`: the message is queued for the sending pipeline, not yet handed to the provider. Later states (`DELIVERED`, `BOUNCED`, …) arrive by webhook.

An optional `Idempotency-Key` header (1–255 chars, 24h TTL) makes a retry safe: the first request wins and a retry carrying the same key AND body replays its receipt. Reusing a key with a different body answers 422 rather than serving another request's result.

Requires the `emails:send` scope — Send emails from your verified domains.

## Headers

- `Idempotency-Key` string: Replay-safety key (24h TTL). Reuse it only to retry the identical request.

## Request body

- SendEmailV1: Body for POST /api/v1/emails. Either `template` or `subject`+`body` is required, and `to` names exactly one recipient.
  - `attachments` object[]
    - `content` string, required
    - `contentId` string
    - `contentType` string, required
    - `disposition` 'attachment' | 'inline'
    - `filename` string, required
  - `bcc` string[]
  - `body` string
  - `cc` string[]
  - `data` object: Arbitrary JSON value (string, number, boolean, null, array, or object).
  - `from` union
    - string, email
    - object
      - `email` string, email, required
      - `name` string
  - `headers` object: Extra header fields to add to the message, as name → value. A name must be an RFC 5322 field-name (printable ASCII, no spaces or colons) and must not be one Sendly writes itself: From, Sender, Reply-To, To, Cc, Bcc, Subject, Date, Message-ID, MIME-Version, Content-Type, Content-Transfer-Encoding, Content-Disposition, Return-Path, DKIM-Signature, DomainKey-Signature, Received, Authentication-Results, List-Unsubscribe, List-Unsubscribe-Post, and any X-SES-*, X-Sendly-*, Resent-* or ARC-* header. A reserved name, in any letter case, fails validation and nothing is sent.
  - `name` string
  - `reply` string, email
  - `subject` string
  - `subscribed` boolean
  - `tags` string[]
  - `template` string, uuid
  - `to` union, required: The single recipient. Use `cc`/`bcc` to copy others on the same message.
    - string, email
    - object
      - `email` string, email, required
      - `name` string

## Response `202`

Email queued

- EmailV1: Receipt for a single transactional send.
  - `from` string, email, required: The sender actually used. Worth reading rather than assuming: it is resolved server-side and may come from the template when the request named none.
  - `id` string, uuid, required: The Email row this send created. Quote it in support requests.
  - `status` 'PENDING' | 'SENDING' | 'SENT' | 'DELIVERED' | 'RECEIVED' | 'BOUNCED' | 'FAILED' | 'REJECTED' | 'RENDERING_FAILURE' | 'DELIVERY_DELAY' | 'CANCELLED', required: Delivery status at the moment of the response — `PENDING` for a send the worker has not picked up yet, which is the usual answer. Later states arrive via webhooks, not here.
  - `to` string, email, required: The recipient the message was queued for.

## Other responses

- `401`: `invalid_api_key` or `invalid_session` — missing or invalid credentials.
- `403`: `scope_missing`, `project_disabled`, `domain_not_allowed` — the `from` address does not resolve to a verified domain on this project — or `content_rejected`: automated content review flagged the message and it was not sent.
- `404`: `resource_not_found` — `template` names a template that does not belong to this project.
- `409`: `conflict` — a request with this `Idempotency-Key` is still in flight. Retry shortly.
- `422`: `validation_error` — the body did not match the schema; or `idempotency_key_reused` — this `Idempotency-Key` was already spent on a request with a different body. Send a new key.
- `429`: `rate_limited` — see `Retry-After` and the `RateLimit` headers.
- `500`: `internal_error`.
- `503`: `content_review_unavailable` — content review could not run for this new account, so the message was not accepted. Safe to retry after a short delay.

---

[API](https://skmtc.dev/sendly/apis/sendly-api.md) · [All operations](https://skmtc.dev/sendly/apis/sendly-api/llms.txt) · [OpenAPI document](https://skmtc.dev/sendly/apis/sendly-api/revisions/ce7cfb7f7c7e?raw)
