Emails API
The email library: author, revise and retire emails, sort out what's actually reusable, and split a shared email off for one holder.
All endpoints need a bearer token (see Overview & auth).
Endpoints
| Method & path | What it does |
|---|---|
GET /api/v1/emails | The library. Filters below. |
POST /api/v1/emails | Create one. Body: subject, pre_header, from_address, body_markdown, labels[], template_id. |
GET /api/v1/emails/:id | One email with its markdown body and everywhere it's used. |
PATCH /api/v1/emails/:id | Update any of the same fields. |
DELETE /api/v1/emails/:id | Archives it. An email that has been sent is part of the record of what went out, and the send log has to keep pointing at it. |
POST /api/v1/emails/fork | Split one holder off a shared email. Body: holder_type (sequence_step or broadcast), holder_id. |
Telling reusable content from per-broadcast rows
Every broadcast mints its own email row — that's deliberate, and it means the library fills up with rows that existed for exactly one send. Two questions sort it out, and neither is answerable by search:
GET /api/v1/emails?used_in[]=sequences&used_in[]=flows&used_mode=none
GET /api/v1/emails?used_in[]=broadcasts&used_mode=any
| Facet | Matches an email that… |
|---|---|
broadcasts | a broadcast points at |
sequences | a sequence step points at |
flows | a flow reaches at all — a send_email step naming it, or a start_sequence step pointing at a sequence that contains it |
flows_one_off | a flow's own send_email step names directly |
used_mode is any (in any of them, the default),
all (in all of them) or none (in none of them).
Filtering happens server-side before any limit. Unknown facets are ignored rather
than filtering everything away.
Every row also carries where_used, so you can see what editing an
email would reach without asking a second question. Other filters:
?q= searches subject and pre-header, ?label= matches a
label, ?archived=1 includes archived rows, ?limit=
caps at 200.
The email-source contract
Anywhere an email is attached to something — a sequence step, a broadcast, or
either one's replace-email endpoint — the request carries
email_source. It is required, with no default.
| Value | What it means after the fact |
|---|---|
scratch |
A blank new row in the library. Nothing else points at it, so nothing else is affected by it. |
shared |
The library email named by email_id, itself. One
source: a later edit reaches every send that uses it, all
its opens and clicks accrue to the one email, and anyone who received it
anywhere counts as having received it everywhere.
|
copy |
An independent copy of it, subject kept. Edits stay here, its stats start at zero, and everyone who already received the original counts as not having received this one — so they're eligible again. |
Those consequences are the whole reason the choice exists, which is why there is no default: sharing and forking are opposite answers to "what does my next edit change", and picking one for the caller means they find out which way we picked after the send.
A broadcast never shares
POST /api/v1/broadcasts and its replace-email endpoint accept only
scratch and copy. shared is refused with
422, on the server, rather than trusted to a UI that only offers two
options. What one broadcast sent should stay what it sent — a shared row would
mean editing a sequence step could silently rewrite the record of a send that
already went out.
Forking: the repair
When something is shared that should have been a copy,
POST /api/v1/emails/fork splits that one holder onto a row of its
own. Every other holder keeps the original. Three consequences, all at once: edits
stop reaching everywhere else, the copy's stats start at zero, and everyone who
received the original counts as not having received this one.
POST /api/v1/emails/fork
{ "holder_type": "sequence_step", "holder_id": 12 }
A copy made for one holder keeps the subject. Only the library's own Duplicate prepends "Copy of" — there, two identical rows in a list nobody can tell apart is the actual problem. Attached to a step, the subject is what recipients see, and nobody means to send them "Copy of".
Errors
| Status | When |
|---|---|
401 | Missing or revoked token. |
404 | No such email. |
422 | Validation failed, or an email source that would be a guess. The message names the three choices and what each means. |
See also: Sequences API · Broadcasts API · Emails, for humans