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, "&").replace(/</g, "<").replace(/>/g, ">")
.replace(/"/g, """).replace(/'/g, "'")
}
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 & 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, sofirstNamedoes 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/batchdoes not accepttemplate; welcome mail goes one request per user anyway.
More on variables, drafts and publishing: Email templates.