Sending
One message, a batch, a schedule, or a template.
Scheduling
scheduled_at accepts an ISO 8601 timestamp or a relative phrase: in 2 min, in 3 days, in 2 weeks. Anything it cannot parse is an error rather than a fallback to now, because a silent fallback would fire an entire sequence at once.
A scheduled message can be cancelled with DELETE /v1/emails/{id} while its status is still scheduled. The message becomes canceled and does not go out, even if its time has already come. When it is due, suppressions and topic opt-outs are checked again for every recipient, so someone who unsubscribes after it was scheduled does not receive it. An unsubscribe also takes that address off the scheduled message straight away, from to, cc or bcc, and everyone else on it still receives it. If no to address is left, the message becomes canceled instead of going out.
A message can carry at most 50 recipients across to, cc and bcc, the most the delivery provider accepts on one message. More is refused with 422 invalid_request before anything is stored; send separate messages, or use a campaign for a list.
A marketing message (risk_class: "marketing") carries exactly one recipient. Its unsubscribe link and List-Unsubscribe header belong to one person, so a marketing send that names more than one address across to, cc and bcc is refused with 422 invalid_request before anything is stored. Send one message per recipient, which a batch does in one call, or use a campaign.
Recipient names
to, cc and reply_to take a bare address or Name <address>, so the recipient sees their name in the To line instead of an address. A name may be quoted, as in "Lima, Ana" <ana@example.com>, and is quoted for you in the header either way, so a comma is safe. Names outside ASCII are encoded for you. bcc accepts the same form and discards the name, since Bcc never reaches a header.
The name only reaches the headers. Suppressions, topic opt-outs, an API key's recipient allowlist and the message log all match on the address, so adding a name never changes who is reached, and GET /v1/emails still returns bare addresses. A name longer than 100 characters, or with a line break, < or > in it, is refused with 422.
Campaigns and automations do this on their own: when a contact has a first or last name, it goes in the To line.
Batches
POST /v1/emails/batch takes an array of 1 to 100 messages, up to 25 MB of JSON in all (a single send allows 15 MB, enough for one 10 MB attachment), and the whole array is validated before anything is sent. An entry that fails validation (a malformed address, a name with a line break, a subject with a line break, an oversized attachment) rejects the entire batch with 422 invalid_request, its details pointing at the entry by index, and nothing is sent. Check addresses before building the batch.
Past validation, entries are sent one at a time in order and each succeeds or fails on its own: an unknown template or topic, missing variables, no subject or body, a marketing entry with more than one recipient, a guardrail, the plan allowance or an SES error fails only that entry. The response is { "object": "list", "data": [...], "failed": n }, with data in input order and each entry carrying ok and either the send result or its error. It is 202 when every entry succeeded and 207 when any failed; retry only those. A skipped entry, one whose recipients were all suppressed, counts as a success. An entry that fails on missing variables names them in its message but carries no missing array.
Templates
Pass template and variables instead of html. Values are HTML-escaped when they are substituted into the HTML body, so text supplied by a user or an agent cannot rewrite the message around it. A missing variable is an error, not a half-rendered email with a visible placeholder.
Suppression
A send whose to addresses are all suppressed returns 202 with skipped: true and a reason. It is recorded in the log as rejected rather than silently dropped, so support can answer why something didn't arrive. Every recipient is checked, in to, cc and bcc: a suppressed or opted-out address is dropped wherever it appears and the message goes to the rest, as long as at least one to address is left.