Transactional Email Templates
Managing React Email transactional templates from the content dashboard.
Yayaw manages transactional email templates from
/dashboard/content/email-templates. That route is a Yayaw Table catalog for
searching, filtering, creating, deleting, and opening templates. Each row opens
the dedicated React Email editor at
/dashboard/content/email-templates/[slug].
Related docs:
- CMS Overview for how templates share variables with the wider content system
- CMS Data Models for global CMS variables
- Billing Products for billing purchase and subscription events
CMS Role
Transactional email templates are content-managed communication surfaces. They share authoring, localization, variables, and control-plane patterns with the CMS, but they do not render through the public page runtime. Runtime senders load active templates, apply transactional variables, resolve CMS variables, and fall back to React Email component defaults when no database template exists.
Use email templates for purchase confirmations, subscription updates, invitations, and other event-driven messages. Use pages and sections for public or organization-member web content.
Data Model
Each template stores:
slug,name,subject,preview_text,description,category- rendered
htmland plaintextexports used by runtime sending editor_document, the React Email Editor TipTap JSON documentdefault_locale,locales, andlocalized_contentfor inline translated variantsvariables, the template-specific{{variable}}contractsample_variables, used by the external preview and test sends- legacy
design, kept only so older Unlayer rows can migrate safely
Templates are platform-wide in v1. Organization-specific overrides should be added later as an explicit lookup layer rather than overloading the current global table.
The production control plane exposes template operations for trusted MCP clients:
yayaw_email_templates_listyayaw_email_template_getyayaw_email_template_saveyayaw_email_templates_sync_system
Writes require control-plane:write or control-plane:admin, Yayaw
email-template:update/manage authorization, and a reason.
Localization
Email templates follow the same inline localization model as CMS pages. The
editor toolbar lets authors switch between enabled locales, and each locale
stores its own subject, preview text, description, HTML, text export, and editor
document inside localized_content.
Saving a locale updates that locale only. The default locale also mirrors the legacy top-level columns so existing runtime lookups and older rows continue to work during migration.
The AI panel includes an enabled-by-default locale sync switch. When a template
is saved, the currently open locale is used as the source and the same copy,
structure, styling, and block-order changes are applied to the other enabled
locales. AI-generated sync updates write localized subject, previewText,
description, html, and text values; target-locale editorDocument values
are cleared so the editor can rebuild from the synchronized HTML when that
locale is opened.
The editor canvas focuses on design only. The toolbar opens the rendered email preview in a separate browser tab after exporting the latest editor state and applying sample transactional variables plus global CMS variables.
Variables
Email templates support two variable families:
- Template variables use
{{name}}or nested paths such as{{invitation.inviteLink}}. - CMS variables use the global CMS token format, for example
{site.name}or{data.global.header-menu.main.brand_label}.
The email editor exposes both families. CMS variables are resolved through the same published global-data and site-variable catalog used by pages and sections, so content authors can reuse the same global values across the CMS.
Runtime Behavior
When sending an email, Yayaw first tries the active database template for the configured slug and requested locale. If that locale is missing, rendering falls back to the template default locale and then to the legacy top-level columns. If no active template exists, auth emails keep using their React Email component fallback.
Billing emails are also system templates. They are sent from Stripe webhook handlers and are intentionally non-blocking: if Resend is not configured or the template is missing, the webhook logs the skip and continues processing the purchase or subscription update.
Built-in billing template slugs:
billing-purchase-confirmationbilling-subscription-updatedbilling-invoice-available
Billing templates can use nested variables such as:
{{billing.productName}}{{billing.amountFormatted}}{{billing.billingInterval}}{{billing.billingReason}}{{billing.optionsSummary}}{{billing.lineItemsSummary}}{{billing.discountSummary}}{{billing.periodStart}}and{{billing.periodEnd}}{{billing.invoiceId}},{{billing.invoiceNumber}},{{billing.invoiceUrl}}, and{{billing.invoicePdfUrl}}{{billing.manageUrl}}{{billing.accessUrl}}{{billing.deliverablesSummary}}{{organization.name}}{{user.email}}
billing-invoice-available is sent from Stripe invoice.paid events. The
handler is idempotent through billing_webhook_events and resolves the
organization from invoice metadata, subscription metadata, then local
subscription/customer rows. One-time Checkout enables Stripe invoice creation so
paid lifetime purchases can also generate this template.
Use bun run docs:llm:generate or the dashboard Sync system templates action
after adding billing template metadata so the editor variables and MCP
control-plane sync stay aligned.
Stored HTML is treated as the fast runtime representation. Save the template
after editing so html, text, and editor_document stay synchronized.
Local Workflow
After changing template contracts, system template metadata, schema, or generated assistant docs, run:
bun run generate:actions
bun run docs:generate
bun run docs:check-translations
bun run docs:llm:generate
bun run docs:llm:check
bunx tsc --noEmit
bun run checkFor billing email variables, also run:
bun test src/lib/server/services/billing/billing-email-notifications.test.ts