Documentation

Templates

Stored subjects and bodies with {{variable}} placeholders, addressed by slug and rendered with HTML-escaped values.

A template is a stored subject, HTML body and optional plain-text body with {{name}} placeholders. Send with one by passing template and variables on POST /v1/emails, or name it as a step's template_slug in an automation. The copy is written and reviewed once, and the values filled in at send time are escaped, so they cannot rewrite the message around them. The Templates guide shows the whole flow.

Templates are addressed by slug, not by id.

Variables

A placeholder is two braces around a name: {{name}}. Spaces inside the braces are allowed, so {{ name }} is the same placeholder. A name is letters, digits, underscores and dots. A dot is part of the name, not a path: {{user.name}} takes the value whose key is "user.name". Anything else between braces, such as {{first-name}}, is not a placeholder and is sent literally.

The placeholders in the subject, the HTML body and the text body are collected into the template's variables list whenever it is saved. Every name on that list must have a value when the template is rendered, even one that appears only in the text body. An empty string counts as a value; a missing key or null does not. Values for names the template does not use are ignored.

Values are HTML-escaped when substituted into the HTML body: &, <, >, " and ' become entities. Values come from agents and end users, and a name like <script> would otherwise run in whatever webmail displays it, or a prompt-injected value could rewrite the surrounding message. There is no syntax for inserting raw HTML. The subject and the text body are not HTML, so values go into them as given.

The template object

Every field is always present.

FieldTypeDescription
idstringThe template's id (a UUID). Not used by any endpoint.
slugstringLower-case letters, digits and hyphens. Unique within the workspace.
namestringDisplay name.
subjectstringThe subject line, with placeholders.
htmlstringThe HTML body, with placeholders.
textstring | nullThe plain-text body. null when the template has none. A template saved before empty strings were refused may hold "", which counts as no text body when rendering.
variablesarray of stringEvery placeholder name in the subject and both bodies, sorted and without duplicates. All are required to render.
created_atstring (ISO 8601)When the template was created.
updated_atstring (ISO 8601) | nullWhen the template was last updated. null until the first update.
{
  "id": "4b9e2c7a-1f5d-4a3e-8c6b-2d7f0a9e3c15",
  "slug": "welcome",
  "name": "Welcome",
  "subject": "Welcome to {{plan}}, {{name}}",
  "html": "<p>Hi {{name}}, your {{plan}} workspace is ready.</p>",
  "text": "Hi {{name}}, your {{plan}} workspace is ready.",
  "variables": ["name", "plan"],
  "created_at": "2026-09-15T12:00:00.000Z",
  "updated_at": "2026-09-15T12:30:00.000Z"
}

List templates

GET /v1/templates · requires templates:read

Returns every template in the workspace, sorted by name, in the list envelope without paging. Up to 500 are returned; at 500, has_more is true. Each entry carries variables, so this tells you what a template needs before you render or send it.

Example

curl https://api.sendraven.ai/v1/templates \
  -H "Authorization: Bearer $SENDRAVEN_API_KEY"

Response

200 OK

{
  "object": "list",
  "data": [
    {
      "id": "4b9e2c7a-1f5d-4a3e-8c6b-2d7f0a9e3c15",
      "slug": "welcome",
      "name": "Welcome",
      "subject": "Welcome to {{plan}}, {{name}}",
      "html": "<p>Hi {{name}}, your {{plan}} workspace is ready.</p>",
      "text": "Hi {{name}}, your {{plan}} workspace is ready.",
      "variables": ["name", "plan"],
      "created_at": "2026-09-15T12:00:00.000Z",
      "updated_at": "2026-09-15T12:30:00.000Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

Create or update a template

POST /v1/templates · requires templates:write

Creates a template, or updates the one with this slug if it already exists. A new template returns 201; an existing one returns 200 with the stored template after the update. Either way variables is recomputed from the new copy.

On an update, name, subject and html are replaced. text is replaced when given as a string, kept when left out, and removed when null; variables is recomputed either way, so a placeholder used only in the removed text body leaves the list. An empty string is refused with 422 invalid_request, because it is neither: send null to remove the text body.

Changing a template changes every later send that uses it, including automation steps already scheduled for people partway through a sequence. If the new copy adds a placeholder, those steps fail unless the enrolments carry a value for it.

Body

FieldTypeRequiredDefaultDescription
slugstringyesLower-case letters, digits and hyphens.
namestringyes1 to 200 characters.
subjectstringyes1 to 998 characters. May contain placeholders.
htmlstringyesAt least 1 character. May contain placeholders.
textstring | nullnononePlain-text body. May contain placeholders. On an update, left out keeps the stored text body and null removes it. At least one character; "" is refused.

Example

curl -X POST https://api.sendraven.ai/v1/templates \
  -H "Authorization: Bearer $SENDRAVEN_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "slug": "welcome",
    "name": "Welcome",
    "subject": "Welcome to {{plan}}, {{name}}",
    "html": "<p>Hi {{name}}, your {{plan}} workspace is ready.</p>",
    "text": "Hi {{name}}, your {{plan}} workspace is ready."
  }'

Response

201 Created for a new template, with updated_at null.

{
  "id": "4b9e2c7a-1f5d-4a3e-8c6b-2d7f0a9e3c15",
  "slug": "welcome",
  "name": "Welcome",
  "subject": "Welcome to {{plan}}, {{name}}",
  "html": "<p>Hi {{name}}, your {{plan}} workspace is ready.</p>",
  "text": "Hi {{name}}, your {{plan}} workspace is ready.",
  "variables": ["name", "plan"],
  "created_at": "2026-09-15T12:00:00.000Z",
  "updated_at": null
}

200 OK for an update, with updated_at set.

Errors

StatusTypeWhen
422invalid_requesttext is an empty string. The message says to send null to remove the text body.
422invalid_requestThe body failed validation. details lists each field.

Retrieve a template

GET /v1/templates/{slug} · requires templates:read

Returns one template.

Path parameters

ParameterDescription
slugThe template's slug.

Example

curl https://api.sendraven.ai/v1/templates/welcome \
  -H "Authorization: Bearer $SENDRAVEN_API_KEY"

Response

200 OK

{
  "id": "4b9e2c7a-1f5d-4a3e-8c6b-2d7f0a9e3c15",
  "slug": "welcome",
  "name": "Welcome",
  "subject": "Welcome to {{plan}}, {{name}}",
  "html": "<p>Hi {{name}}, your {{plan}} workspace is ready.</p>",
  "text": "Hi {{name}}, your {{plan}} workspace is ready.",
  "variables": ["name", "plan"],
  "created_at": "2026-09-15T12:00:00.000Z",
  "updated_at": "2026-09-15T12:30:00.000Z"
}

Errors

StatusTypeWhen
404not_foundNo template with this slug in the workspace.

Delete a template

DELETE /v1/templates/{slug} · requires templates:write

Deletes the template. A template that an active or paused automation renders a step from cannot be deleted: the call answers 409 invalid_state naming each such automation, because its step would fail every enrolment that reached it. Delete those automations, or set them to draft, first. Nothing else is checked: a later send naming a deleted template is refused, and a draft automation naming it fails its enrolments once it is activated. Campaigns carry their own HTML and never depend on a template.

Path parameters

ParameterDescription
slugThe template's slug.

Example

curl -X DELETE https://api.sendraven.ai/v1/templates/welcome \
  -H "Authorization: Bearer $SENDRAVEN_API_KEY"

Response

200 OK

{ "slug": "welcome", "deleted": true }

Errors

StatusTypeWhen
404not_foundNo template with this slug in the workspace.
409invalid_stateAn active or paused automation has a step using this template. The message names each one (up to 20) with its id and status. Nothing is deleted.

Render a template

POST /v1/templates/{slug}/render · requires templates:read

Renders the template with the values given and returns the result without sending anything. Use it to check that the values produce sensible copy before a real person sees it. It follows the rules in Variables exactly as a send does: values are escaped the same way, and a missing value is refused the same way.

Path parameters

ParameterDescription
slugThe template's slug.

Body

FieldTypeRequiredDefaultDescription
variablesobjectno{}Placeholder name to value. A string is used as given; a number or boolean is turned into its string form (3, true). An object or array is refused with 422, and null counts as no value. POST /v1/emails is stricter and accepts only strings.

Example

curl -X POST https://api.sendraven.ai/v1/templates/welcome/render \
  -H "Authorization: Bearer $SENDRAVEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "variables": { "name": "Ana <Admin>", "plan": "Pro" } }'

Response

200 OK

text is always present: null when the template has no text body, or an empty one.

{
  "subject": "Welcome to Pro, Ana <Admin>",
  "html": "<p>Hi Ana &lt;Admin&gt;, your Pro workspace is ready.</p>",
  "text": "Hi Ana <Admin>, your Pro workspace is ready."
}

Errors

StatusTypeWhen
404not_foundNo template with this slug in the workspace.
422invalid_requestvariables is not an object, or one of its values is an object or an array. The message names the value, for example variables.plan.
422missing_variablesOne or more of the template's variables has no value. The error object adds missing, an array of the names, for example { "error": { "type": "missing_variables", "message": "Template is missing values for: plan", "missing": ["plan"] } }.

Duplicate a template

POST /v1/templates/{slug}/duplicate · requires templates:write

Copies the template's subject, HTML and text bodies under a new slug. The copy is a new template with its own id and created_at; later changes to either do not affect the other.

Path parameters

ParameterDescription
slugThe slug of the template to copy.

Body

FieldTypeRequiredDefaultDescription
slugstringyesThe new template's slug. Lower-case letters, digits and hyphens, and unused in the workspace.
namestringnothe source's name followed by copySurrounding whitespace is trimmed, then 1 to 200 characters. An empty or blank name is refused.

Example

curl -X POST https://api.sendraven.ai/v1/templates/welcome/duplicate \
  -H "Authorization: Bearer $SENDRAVEN_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "slug": "welcome-v2" }'

Response

201 Created

{
  "id": "7e3a9c1f-5b2d-4e8a-a6f4-0c9d2b7e1a53",
  "slug": "welcome-v2",
  "name": "Welcome copy",
  "subject": "Welcome to {{plan}}, {{name}}",
  "html": "<p>Hi {{name}}, your {{plan}} workspace is ready.</p>",
  "text": "Hi {{name}}, your {{plan}} workspace is ready.",
  "variables": ["name", "plan"],
  "created_at": "2026-09-15T13:00:00.000Z",
  "updated_at": null
}

Errors

StatusTypeWhen
404not_foundNo template with the source slug in the workspace. Checked before the body.
409conflictA template with the new slug already exists.
422invalid_requestslug is missing or not lower-case letters, digits and hyphens, or name is empty, blank or longer than 200 characters. The message names each field that failed, and details lists them.