Emails

Send an email

POST
/api/v1/emails

Queues a send to one recipient from a published template. Returns 202 with a message id once the send is durably recorded — never waiting on the provider, so this is safe to call from inside your own user-facing request. Address verification, rendering and provider dispatch happen in the background, exactly as they do behind SendGrid's 202 Accepted and Resend's immediate id.

Problems with the REQUEST still come back right away as a 4xx: an unknown or unpublished template, no verified sender, a suppressed or undeliverable recipient, a paused project, a cap. What moves is the delivery itself.

Follow a message with GET /api/v1/emails/{id}, or subscribe to your provider's webhooks. Pass an Idempotency-Key header to make retries safe.

Requires a write-scope key.

Authorization

bearerAuth write
headerAuthorizationBearer <token>

An API key from the dashboard under Settings → API keys, sent as Authorization: Bearer aem_….

Authorization has two independent axes.

The scope is ranked — a key satisfies any requirement at or below its own tier:

  • read — see messages, contacts and metrics. Changes nothing, and cannot send.
  • write — everything read does, plus managing templates, contacts, automations, segments and suppressions. This is what editing a template needs.
  • admin — everything write does, plus sending configuration: domains, senders, webhook registration, kill switch, daily cap, brand.

There is no approve scope. It was a rung once; it is not one now, and a key requested with it is rejected.

The approval grant (can_approve) is a separate boolean, not a rung. Delivering mail to a real inbox needs write and the grant. Keeping them on separate axes is what makes the review gate a control rather than a convention: a key that may propose is not automatically a key that may approve its own proposal.

Give your application the lowest tier that works. Most need write and the grant — the dashboard mints that combination as Send + manage; Manage only is the same rung with the grant withheld.

Scope: write

Header Parameters

Idempotency-Key?string

Unique per logical send (≤256 chars). A repeat returns the original result with deduplicated: true.

Lengthlength <= 256

Request Body

application/json
  1. body
to*string

Recipient email address. Single recipient — there are no bulk sends.

Match^(?:[A-Za-z0-9_'+\-]+\.)*[A-Za-z0-9_'+\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$
Formatemail
cc?|array<>

Carbon-copy recipient(s) of the same message — a string or array. Not a way to bulk-send.

bcc?|array<>

Blind-carbon-copy recipient(s) — a string or array.

template*string

Slug of a published template in this project.

Length1 <= length
props?

Values for the template's variables.

locale?string

Deliver a published translation for this locale (e.g. fr, pt-BR); falls back to the base language when no translation exists.

Length2 <= length <= 35
from?string

Sender, as Name <addr@domain> or a bare address. The address must be on a verified sending domain in this project. Omit to use the project's default sender.

Length3 <= length <= 320
reply_to?|array<>

Reply-To address(es) — a string or array.

headers?

Custom email headers. Reserved headers (List-Unsubscribe, Idempotency-Key, Cc/Bcc/Reply-To) are rejected.

tags?array<>

Up to 10 {name,value} metadata tags, forwarded to the provider for analytics.

Itemsitems <= 10
scheduled_at?string

ISO 8601 timestamp, or natural language like in 2 hours / tomorrow 9am. A future value schedules the send via the worker. Cannot be combined with attachments. Manage with PATCH (reschedule) / DELETE (cancel).

Length1 <= length
attachments?array<>

Up to 10 attachments (base64 content). Total ≤ ~5 MB. Cannot be combined with scheduled_at.

Itemsitems <= 10

Response Body

Idempotent replay — the Idempotency-Key matched a send we already accepted. Carries that send's current status.

application/json
  1. response
id*string

Message id — use it with GET /api/v1/emails/{id}.

status*string

sent (delivered to provider synchronously), queued (throttled to the worker), or scheduled.

Value in"sent""queued""scheduled"
deduplicated?boolean

Present and true when a matching Idempotency-Key replayed a prior send.

Example Request
curl -X POST 'https://sendandretain.com/api/v1/emails' \  -H 'Idempotency-Key: welcome-jane-2026-07-20' \  -H 'Content-Type: application/json' \  -d '{  "to": "[email protected]",  "template": "welcome",  "props": {    "firstName": "Jane"  },  "from": "Acme <[email protected]>"}'
Example Response
{  "id": "msg_3aF9c1",  "status": "sent",  "deduplicated": true}