Templates API
Write an email once, with variables, and send it by id or alias from your code. Templates follow Resend's Templates API, so code written with a Resend SDK works after you change the base URL.
How templates work
A template has a draft and a published version. Creating or editing a template only changes the draft. Publishing copies the draft into the published version, and every send that names the template renders the published version. So you can edit a template that is live in your app, send yourself a test from the dashboard, and publish when it is right.
A template can set a default from, subject and reply_to. A send can override each of them. The body is HTML, or Markdown, which SMTPfast renders into a clean email layout.
Reading templates needs the email:read scope; creating, editing, publishing and deleting them needs email:send, the same split as broadcasts.
Variables
Write a variable as {{{KEY}}} anywhere in the subject, preview text or body, and declare it with a type and an optional fallback value. You can also write a fallback at the point of use: {{{PLAN|free}}}.
<p>Hi {{{NAME|there}}},</p>
<p>Your order of {{{PRODUCT}}} comes to {{{PRICE}}}.</p>
<p><a href="{{{RESEND_UNSUBSCRIBE_URL}}}">Unsubscribe</a></p>- A variable resolves to the value passed in the send, then its inline fallback, then its declared
fallback_value. A declared variable with neither must be given a value: a send that leaves it out answers 422 and names it inmissing_variables, before anything is charged or queued. The dashboard preview and test send show it empty instead. - Values are HTML-escaped in the HTML body, so a value can never add markup to your email.
- Keys use letters, digits and underscores, up to 50 characters, and a template can declare up to 50. Types are
stringandnumber; a fallback must match its type. FIRST_NAME,LAST_NAME,EMAIL,UNSUBSCRIBE_URL,RESEND_UNSUBSCRIBE_URL,contactandthisare reserved. The first three are contact fields: a broadcast fills them from the contact, and a transactional send fills them from the variables you pass under those names.{{{RESEND_UNSUBSCRIBE_URL}}}and{{unsubscribe_url}}become each recipient's own unsubscribe link.
Send with a template
Pass a template object to POST /v1/emails or to each item of POST /v1/emails/batch, with the template id or alias and the variables.
curl -X POST https://smtpfa.st/api/v1/emails \
-H "Authorization: Bearer $SMTPFAST_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "[email protected]",
"template": {
"id": "order-confirmation",
"variables": { "PRODUCT": "Desk lamp", "PRICE": 49 }
}
}'const res = await fetch("https://smtpfa.st/api/v1/emails", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SMTPFAST_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
to: "[email protected]",
template: { id: "order-confirmation", variables: { PRODUCT: "Desk lamp", PRICE: 49 } },
}),
});
const { id } = await res.json();- Only a published template can be sent. A template that was never published answers 422.
- Every variable that has no fallback and is used without an inline fallback needs a value. If one is missing, the send answers 422 with the keys in
missing_variables. html,textandreactcannot be sent together with a template.from,subjectandreply_toin the request win over the template's defaults. A subject in the request is rendered with the same variables.- After rendering, the email is an ordinary send: the sender domain must be verified, suppressed recipients are dropped, plan limits and credits apply, and an
Idempotency-Keyworks as usual. A retry with the same key replays the first email, even if the template was published again since.
Start a broadcast from a template
Pass template_id when you create a broadcast, or choose Start from a template in the broadcast editor. The template's current content, as the template editor shows it, is copied into the new draft, and declared fallbacks are written into the tags so the broadcast renders the same. Any field you send alongside wins. A broadcast still needs an unsubscribe link before it can be sent.
POST /v1/broadcasts
{
"template_id": "product-update",
"name": "October update",
"segment_id": "seg_customers"
}Starter gallery
The Templates page in the dashboard has 10 starters you can copy in one click. They use table layout and inline styles, fit a 600px column, adapt to dark mode in the clients that support it, and need no external fonts or images.
| Starter | Alias | What it is for |
|---|---|---|
| Welcome | welcome | A warm first email with one clear next step and a short getting-started list. |
| Email verification | verify-email | A one-time code with a magic link button, for sign-up checks or passwordless sign-in. |
| Password reset | password-reset | A reset link with an expiry, plus the request details so a reader can spot a bad actor. |
| Receipt | receipt | A clear payment receipt: amount up top, line items, totals and an invoice link. |
| Product update | product-update | A newsletter with one headline feature and a short list of smaller changes. |
| Weekly digest | weekly-digest | Three headline numbers and the week's top items, for a recurring summary. |
| Event invitation | event-invitation | A calendar-style date tile, the essentials at a glance, and RSVP plus add-to-calendar buttons. |
| Re-engagement | re-engagement | A friendly nudge for quiet accounts: what changed, one button back, and an honest way out. |
| Team invitation | team-invite | An invite to join a workspace, showing who invited them and the role they will get. |
| Usage alert | usage-alert | A plan limit warning with a progress bar, what happens next, and an upgrade path. |
Template object
{
"object": "template",
"id": "clx_tpl123",
"current_version_id": "b2693018-7abb-4b4b-b4cb-aadf72dc06bd",
"alias": "order-confirmation",
"name": "Order confirmation",
"status": "published",
"published_at": "2026-10-03T09:12:00.000Z",
"created_at": "2026-10-01T08:00:00.000Z",
"updated_at": "2026-10-03T09:12:00.000Z",
"from": "Acme <[email protected]>",
"subject": "Your order {{{ORDER_ID}}}",
"reply_to": null,
"html": "<p>Name: {{{PRODUCT}}}</p><p>Total: {{{PRICE}}}</p>",
"text": null,
"variables": [
{
"id": "e169aa45-1ecf-4183-9955-b1499d5701d3",
"key": "PRODUCT",
"type": "string",
"fallback_value": "item",
"created_at": "2026-10-01T08:00:00.000Z",
"updated_at": "2026-10-01T08:00:00.000Z"
}
],
"has_unpublished_versions": false,
"preview_text": null,
"markdown": null,
"category": "Billing"
}The content fields are the draft. preview_text, markdown and category are SMTPfast additions.
/v1/templatesCreate a template as a draft. It needs a name and html (or markdown). Publish it before sending with it.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Template name |
| html | string | Yes | HTML body. Optional when markdown is given. |
| alias | string | No | Unique handle in your team, usable wherever the id is |
| from | string | No | Default sender |
| subject | string | No | Default subject |
| reply_to | string | string[] | No | Default Reply-To |
| text | string | No | Plain-text body. Generated from the HTML when left out; an empty string stops that. |
| variables | array | No | Up to 50 { key, type, fallback_value } objects |
| markdown | string | No | Not in Resend. Markdown body, used when html is empty. |
| preview_text | string | No | Not in Resend. Inbox preview line. |
| category | string | No | Not in Resend. Label for the dashboard gallery. |
Request Body
{
"name": "order-confirmation",
"alias": "order-confirmation",
"from": "Acme <[email protected]>",
"subject": "Your order {{{ORDER_ID}}}",
"html": "<p>Name: {{{PRODUCT}}}</p><p>Total: {{{PRICE}}}</p>",
"variables": [
{ "key": "ORDER_ID", "type": "string" },
{ "key": "PRODUCT", "type": "string", "fallback_value": "item" },
{ "key": "PRICE", "type": "number", "fallback_value": 25 }
]
}Response
{
"id": "clx_tpl123",
"object": "template"
}/v1/templatesList templates, newest first. Rows carry no content.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| limit | number | No | 1 to 100, default 20 |
| after | string | No | Template id: return older templates. Not with before. |
| before | string | No | Template id: return newer templates. Not with after. |
| q | string | No | Not in Resend. Search name, alias and subject. |
Response
{
"object": "list",
"data": [
{
"id": "clx_tpl123",
"name": "order-confirmation",
"status": "published",
"published_at": "2026-10-03T09:12:00.000Z",
"created_at": "2026-10-01T08:00:00.000Z",
"updated_at": "2026-10-03T09:12:00.000Z",
"alias": "order-confirmation",
"subject": "Your order {{{ORDER_ID}}}",
"category": null,
"has_unpublished_versions": false
}
],
"has_more": false
}/v1/templates/:idGet a template by id or alias, with its draft content and variables.
/v1/templates/:idChange the draft. Only the fields you send change, and variables replace the whole list. Sends keep using the published version until you publish again.
Request Body
{
"html": "<p>Total: {{{PRICE}}}</p><p>Name: {{{PRODUCT}}}</p>",
"expected_updated_at": "2026-10-03T09:12:00.000Z"
}Response
{
"object": "template",
"id": "clx_tpl123",
"updated_at": "2026-10-03T09:14:02.511Z",
"status": "published",
"published_at": "2026-10-03T09:12:00.000Z",
"has_unpublished_versions": true
}/v1/templates/:id/publishPublish the current draft. Sends that name the template use this version from now on. Send an optional body with expected_updated_at to publish only the version you loaded; a newer one answers 409 revision_conflict.
Response
{
"object": "template",
"id": "clx_tpl123"
}/v1/templates/:id/duplicateCopy the draft into a new, unpublished template named with "(copy)" appended. The alias is not copied.
Response
{
"object": "template",
"id": "clx_tpl456"
}/v1/templates/:idDelete a template. Emails already sent with it are not affected; sends that still name it answer 404.
Response
{
"object": "template",
"id": "clx_tpl123",
"deleted": true
}Errors
Template endpoints answer errors in Resend's shape, with error carrying the same text as every other SMTPfast endpoint does.
{
"statusCode": 404,
"name": "not_found",
"message": "Template not found",
"error": "Template not found"
}A send that leaves out a required variable answers 422 from POST /v1/emails:
{
"error": "Missing values for the template variables ORDER_ID, PRODUCT, which have no fallback",
"missing_variables": ["ORDER_ID", "PRODUCT"]
}Differences from Resend
markdown,preview_textandcategoryare extra fields, and the list acceptsq.- A
textof exactly""stops a text part being generated from the HTML, as in Resend, but the email still carries an empty plain-text alternative, as any SMTPfast email sent withtext: ""does. - Timestamps are ISO 8601.