Knowledge base
Templates
A body written once, versioned, and sent many times: from the composer, from your own code, or by an agent.
Details
- A template belongs to the connection rather than to the person who wrote it, which is the whole reason the first version of this was replaced. That one was keyed on the user: a teammate’s template was invisible to a workspace API key, so an integration could not send what the person who set it up could see, and deleting the author’s account took the workspace’s templates with it. The rows were carried over rather than dropped.
- Two ways to author a body. A block tree over the components of
@react-email/components(Section, Row, Column, Container, Text, Heading, Button, Link, Img, Hr, Markdown, CodeBlock, CodeInline), checked as it is written, so a bad node or an unsafehrefis refused on the call that wrote it instead of arriving as a broken email. Or markup you rendered yourself: if your templates are already react-email components in your own repo, render them there with@react-email/renderand post the HTML, which is sanitised once when the version is published. - Twenty-three starting points and a blank one, in a gallery rather than a menu, because these are things you have to look at before choosing between them, so every card shows the email itself. Welcome and verification, receipts and invoices, shipping and renewals, digests and announcements, laid out under four headings, and each one opens in the visual editor with every piece movable, restylable and deletable. Whichever you pick arrives as a draft, so nothing is sendable until you publish it.
- The gallery is not only in the app. GET /templates/starters lists the same starters, GET /templates/starters/{slug} returns one with the document it would seed, and naming a starter when you create a template, or when you replace a template’s content, seeds it exactly as picking the card does. The SDK wraps the same calls, and the MCP server has listTemplateStarters and getTemplateStarter.
- Named slots and named props are the two kinds of hole, written as {{key}} in the body and in the subject. A slot is filled by whoever edits the template and carries a default, so a send that names nothing still renders. A prop is supplied at send, and one marked required refuses the send when it is absent: a 422, and no mail leaves. That refusal is the point of declaring it: the alternative is a message going out with a blank where the order number should be, which nothing reports and nobody can recall.
- A published version is frozen. Editing the body of a published template mints a new draft rather than rewriting what is live, so sends keep resolving what they resolved yesterday until somebody publishes, and a send that pins a version number is unaffected even then. The body is compiled at publish, which is what makes a template that does not render fail for the person publishing it rather than for a recipient.
- Preview renders exactly what a send would produce without sending it, and reports what is still empty instead of refusing it. A template being previewed is usually a template being written, and an author filling in one block at a time should not have to satisfy every prop to see what they have so far.
- Every template carries its own record of what it has sent: how many went out over the last seven, thirty or ninety days, which of them were opened and which were clicked, a day-by-day series, and a split by where each send came from: the composer, your code, an agent, or the queue. Each rate names its own denominator rather than borrowing one, because they are genuinely different: opens are counted over the messages that carried a pixel, clicks over the messages that carried a rewritten link, and a message can carry one without the other. A preview is never recorded at all, and a test send is counted separately and left out of every figure, because it never left.
- That record is honest about what it cannot see, which is the reason to trust the half it can. A send is matched to its delivery record by the send itself, and only mail sent from your own code or from the queue carries one. A message sent from the composer, from an agent or by the assistant does not. So a template used mostly from the composer arrives with most of its sends unmatched, and the unmatched count is shown as its own number rather than folded in as nobody opened it.
- Four surfaces over one service, so “what does publishing mean” has one answer rather than three that agree today. The composer’s Templates button offers the published ones and REPLACES the message with what you pick rather than pasting it in: the send names the template, the version is pinned at the moment you choose it, and the body is rendered where it was authored, so a publish between choosing and clicking cannot change what goes out, and a layout the composer could not have held survives intact. What you fill in there is the values, not the body. /templates carries the same service over REST, behind the templates:read and templates:write scopes, wrapped method for method by the SDK. Sending one needs a send permission as well as a template permission, so a key issued to a copywriting tool can author and publish without being able to mail anybody. The MCP server carries the same range, from listing, reading and previewing to editing, publishing, restoring and deleting, and the assistant in the app always asks before it deletes a template or one of its versions, because a delete cannot be taken back. Editing a stored body is a screen: /mail/templates, under Management in the sidebar, carries a block canvas with a palette and an inspector, a live preview beside it, and Save and Publish as separate acts, which is what makes a published version something you can leave alone while you work on the next one.
- 200 templates per connection, a block document capped at 500 blocks, eight levels of nesting, 20,000 characters in any one value and 100 slots and 100 props; a body posted as markup capped at a million characters, and a subject at 998. Every one of those is a guard against a runaway script rather than a plan limit, every one refuses with a message naming the number it refused on, and nothing about templates is behind a tier.
- Automations send your published templates too, one for each email step, filled in for every contact.