# 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](/content/docs/api/index.html)).

## Endpoints

| Method & path              | What it does                                                                                                                                                                                                                                                                               |
|----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `GET /api/v1/emails`      | The library. Filters below.                                                                                                                                                                                                                                                                  |
| `POST /api/v1/emails`     | Create one. Body: `title`, `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 by default. 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. With `?permanent=1` the row is permanently deleted instead — allowed only for an email nothing points at; anything held by the send log, the queue, a sequence, a broadcast or a flow refuses with `409` and a list of what's holding it.  |
| `POST /api/v1/emails/:id/preview` | The compiled, personalized HTML for one person. Optional `body_markdown` / `template_id` render a draft that is never saved. See below.                                                                                                             |
| `POST /api/v1/emails/:id/test_send` | Deliver it for real, to one address. Body: `to` (required). See below.                                                                                                                                                                                                                                                                            |
| `POST /api/v1/emails/fork` | Split one holder off a shared email. Body: `holder_type` (`sequence_step` or `broadcast`), `holder_id`.                                                                                                                                                                                               |

## Title and subject

An email carries two names. `subject` is the subject line, as recipients read it. `title` is what the email is called wherever it's managed rather than read — the library list, a sequence's steps, a picker — and never reaches a recipient. Set one when the subject makes a bad label: "Try Builder Methods Pro again ($50 off as a returning member)" is a fine subject and a useless row in a list of five steps.

Reading, `title` always answers "what is this email called" — the subject stands in when nobody has set one, so it is never blank. `title_raw` is what's actually stored, and is `null` for an email that has never been named, which is how you tell "named this" from "falling back". Writing, send `title`; an empty string clears it and hands the job back to the subject.

```
GET /api/v1/emails/42

{ "email": { "id": 42,
             "title": "BMP winback",
             "title_raw": "BMP winback",
             "subject": "Try Builder Methods Pro again ($50 off as a returning member)",
             … } }

GET /api/v1/emails/43          # never given a title

{ "email": { "id": 43,
             "title": "Where to start",
             "title_raw": null,
             "subject": "Where to start",
             … } }
```

## Reading back what you wrote

`POST /api/v1/emails/:id/preview` returns the email as it would land: components expanded, `{% button %}` rendered in the theme's colours, markdown converted, the layout's [theme tags](/content/docs/api/templates.html#theme-tags/index.html) resolved, the layout wrapped around it and Liquid resolved against one real person. The theme tags go first, at compile time, before Liquid runs at all — so what a preview shows is the theme the layout actually points at. Tracking is the one thing a preview leaves out — no wrapped links, no open pixel — because a preview is never a send.

```
POST /api/v1/emails/42/preview
{ "person_id": 17 }

{ "html": "<!doctype html>…", "person": { "id": 17, "email": "pat@example.com", "name": "Pat" } }
```

| Field         | What it does                                                                                                                                                                   |
|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `person_id`  | Who to render for. Omitted, it's a pinned preview person, or anyone.                                                                                                                                                             |
| `segment_id` | Render for somebody who would actually receive this: a member of that segment, at random. Their merge fields are the real ones.                                                                                                  |
| `mode`       | `random` for anyone at all — useful for catching a missing field your own tidy test record would never hit.                                                                                                                       |
| `body_markdown` | Render _this_ body instead of the saved one. Nothing is written.                                                                                                                                                                  |
| `template_id` | Render through this layout instead of the saved one. Nothing is written.                                                                                                                                                               |

The last two are the check-before-write: whether a component expands, whether a button renders, whether a conditional resolves the way it was meant to — answered without the email having to carry the draft first. The record is untouched, and so is its cached compile, so a preview of a half-written body can never be what a later send goes out with.

`422` when there is nobody to preview as. A preview needs a real person, because rendering against an imaginary one is how a broken `{{ person.first_name }}` gets through.

## Proving it actually sends

A preview answers what the email says. `POST /api/v1/emails/:id/test_send` answers whether it arrives: the compiled email, through the configured provider, into a real inbox. It's the only check that exercises the provider, the sending domain and whatever the receiving client does with the HTML.

```
POST /api/v1/emails/42/test_send
{ "to": "reader@example.com" }

{ "sent": true, "to": "reader@example.com", "provider": "postmark",
  "note": "Accepted by the provider. Acceptance isn't delivery." }
```

`to` is required. An API token isn't a person, so unlike the editor there is no "me" to fall back to — and a real message in a real inbox deserves a deliberate address rather than a default. Anything that isn't an address is a `422`.

Two consequences worth knowing before you call it:

- **It's marked as a test**, so nobody's sent-status moves and nobody's place in a sequence changes. Sending one doesn't make a recipient ineligible for the real thing later.
- **The address becomes a person** if it isn't one already, because a send needs somebody to send to. Testing against an address is a small, deliberate way of adding it.

The response says `sent`, not _delivered_. The provider accepting a message is not the inbox receiving it, and reporting otherwise is how a bounce goes unnoticed.

## 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 title, 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<br> source: a later edit reaches **every send that uses it**, all<br> its opens and clicks accrue to the one email, and anyone who received it<br> anywhere counts as having received it<br> everywhere. |
| `copy`       | An independent copy of it, subject kept. Edits stay here, **its stats**<br> **start at zero**, and everyone who already received the original<br> 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.                                                     |
| `409` | `DELETE ?permanent=1` on an email something still points at. The errors name the holders; archive instead, or detach them first. |

See also: [Sequences API](/content/docs/api/sequences.html) · [Broadcasts API](/content/docs/api/broadcasts.html) · [Emails, for humans](/content/docs/usage/emails.html)
