---
title: "List templates"
description: "Every template on the connection, newest first, keyset paginated."
url: "https://openemail.uk/docs/api/templates/list"
area: "API"
category: "Mailbox"
---

# List templates

Every template on the connection, newest first, keyset paginated.

`GET /templates`

## GET /templates

Every template on the connection, newest first, keyset paginated.

## Two engines

**shell**

```
export OE=https://api.openemail.uk
export AUTH="Authorization: Bearer $OPENEMAIL_API_KEY"
```

A template is a body stored once and sent many times, and it belongs to the CONNECTION rather than to whoever wrote it. A workspace key sees the same templates a colleague does, and deleting the author’s account does not take them with it.

`engine: "blocks"` stores a tree whose node kinds are the exports of `@react-email/components` (`Section`, `Row`, `Column`, `Container`, `Text`, `Heading`, `Button`, `Link`, `Img`, `Hr`, `Markdown`, `CodeBlock` and `CodeInline`) and whose props are those components’ own. It is validated on the way in, so a bad node is a 422 on the call that wrote it rather than a broken email later.

`engine: "html"` stores markup you already have, sanitised once when the version is published. This is the one to reach for when your templates are react-email components living in your own repo: render the component with `@react-email/render` in your own build and post the result. There is no JSX endpoint and there is not going to be one. The API takes HTML because HTML is what a mail client reads, and executing a caller’s component would buy a sandbox nobody needs.

| Declared as | Filled by | Missing at send |
| --- | --- | --- |
| `slots` | whoever edits the template | the slot’s own `default` renders |
| `props` | whoever sends | `missing_template_prop`, a 422, and no mail leaves |

Both are written as `{{key}}` in the body and in the subject, and both carry a `kind` (`text`, `url` or `image`) which decides how the value is escaped when it is substituted. A key nothing declares fails at publish; a `url` value whose scheme is not `http`, `https` or `mailto` is refused rather than rendered.

> A published version is frozen. Editing the body of a published template mints a new draft instead of changing what live sends resolve, so a colleague rewriting the copy cannot change what your code already sends, and pinning `version` means it cannot change it when they publish either.

## Example

Needs `templates:read`. `limit` goes to 100, `status` narrows to `draft`, `active` or `archived`, and `cursor` is opaque, so pass back the `nextCursor` you were given rather than building one.

**curl**

```
curl "$OE/templates?limit=25&status=active" -H "$AUTH"
```

**Response**

```
{
  "object": "list",
  "data": [
    {
      "object": "template",
      "id": "tpl_9c1f0a4b7e05d3862c1f0a44",
      "name": "Order shipped",
      "slug": "order-shipped",
      "description": null,
      "status": "active",
      "publishedVersion": 3,
      "latestVersion": 4,
      "createdAt": "2026-08-01T09:12:44.000Z",
      "updatedAt": "2026-08-28T16:03:10.000Z"
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
```

> `publishedVersion` is what a send without a `version` resolves and `latestVersion` is the draft sitting on top of it. The two differing means somebody has edited and not published. It is not an error, and it is worth surfacing in a deploy log.

> A row is metadata. The declared `slots` and `props` come back from a retrieval, which is the call to make when you need to know what to pass one.
