# One POST, one gate, one send that can't be lost

> The transactional email API with one admission gate: durable 202 sends, idempotency keys, batches of 500, scheduling, suppressions, and validation.

Canonical: https://emailfast.dev/features/email-api

## A send call that holds up under failure

Email Fast is a transactional email API with a single write path. You call
`POST /v1/emails`, and a 202 from the API means the send is committed to a durable, partitioned outbox before we answer — a crash can't lose it, and a retry with the same idempotency key can't double-send. Everything else on this page — batches,
scheduling, streams, suppressions, validation — hangs off that one call and passes
through that one gate.

## How it works

| Surface | What you get |
|---|---|
| `POST /v1/emails` | One message. `idempotency_key` makes retries safe; `send_at` schedules up to 30 days out; a scheduled send can be canceled before it leaves |
| `POST /v1/emails/batch` | Up to 500 messages per call, with a per-item result for each |
| Streams | Separate transactional traffic from broadcasts, so one kind's volume never sits in front of the other |
| `GET /v1/messages/:id` | The full timeline of any send: admission, render, queue, delivery events |
| Suppressions | Bounces, complaints, and unsubscribes enforced at admission — readable and manageable over the API |
| `POST /v1/validate` | Catch undeliverable addresses before you spend a send on them |

Underneath all of it: every send — REST, SMTP, browser SDK, compatibility endpoints, broadcasts, automations — passes through one admission gate: idempotency, suppression, quota, and content policy in a single checkpoint no ingress can skip.

## The evidence

:::panel What a 202 actually commits to
```json
// POST /v1/emails
{
  "to": "ada@example.com",
  "subject": "Reset your password",
  "html": "<p>Your reset link is inside.</p>",
  "idempotency_key": "reset-8412"
}

// HTTP/1.1 202 Accepted
{ "id": "msg_01h9...", "status": "queued" }
```
Replay that exact request — same `idempotency_key` — and you get the same
`msg_01h9...` back, not a second email. Kill the process right after the 202 and the
send still happens: it was on disk before we answered.
:::

## Honest limits

:::tradeoffs What a 202 is not
A `202` is admission, not delivery. Delivery is asynchronous; the outcome arrives on
the message timeline and over [webhooks](/features/webhooks). The caps are real: 500
per batch, 30 days for `send_at` — guardrails, not upsells. And during early access,
live outbound delivery opens at launch; everything here runs today against
[sandbox keys](/features/sandbox).
:::

## Where to go next

Start with the [developer overview](/developers) or go straight to the
[API reference](/docs). Coming from another provider? Your existing SDK may already
work — the [comparison](/compare/sendgrid-alternative) covers the differences that
matter.

## How do idempotency keys work?

Pass idempotency_key on any send. A retry with the same key returns the original message instead of creating a duplicate — per message, honored for 90 days. Crash mid-request, retry blindly, and you still send exactly once.

## Can I schedule and cancel sends?

Yes. send_at schedules up to 30 days ahead, and a scheduled message can be canceled any time before it leaves. GET /v1/messages/:id shows exactly where it is.

## What are the batch limits?

POST /v1/emails/batch takes up to 500 messages per call and returns a per-item result for each, so one bad address never fails the other 499.

## How do I find out what happened to a message?

Every send has a timeline: GET /v1/messages/:id returns the full event history, and signed webhooks push the same events to your endpoint as they happen.

## Can I try it without sending real email?

Yes — sandbox keys run this whole surface against the real pipeline with nothing leaving, free and unlimited on every plan.
