Docs

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 screen: a Yayaw Table full-page table for searching, filtering, creating, activating, deactivating, deleting, and opening templates (see The email templates list). Each row opens the dedicated React Email editor at /dashboard/content/email-templates/[slug].

Related docs:

The email templates list

/dashboard/content/email-templates (route permission email-template:list) renders the screen dashboard.content.email-templates. The templates are the platform's, so it is a global screen: one copy for the whole platform, which only Super Admins customize (see Dashboard). Its default is the full-page table on the system:email-templates source, the templates edited last first, 20 a page.

The table lists every template to whoever holds email-template:list globally, whatever organization is active, and an organization's grant never opens it. Whoever also holds email-template:manage globally (Super Admins) gets:

  • New template: the create form (name, slug made from the name when left empty, category), then the new template's editor;

  • a row's deletion, and for a selection Delete, Activate and Deactivate, each confirmed; activating or deactivating changes only the templates not already so.

The server checks every write again (email-template:create, delete or update, globally).

The table keeps the list's columns (name, slug, category, status, variables, updated), its saved views and favorites (email-templates-catalog-v1), and its work queues after the screen's own view: Active templates, Inactive templates and Transactional email. It offers Table, List and a Kanban by category, whose cards cannot be dragged: a template's category is changed in its editor.

  • Search reads the name, slug and category, and every language's subject and description. It never reads a template's HTML, plain text or editor document.

  • Paging: at most 100 templates a page (10, 20 or 50 offered).

  • Order: names sort as people read them ("Welcome 2" before "Welcome 10"), in the database; where two names differ only by case or accents, the lowercase, unaccented one comes first.

  • URL parameters: the table's parameters are keyed by its source (system:email-templates-…), and view=<id> opens a saved view or a work queue.

An assistant preparing a draft of this screen reads the source's columns only, never a row's id or whether the viewer may change it; assistants list and edit templates with the tools below.

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 html and plain text exports used by runtime sending

  • editor_document, the React Email Editor TipTap JSON document

  • default_locale, locales, and localized_content for inline translated variants

  • variables, the template-specific {{variable}} contract

  • sample_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_list

  • yayaw_email_template_get

  • yayaw_email_template_save

  • yayaw_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-confirmation

  • billing-subscription-updated

  • billing-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 and the yayaw_email_templates_sync_system control-plane tool 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 check

For billing email variables, also run:

bun test src/lib/server/services/billing/billing-email-notifications.test.ts