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.
On this page
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
- Click the template dropdown in the toolbar and select New Template
- Enter a name (e.g., "Welcome Email") and a slug (e.g., "welcome")
- The slug is how you reference the template in code:
template: "welcome" - 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.
<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>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>"
});| Param | Type | Required | Description |
|---|---|---|---|
| to | string | string[] | Yes | Recipient(s), max 50 |
| subject | string | Yes* | Email subject (*optional if using template) |
| html | string | Yes* | HTML body (*optional if using template) |
| text | string | No | Plain-text fallback |
| replyTo | string | No | Reply-to address |
| template | string | No | Template slug from Email tab |
| data | Record<string, string> | No | Values for {{variable}} placeholders |
{
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: 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 };// 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.
| Plan | Monthly Limit | Rate Limit |
|---|---|---|
| Free | 100 | 5/min |
| Lite | 1,000 | 10/min |
| Starter | 2,500 | 10/min |
| Builder | 6,000 | 30/min |
| Pro | 13,000 | 30/min |
| Elite | 25,000 | 60/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
<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:
| Tool | Description |
|---|---|
| listEmailTemplates | List all templates in a project |
| getEmailTemplate | Get full template with HTML content |
| readEmailTemplate | Read content with line numbers (for editing) |
| createEmailTemplate | Create a new template with name, slug, subject |
| updateEmailTemplate | Update content, subject, variables |
| editEmailTemplate | Partial text replacement (preferred for edits) |
| searchEmailTemplate | Search content for patterns |
| deleteEmailTemplate | Delete a template |
| getEmailUsage | Check 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.