| ← 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
- Database-Stored Templates: Email templates are stored in the database, making them easy to update without deploying code changes
- WYSIWYG Markdown Editor: Uses EasyMDE for intuitive template editing with live preview
- Variable Insertion: Click-to-insert buttons for available variables in each template
- Auto-Discovery: Automatically discovers all Mailer classes and their methods using PHP reflection
- Dual Format Support: Create both HTML and plain text versions of emails
- Template Fallback: Falls back to file-based templates if database template is inactive
- Conditional Logic: `` blocks allow templates to show or hide content based on variable values — no PHP execution, safe for admin editing
- Authorization Integration: Full RBAC support through EmailTemplatePolicy
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:
- Find all classes extending
Cake\Mailer\Mailer - Analyze public methods and extract parameters
- Extract view variables set in the method
- Extract default subject lines from code
- Provide information for template creation and editing
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:
- Processes `` conditional blocks before variable substitution
- Supports `` and
${variableName}syntax - Converts Markdown to HTML for HTML templates
- Generates previews with sample data
- Handles missing variables gracefully
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:
- Intercepts the
render()method before email generation - Checks database for active template matching the mailer class and action
- If found, renders email from database template with provided variables
- If not found or inactive, falls back to file-based templates in
templates/email/ - Logs template usage for debugging
Template Precedence:
- Active database template - Used if
is_active = true - File-based template - Used if no active database template exists
- Falls back to file-based templates in:
templates/email/html/{actionMethod}.phptemplates/email/text/{actionMethod}.phpplugins/{Plugin}/templates/email/html/{actionMethod}.phpplugins/{Plugin}/templates/email/text/{actionMethod}.php
4. Model Layer
EmailTemplatesTable (src/Model/Table/EmailTemplatesTable.php)
- Validation rules for templates
- Unique constraint on (mailer_class, action_method)
- Helper methods for finding templates
- Business logic for template management
EmailTemplate Entity (src/Model/Entity/EmailTemplate.php)
- JSON encoding/decoding for available_vars
- Virtual field for display name
- Accessible properties configuration
5. EmailTemplatesController
Location: src/Controller/EmailTemplatesController.php
Actions:
index(): List all templates with filteringview($id): View template details and previewadd(): Create new templateedit($id): Edit existing templatedelete($id): Delete templatediscover(): Show all discoverable mailer methodssync(): Auto-create templates for all discovered methodspreview($id): Generate template preview
6. Frontend Components
Stimulus Controller: assets/js/controllers/email-template-editor-controller.js
Features:
- Extends EasyMDE markdown editor
- Adds variable insertion buttons
- Highlights variables in preview mode
- Provides toolbar for template editing
- Side-by-side editing and preview
- Fullscreen mode
Views: templates/EmailTemplates/
index.php: Template listing with filtersform.php: Add/edit form with dual editors (HTML + Text)view.php: Template details and previewdiscover.php: Mailer discovery interface
User Guide
For Administrators
Discovering Available Email Templates
- Navigate to Admin > Email Templates > Discover
- View all discovered mailer classes and their methods
- See which methods have templates and which don’t
- Click “Create Template” for methods without templates
Creating a New Template
Method 1: From Discovery Page
- Go to
/email-templates/discover - Find the mailer method you want to template
- Click “Create Template”
- 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
- Go to
/email-templates/add - Select mailer class and action method from dropdowns
- Available variables will be populated automatically
Editing the Template:
- Enter subject template using variable placeholders
- Edit plain text version using EasyMDE markdown editor
- Edit HTML version using EasyMDE markdown editor
- Click variable buttons to insert `` placeholders
- Check “Active” to use this template instead of file-based template
- 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:
- Click any variable button above the editor to insert at cursor
- Variables shown as ``
- Preview mode highlights variables in blue badges
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:
- Click “Sync Templates” on the index page
- System creates inactive templates for all methods without templates
- 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)
- Review and activate templates individually after testing
Previewing Templates
- View page shows preview with placeholder values
- Variables are shown as
[variableName] - Both HTML and text versions are displayed
- Preview uses sample data to show how the email will look
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:
- Auto-discover this method via reflection
- Extract available variables:
userName,activationUrl,siteTitle - Extract subject: “Welcome to KMP!”
- 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:
- `` - Primary syntax (recommended, shown in UI)
${variableName}- Alternative syntax (also supported)
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:
- No
eval()or PHP execution — this is a safe pattern language using regex - Variable names in conditions do not need a
$prefix (it is stripped automatically if present) - All comparisons are string-based
&&(AND) has higher precedence than||(OR), matching standard boolean logic- Unsupported expressions log a warning and evaluate to
false
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:
**bold**→ bold*italic*or_italic_→ italic`code`→code
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:
canIndex: View template listcanView: View template detailscanCreate: Create new templatescanUpdate/canEdit: Edit templatescanDelete: Delete templatescanDiscover: View mailer discovery pagecanSync: Synchronize templatescanPreview: Preview templates
All actions require appropriate permissions assigned to user roles via the RBAC system.
Best Practices
Template Design
- Always provide both HTML and text versions
- HTML for rich formatting
- Text for email clients that don’t support HTML or for user preference
- Use descriptive subjects
- Include variables to personalize:
Welcome to - Keep subject lines under 60 characters when possible
- Include variables to personalize:
- Keep templates focused
- One template per mailer action
- Don’t try to handle multiple scenarios in one template
- Test before activating
- Use preview feature to check rendering
- Test with real data if possible
- Keep template inactive until verified
- Mobile-friendly
- Keep layouts simple
- Text should be readable on small screens
- Avoid complex HTML structures
Variable Usage
- Use consistent naming
- Match variable names to
setViewVars()in code exactly (case-sensitive) - Use camelCase for consistency
- Match variable names to
- Document in code
- Comment what variables are available
- Include examples in docblocks
- Provide defaults
- Handle missing variables gracefully in code
- Use fallback values in
setViewVars()
Migration Strategy
To migrate from file-based to database templates:
- Run Sync - Creates inactive templates with file content
- Review and Edit - Update content as needed in web interface
- Test - Preview templates, test with actual emails
- Activate - Enable templates one at a time
- Monitor - Check logs for template usage
- Archive Files - Keep file-based templates as backup
Technical Details
Variable Extraction
The system attempts to extract variables from mailer methods by:
- Reading the source file
- Finding
setViewVars()calls using regular expressions - Parsing the array structure
- Extracting variable names
The EmailTemplateRendererService::extractVariables() method also extracts variables from templates at render time:
- Finds `` and
${variable}placeholders - Excludes
andcontrol tags from the variable list - Extracts variable names referenced in
conditions (e.g., `status` from)
This is done using regular expressions. While robust, it may miss:
- Variables set in loops
- Variables from complex expressions
- Variables set conditionally
Solution: Manually specify variables when creating templates if auto-detection misses any.
Performance
- Template lookups are cached at the query level
- No performance impact when using file-based templates
- Minimal overhead for database template rendering
- Consider caching frequently-used templates for high-volume emails
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:
- Is template active? (
is_active = 1) - Does mailer class use the trait?
- Are class and method names exact matches? (case-sensitive)
- Check logs for errors or template usage messages
- Clear application cache:
bin/cake cache clear_all
Variables Not Replacing
Symptom: Variables like `` appear literally in sent emails
Check:
- Variable names match exactly (case-sensitive)
- Variables are set in
setViewVars()in the mailer method - Syntax is correct: `` with no spaces
- No typos in variable names
- Check logs for rendering errors
Discovery Not Finding Mailers
Symptom: Mailer classes don’t appear in discovery page
Check:
- Mailer class extends
Cake\Mailer\Mailer - Class is not abstract
- Methods are public (not private or protected)
- File is in correct location:
src/Mailer/for appplugins/{Plugin}/src/Mailer/for plugins
- Class is properly namespaced
Editor Not Loading
Symptom: Markdown editor doesn’t appear or has errors
Check:
- Ensure assets are compiled:
npm run devornpm run production - Check browser console for JavaScript errors
- Verify EasyMDE is in package.json dependencies
- Clear browser cache
- Check Content Security Policy settings don’t block editor scripts
Preview Not Showing
Symptom: Preview doesn’t display when editing template
Check:
- Click the eye icon (👁) to enable preview mode
- Try side-by-side mode (⇆) to see both views
- Check for JavaScript errors in browser console
- 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:
- Template Versioning: Track changes and revert to previous versions
- A/B Testing: Test different template variations
- Template Inheritance: Shared snippets/headers/footers (note: `` conditionals address some use cases, but shared layout blocks are a separate concept)
- Rich HTML Editor: WYSIWYG option in addition to Markdown
- Email Testing: Send test emails from the interface
- Import/Export: Backup and restore templates
- Template Categories: Organize templates by category or tag
- Multi-language Support: Localized templates
- Template Scheduling: Schedule template changes for specific dates
- Usage Analytics: Track template performance and open rates
Related Documentation
- 6. Services - Overview of all KMP services
- 6.1 Email Service - Email sending and queueing
- 4.4 RBAC Security Architecture - Authorization system
- 7.3 Testing Infrastructure - Testing email functionality
| ← Back to Services | ← Back to Table of Contents |