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 the one part of this that is only in the app. A caller reaching templates from your own code or from an agent posts a body (a block document or your own markup) rather than naming a starting point, so the twenty-three are somewhere to begin rather than a catalogue to install from.
- 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 is nine endpoints 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 five tools: list, read, preview, create and send. There is deliberately no tool to update, delete or publish an existing one. An edit mints a draft the next send would not resolve to, and a delete cannot be taken back. Editing a stored body is a screen: /workspace/templates 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.