← Back to Services

6.3 Email Templates and Tenant Mail

KMP stores editable email templates in each tenant database and sends them through the mail transport selected for the active tenant. Template identity, rendering, and transport configuration are separate responsibilities.

Template model

EmailTemplatesTable uses a unique lowercase hyphenated slug as the stable workflow-native identity. A template includes subject, Markdown/HTML content, text content, variable metadata/schema, and is_active. At least one body format is required.

EmailTemplateResolverService::resolveBySlug() returns one active template or throws EmailTemplateNotFoundException. There is no current MailerDiscoveryService; older documentation referring to it is obsolete.

Rendering

EmailTemplateRendererService supports:

The HTML pipeline escapes substituted values before Markdown conversion. Template HTML itself is administrator-authored trusted content, so editing templates remains a privileged action. Do not add executable PHP, arbitrary expression evaluation, or secrets to template values.

TemplateAwareMailerTrait renders a preloaded active database template and falls back to the mailer’s file template when no active database template is available. Workflow Core.SendEmail accepts a stable slug (and retains legacy numeric-ID compatibility); new definitions should use slugs.

Known implementation gap (2026-08-28): EmailTemplateRendererService and TemplateAwareMailerTrait currently write an html_preview containing the first 200 rendered body characters at debug level. This conflicts with the no-body logging rule below and should be removed before application logs are treated as content-safe.

Tenant transport

TenantMailConfigurator temporarily overlays the default Cake mail transport/profile while a tenant context is active and returns a restore callback. Supported tenant modes are default, disabled/debug, SMTP, Azure Communication Services, SendGrid, and Resend. Credentials are secret references resolved by the configured secret store, not literal values stored in workflow definitions or logs.

The tenant can override the default From address/name; an explicit mailer setFrom() still wins. Unknown modes fall back to the platform default with a warning. Tenant context cleanup must restore the prior transport/profile even after failure.

Authoring contract

  1. Define a stable slug and variable schema.
  2. Prepare all domain variables in a service/workflow action; pre-format dates when required by the mailer contract.
  3. Preview both HTML and text with representative values.
  4. Resolve/send under the correct tenant context.
  5. Never log message bodies, credentials, or sensitive recipient data.

Keep business branching in the workflow or variable-preparation service; use the template conditional DSL only for presentation-level optional sections.

Verification

Test slug uniqueness/active resolution, missing-template failure, required/mistyped variables, conditional expressions, escaping, Markdown, multipart fallback, timezone formatting, preview authorization, tenant-specific From/transport selection, secret-resolution failure, restore callbacks, and cross-tenant template isolation.