Skip to the content.
← Back to Services ← Back to Table of Contents

6.3 Email Template Management System

Last Updated: February 11, 2026,
Status: Active
Controllers: EmailTemplatesController
Services: MailerDiscoveryService, EmailTemplateRendererService

Overview

The KMP Email Template Management System provides a centralized, database-driven approach to managing email templates. It allows administrators to edit email content through a user-friendly web interface without modifying code files, while maintaining full backward compatibility with file-based templates.

Key Features

Architecture

Database Schema

Table: email_templates

Column Type Description
id INT Primary key
mailer_class VARCHAR(255) Fully qualified class name (e.g., App\Mailer\KMPMailer)
action_method VARCHAR(100) Method name (e.g., resetPassword)
subject_template VARCHAR(500) Email subject with variable placeholders
html_template TEXT HTML version of the email (stored as Markdown)
text_template TEXT Plain text version of the email
available_vars JSON Array of available variables for this template
is_active BOOLEAN Whether to use this template instead of file-based template
created DATETIME Creation timestamp
modified DATETIME Last modification timestamp

Unique Constraint: (mailer_class, action_method)

Core Components

1. MailerDiscoveryService

Location: src/Services/MailerDiscoveryService.php

Discovers all Mailer classes in core app and plugins using PHP reflection to:

Key Methods:

public function discoverMailers(): array
public function discoverMailerMethods(string $mailerClass): array
public function extractAvailableVariables(string $mailerClass, string $method): array

2. EmailTemplateRendererService

Location: src/Services/EmailTemplateRendererService.php

Renders templates by replacing variable placeholders with actual values:

Key Methods:

public function renderTemplate(EmailTemplate $template, array $variables): array
public function renderPreview(EmailTemplate $template): array

3. TemplateAwareMailerTrait

Location: src/Mailer/TemplateAwareMailerTrait.php

The trait that integrates with CakePHP’s Mailer pipeline:

use App\Mailer\TemplateAwareMailerTrait;

class KMPMailer extends Mailer
{
    use TemplateAwareMailerTrait;
    // ... rest of class
}

How it works:

  1. Intercepts the render() method before email generation
  2. Checks database for active template matching the mailer class and action
  3. If found, renders email from database template with provided variables
  4. If not found or inactive, falls back to file-based templates in templates/email/
  5. Logs template usage for debugging

Template Precedence:

  1. Active database template - Used if is_active = true
  2. File-based template - Used if no active database template exists
  3. Falls back to file-based templates in:
    • templates/email/html/{actionMethod}.php
    • templates/email/text/{actionMethod}.php
    • plugins/{Plugin}/templates/email/html/{actionMethod}.php
    • plugins/{Plugin}/templates/email/text/{actionMethod}.php

4. Model Layer

EmailTemplatesTable (src/Model/Table/EmailTemplatesTable.php)

EmailTemplate Entity (src/Model/Entity/EmailTemplate.php)

5. EmailTemplatesController

Location: src/Controller/EmailTemplatesController.php

Actions:

6. Frontend Components

Stimulus Controller: assets/js/controllers/email-template-editor-controller.js

Features:

Views: templates/EmailTemplates/

User Guide

For Administrators

Discovering Available Email Templates

  1. Navigate to Admin > Email Templates > Discover
  2. View all discovered mailer classes and their methods
  3. See which methods have templates and which don’t
  4. Click “Create Template” for methods without templates

Creating a New Template

Method 1: From Discovery Page

  1. Go to /email-templates/discover
  2. Find the mailer method you want to template
  3. Click “Create Template”
  4. The form will be pre-populated with:
    • Mailer class and method
    • Available variables
    • Existing file-based template content (if any)
    • Default subject

Method 2: Manual Creation

  1. Go to /email-templates/add
  2. Select mailer class and action method from dropdowns
  3. Available variables will be populated automatically

Editing the Template:

  1. Enter subject template using variable placeholders
  2. Edit plain text version using EasyMDE markdown editor
  3. Edit HTML version using EasyMDE markdown editor
  4. Click variable buttons to insert `` placeholders
  5. Check “Active” to use this template instead of file-based template
  6. Click “Save”

Using the Editor

Available Features:

Icon/Button Function Shortcut
B Bold text Ctrl+B
I Italic text Ctrl+I
H Heading -
Quote -
Unordered list -
1. Ordered list -
🔗 Insert link -
👁 Toggle preview -
Side-by-side -
Fullscreen F11
{} Insert variable -
? Guide -

Variable Insertion:

Example Template:

Plain Text:

Hello ,

Someone has requested a password reset for your account ().

If this was you, please click the link below:


If you did not request this, you can safely ignore this email.


HTML Template (Markdown):

Someone has requested a password reset for the **AMP** account associated with ****.

If this was you, please click the link below to reset your password:

[Reset My Password]()

If you did not request this, you can safely ignore this email.

---


Synchronizing Templates

The “Sync” feature creates database records for all discovered mailer methods:

  1. Click “Sync Templates” on the index page
  2. System creates inactive templates for all methods without templates
  3. Templates are created with:
    • Content from existing file-based templates (if any)
    • Default subject from code
    • Available variables detected from code
    • Inactive status (won’t be used until you activate them)
  4. Review and activate templates individually after testing

Previewing Templates

For Developers

Adding New Mailer Methods

When you create a new mailer method:

public function welcomeEmail(string $to, string $userName, string $activationUrl): void
{
    $this->setTo($to)
        ->setFrom(StaticHelpers::getAppSetting('Email.SystemEmailFromAddress'))
        ->setSubject('Welcome to KMP!')
        ->setViewVars([
            'userName' => $userName,
            'activationUrl' => $activationUrl,
            'siteTitle' => StaticHelpers::getAppSetting('KMP.LongSiteTitle'),
        ]);
}

The system will:

  1. Auto-discover this method via reflection
  2. Extract available variables: userName, activationUrl, siteTitle
  3. Extract subject: “Welcome to KMP!”
  4. Make it available for template creation in the admin interface

Using the Trait

All mailer classes should use the trait:

<?php
declare(strict_types=1);

namespace App\Mailer;

use App\Mailer\TemplateAwareMailerTrait;
use Cake\Mailer\Mailer;

class MyMailer extends Mailer
{
    use TemplateAwareMailerTrait;
    
    // Your mailer methods...
}

Variable Syntax in Templates

Templates support two variable syntaxes:

Variables are replaced when the email is rendered. Both syntaxes work identically.

Conditional Logic in Templates

Database-stored templates support conditional blocks using a mustache-like `` syntax. This lets templates show or hide content based on variable values without any PHP execution — the syntax is parsed as a safe DSL using regex and string comparison.

Supported Syntax:

Syntax Description
... Show content when variable equals value
... Show content when variable does NOT equal value
... OR — show content when either condition is true
... AND — show content when both conditions are true

Processing Order: Conditionals are processed BEFORE variable substitution by EmailTemplateRendererService::processConditionals(). This means `` placeholders inside conditional blocks work normally.

Important Notes:

Example — Authorization Notification with Status-Based Content:

Hello ,

Your authorization for  has been updated.


Congratulations! Your authorization has been approved and is now active.
Your authorization is valid from  to .



Your authorization has been revoked as of .
If you believe this is an error, please contact your group's officer.



If you have questions about this decision, please reach out to your local officer.



Auto-Conversion on Sync/Import:

When file-based templates containing PHP conditionals are imported via the Sync feature, the convertTemplateVariables() method in EmailTemplatesController automatically converts PHP conditional syntax to `` syntax:

File-Based PHP Syntax Converted Database Syntax
<?php if ($status == "Approved") : ?> ``
<?php endif; ?> ``
<?= $memberName ?> ``
<?= h($memberName) ?> ``

This conversion happens during sync so that administrators can edit the template using the clean `` syntax in the web interface.

Markdown Formatting Reference

Text Formatting:

Headings:

# Heading 1
## Heading 2
### Heading 3

Links:

[Link Text](http://example.com)
[Link with Variable]()

Lists:

- Unordered item 1
- Unordered item 2

1. Ordered item 1
2. Ordered item 2

Other:

---                  (horizontal line)
> Quoted text        (blockquote)

Paragraphs: Leave a blank line between paragraphs for proper spacing.

Authorization

The system uses EmailTemplatePolicy for authorization:

All actions require appropriate permissions assigned to user roles via the RBAC system.

Best Practices

Template Design

  1. Always provide both HTML and text versions
    • HTML for rich formatting
    • Text for email clients that don’t support HTML or for user preference
  2. Use descriptive subjects
    • Include variables to personalize: Welcome to
    • Keep subject lines under 60 characters when possible
  3. Keep templates focused
    • One template per mailer action
    • Don’t try to handle multiple scenarios in one template
  4. Test before activating
    • Use preview feature to check rendering
    • Test with real data if possible
    • Keep template inactive until verified
  5. Mobile-friendly
    • Keep layouts simple
    • Text should be readable on small screens
    • Avoid complex HTML structures

Variable Usage

  1. Use consistent naming
    • Match variable names to setViewVars() in code exactly (case-sensitive)
    • Use camelCase for consistency
  2. Document in code
    • Comment what variables are available
    • Include examples in docblocks
  3. Provide defaults
    • Handle missing variables gracefully in code
    • Use fallback values in setViewVars()

Migration Strategy

To migrate from file-based to database templates:

  1. Run Sync - Creates inactive templates with file content
  2. Review and Edit - Update content as needed in web interface
  3. Test - Preview templates, test with actual emails
  4. Activate - Enable templates one at a time
  5. Monitor - Check logs for template usage
  6. Archive Files - Keep file-based templates as backup

Technical Details

Variable Extraction

The system attempts to extract variables from mailer methods by:

  1. Reading the source file
  2. Finding setViewVars() calls using regular expressions
  3. Parsing the array structure
  4. Extracting variable names

The EmailTemplateRendererService::extractVariables() method also extracts variables from templates at render time:

This is done using regular expressions. While robust, it may miss:

Solution: Manually specify variables when creating templates if auto-detection misses any.

Performance

Logging

Template usage is logged at DEBUG level:

Log::debug('Email rendered from database template', [
    'mailer_class' => 'App\Mailer\KMPMailer',
    'template_id' => 5,
    'action' => 'resetPassword',
]);

Check logs at logs/debug.log or logs/error.log for template-related messages.

Troubleshooting

Template Not Being Used

Symptom: File-based template still being used instead of database template

Check:

  1. Is template active? (is_active = 1)
  2. Does mailer class use the trait?
  3. Are class and method names exact matches? (case-sensitive)
  4. Check logs for errors or template usage messages
  5. Clear application cache: bin/cake cache clear_all

Variables Not Replacing

Symptom: Variables like `` appear literally in sent emails

Check:

  1. Variable names match exactly (case-sensitive)
  2. Variables are set in setViewVars() in the mailer method
  3. Syntax is correct: `` with no spaces
  4. No typos in variable names
  5. Check logs for rendering errors

Discovery Not Finding Mailers

Symptom: Mailer classes don’t appear in discovery page

Check:

  1. Mailer class extends Cake\Mailer\Mailer
  2. Class is not abstract
  3. Methods are public (not private or protected)
  4. File is in correct location:
    • src/Mailer/ for app
    • plugins/{Plugin}/src/Mailer/ for plugins
  5. Class is properly namespaced

Editor Not Loading

Symptom: Markdown editor doesn’t appear or has errors

Check:

  1. Ensure assets are compiled: npm run dev or npm run production
  2. Check browser console for JavaScript errors
  3. Verify EasyMDE is in package.json dependencies
  4. Clear browser cache
  5. Check Content Security Policy settings don’t block editor scripts

Preview Not Showing

Symptom: Preview doesn’t display when editing template

Check:

  1. Click the eye icon (👁) to enable preview mode
  2. Try side-by-side mode (⇆) to see both views
  3. Check for JavaScript errors in browser console
  4. Verify template has content in the editor

Common Variables Reference

Depending on the email type, you may have access to:

Variable Description Example
`` Recipient’s email address user@example.com
`` Member’s full name John Smith
`` Member’s SCA name Lord John of Example
`` User’s username johnsmith
`` Link to reset password https://kmp.example.com/...
`` Standard admin signature Thank you\nWebminister
`` Site title Kingdom Management Portal
`` Name of a gathering/event Winter Festival
`` Name of an activity Heavy Combat
`` Name of a branch Kingdom of Example

Note: Available variables vary by email type. Always check the “Available Variables” section above each editor when creating/editing templates.

Future Enhancements

Potential improvements for future versions:


← Back to Services ← Back to Table of Contents