Guides
GuideEmail Templates

Email Templates

Design HTML email templates with dynamic variables, preview them in the Email tab, and send transactional emails from your published app or backend functions.

Design templates in the Email tab using HTML with {{variable}} placeholders. Send emails from your app with:

await email.send({ to, template: "welcome", data: { user_name: "John" } })

Let AI build it for you

You don't need to write any code. Just tell the AI assistant what you need — for example: "Create a welcome email template with the user's name and a login button, and a backend function that sends it when someone signs up." The AI will design the template, set up variables, and write the backend function automatically.

Designing Templates

Open your project in Sites and click the Email tab (between Design and Config). Here you can create, edit, and preview email templates.

Creating a Template

  1. Click the template dropdown in the toolbar and select New Template
  2. Enter a name (e.g., "Welcome Email") and a slug (e.g., "welcome")
  3. The slug is how you reference the template in code: template: "welcome"
  4. Optionally set a default subject line

Editing Modes

Visual Editor

WYSIWYG editing with bold, italic, links, headings. Variables appear as colored pills.

Code Editor

Edit raw HTML directly. Full control over email markup and styling.

Preview

See the rendered email with test data. Toggle between desktop and mobile views.

Variables & Placeholders

Use {{variable_name}} placeholders in your template HTML and subject line. When sending, the developer passes values for each variable via the data parameter.

Template HTML
<h1>Hi {{user_name}},</h1>
<p>Welcome to {{app_name}}!</p>
<p>Your account is ready. Click below to get started:</p>
<a href="{{login_url}}">Log In</a>
Sending with data
await email.send({
  to: "jane@example.com",
  template: "welcome",
  data: {
    user_name: "Jane",
    app_name: "MyApp",
    login_url: "https://myapp.com/login"
  }
});

Insert Value Button

Click Insert Value in the toolbar to add variables. You can type a custom variable name or pick from your project's backend functions. Backend function names display with () in the visual editor to distinguish them.

How each placeholder is filled in: a value passed in data at send time always wins. If data doesn't include that key, the variable's default value (set when you define it in the Email tab) is used. With neither, the raw {{variable}} text is left as-is in the sent email.

{{year}} is special-cased: it's auto-filled with the current year whenever you don't supply a year value or default yourself — handy for a footer copyright line.

Any {{variable}} with no data value and no default stays as raw text in the sent email. Make sure your sending code passes all required variables, or set defaults for optional ones.

Sending Emails

Send emails from your published app pages using the SDK, or from backend functions using ctx.email.

From Published App Pages (SDK)

import { email } from '../api/sdk';

// Using a template
await email.send({
  to: "user@example.com",
  template: "welcome",
  data: { user_name: "John", login_url: "https://..." }
});

// Or send raw HTML (no template)
await email.send({
  to: "user@example.com",
  subject: "Order Confirmed",
  html: "<h1>Thanks!</h1><p>Your order is confirmed.</p>"
});
email.send() Parameters
ParamTypeRequiredDescription
tostring | string[]YesRecipient(s), max 50
subjectstringYes*Email subject (*optional if using template)
htmlstringYes*HTML body (*optional if using template)
textstringNoPlain-text fallback
replyTostringNoReply-to address
templatestringNoTemplate slug from Email tab
dataRecord<string, string>NoValues for {{variable}} placeholders
Response
{
  success: true,
  messageId: "abc-123",
  remaining: 95  // emails left this month
}

Sending from Backend Functions

Backend functions use ctx.email.send() with the same API. This is the recommended approach for production apps since you can query data from tables before sending.

Backend function example
// Backend function: sendWelcomeEmail
const user = await ctx.users.getByEmail(params.email);

await ctx.email.send({
  to: params.email,
  template: "welcome",
  data: {
    user_name: user?.name || "there",
    app_name: "MyApp",
    login_url: "https://myapp.com/login"
  }
});

return { sent: true };
With table data
// Backend function: sendOrderConfirmation
const order = await ctx.tables.getRow("Orders", params.orderId);
const user = await ctx.users.getById(order.createdBy);

await ctx.email.send({
  to: user.email,
  template: "order-confirmation",
  data: {
    user_name: user.name,
    order_id: order.id,
    item: order.Item,
    total: String(order.Total),
    tracking_url: `https://myapp.com/orders/${order.id}`
  }
});

return { sent: true };

Tip: Use backend functions for production email sending

Backend functions let you query tables, validate data, and handle errors before sending. The SDK's email.send() from pages works too, but backend functions give you more control.

Quotas & Limits

Each plan includes a monthly email quota. Usage resets on the 1st of each month.

PlanMonthly LimitRate Limit
Free1005/min
Lite1,00010/min
Starter2,50010/min
Builder6,00030/min
Pro13,00030/min
Elite25,00060/min
  • Each recipient in a multi-recipient send counts as 1 email
  • Rate limits are per minute, per account
  • Emails are sent from noreply@serenitiesai.com
  • Check remaining quota in the Email tab toolbar or via email.send() response

Email HTML Best Practices

Email clients have limited CSS support. Follow these guidelines for emails that render correctly everywhere:

Do

  • Use inline CSS styles
  • Use table-based layouts
  • Keep max width to 600px
  • Include a plain-text fallback
  • Test with the Send Test button
  • Use web-safe fonts

Don't

  • Use external CSS stylesheets
  • Use flexbox or grid layouts
  • Use JavaScript in emails
  • Use background images (unreliable)
  • Rely on media queries alone
  • Use custom web fonts
Recommended template structure
<table width="100%" cellpadding="0" cellspacing="0" style="background:#f5f5f5;">
  <tr>
    <td align="center" style="padding:40px 20px;">
      <table width="600" cellpadding="0" cellspacing="0"
             style="background:#ffffff; border-radius:8px;">
        <tr>
          <td style="padding:32px;">
            <h1 style="margin:0 0 16px; font-size:24px; color:#111;">
              Hi {{user_name}},
            </h1>
            <p style="margin:0 0 24px; font-size:16px; color:#555; line-height:1.6;">
              Welcome to our app! We're excited to have you.
            </p>
            <a href="{{login_url}}"
               style="display:inline-block; padding:12px 24px;
                      background:#4f46e5; color:#fff; text-decoration:none;
                      border-radius:6px; font-weight:600;">
              Get Started
            </a>
          </td>
        </tr>
      </table>
    </td>
  </tr>
</table>

MCP Tools for AI

AI assistants connected via MCP can manage email templates using these tools:

ToolDescription
listEmailTemplatesList all templates in a project
getEmailTemplateGet full template with HTML content
readEmailTemplateRead content with line numbers (for editing)
createEmailTemplateCreate a new template with name, slug, subject
updateEmailTemplateUpdate content, subject, variables
editEmailTemplatePartial text replacement (preferred for edits)
searchEmailTemplateSearch content for patterns
deleteEmailTemplateDelete a template
getEmailUsageCheck sending quota and usage

Testing Emails

Use the Send Test button in the Email tab toolbar to send a test email. You can enter test values for each variable to see how the final email looks in a real inbox.

Send Test saves your current edits before sending, so you always test the latest version of the template.