Welcome email from a template

Keep the copy in a template, and send it with nothing but an id and a few values.

A welcome email is written once and edited by marketing forever after. Keeping it in a template means those edits never need a deploy — your sign-up code only knows the template id and the values to fill in.

1. Create the template, once

Template management needs a full_access key (or the dashboard's Templates page). Run this from your laptop or a setup script, not from the app.

curl -X POST https://api.rovela.dev/templates \
  -H "Authorization: Bearer re_your_full_access_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Welcome",
    "subject": "Welcome to Acme, {{first_name}}",
    "html": "<h1>Hi {{first_name}},</h1><p>Your workspace <strong>{{workspace}}</strong> is ready.</p><p><a href=\"{{login_url}}\">Open Acme</a></p>",
    "text": "Hi {{first_name}},\n\nYour workspace {{workspace}} is ready.\nOpen Acme: {{login_url}}"
  }'

Keep the returned id; it is what the app sends with.

{"object": "template", "id": "3b0e7c52-...", "name": "Welcome", ...}

2. Send it from the sign-up handler

A sending_access key is enough. There is no subject or html in the request: both come from the template, rendered with the variables.

const WELCOME_TEMPLATE_ID = process.env.WELCOME_TEMPLATE_ID

// Template values are inserted as-is, without HTML escaping. Anything the
// user typed goes through this first, or a name like <img src=x> ends up
// as markup in your email.
function escapeHtml(s) {
  return String(s)
    .replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;").replace(/'/g, "&#39;")
}

export async function sendWelcome(user) {
  const res = await fetch("https://api.rovela.dev/emails", {
    method: "POST",
    headers: {
      Authorization: "Bearer " + process.env.ROVELA_API_KEY,
      "Content-Type": "application/json",
      // One welcome per user, however many times sign-up is retried.
      "Idempotency-Key": "welcome-" + user.id,
    },
    body: JSON.stringify({
      from: "Acme <hello@mail.acme.com>",
      to: user.email,
      template: {
        id: WELCOME_TEMPLATE_ID,
        variables: {
          first_name: escapeHtml(user.firstName),
          workspace: escapeHtml(user.workspaceName),
          login_url: "https://app.acme.com/login",
        },
      },
    }),
  })
  if (!res.ok) throw new Error("welcome email failed: " + res.status)
}
The same variables fill the subject and the text part, so an escaped value shows up there as an entity — Tom &amp; Jerry instead of Tom & Jerry. If your names can contain & or <, keep separate variables for HTML and for plain text.

What can go wrong

  • A literal {{first_name}} in the inbox. A placeholder with no matching key is left as-is. Keys are matched exactly, so firstName does not fill {{first_name}}.
  • Someone edits the live template. Edits apply to the next send, including scheduled ones. Duplicate it, edit the copy, test it, then switch WELCOME_TEMPLATE_ID.
  • A batch send. /emails/batch does not accept template; welcome mail goes one request per user anyway.

More on variables, drafts and publishing: Email templates.