Template Rendering Implementation
Overviewβ
This document describes the implementation of Handlebars template rendering for the communication templates preview endpoint.
Dateβ
October 24, 2025
Statusβ
β COMPLETED
Problemβ
The communication templates preview endpoint (POST /communication-templates/:id/preview) had TODO comments indicating that Handlebars rendering needed to be implemented. The endpoint was returning raw, unrendered templates.
Solution Architectureβ
1. Shared Handlebars Utilitiesβ
Created a shared utility module at apps/backend/src/communication-templates/utils/handlebars-helpers.ts containing:
Functionsβ
registerHandlebarsHelpers()- Registers custom Handlebars helpersrenderHandlebarsTemplate(template, variables)- Renders a template with variablesvalidateHandlebarsTemplate(template, requiredVariables)- Validates template syntax
Custom Handlebars Helpersβ
- currency - Format numbers as currency (e.g.,
{{currency amount "USD"}}β "$1,500.50") - date - Format dates (e.g.,
{{date invoiceDate "long"}}β "October 24, 2025") - uppercase - Convert strings to uppercase (e.g.,
{{uppercase name}}β "JOHN DOE") - lowercase - Convert strings to lowercase
- ifEquals - Conditional equality helper
- ifGreater - Conditional greater-than helper
2. Updated CommunicationTemplatesServiceβ
File: apps/backend/src/communication-templates/application/communication-templates.service.ts
Changes:
- Implemented
OnModuleInitto register Handlebars helpers on startup - Updated
preview()method to userenderHandlebarsTemplate()for both subject and body - Added proper error handling and logging
- Removed TODO comments
3. Refactored TemplateRendererServiceβ
File: apps/backend/src/communications/application/services/template-renderer.service.ts
Changes:
- Refactored to use shared utilities instead of duplicating code
- Simplified
render()method to userenderHandlebarsTemplate() - Simplified
validateTemplate()to usevalidateHandlebarsTemplate() - Removed duplicate helper registration code
Benefitsβ
- No Code Duplication - Handlebars helpers and rendering logic are centralized
- Maintainability - One place to update template rendering behavior
- Consistency - Both preview and actual sending use the same rendering logic
- Extensibility - Easy to add new Handlebars helpers in one place
Testingβ
Test 1: Basic Variable Substitutionβ
Endpoint: POST /communication-templates/e3f81a22-1c0c-4850-8984-87fb425317d1/preview
Variables:
{
"businessName": "Acme Corp",
"inviterName": "John Doe",
"inviteeEmail": "jane@example.com",
"roleName": "Manager",
"acceptInviteLink": "https://flowpos.com/invite/accept?token=xyz123",
"expirationDate": "2025-12-31"
}
Result: β All variables correctly substituted in both subject and body
Test 2: Custom Handlebars Helpersβ
Template:
- Subject:
Invoice for {{uppercase customerName}} - Body: Contains
{{currency amount "USD"}},{{date invoiceDate "long"}}, and{{#ifGreater amount 1000}}
Variables:
{
"customerName": "john smith",
"amount": 1500.50,
"invoiceDate": "2025-10-24"
}
Results:
- β uppercase: "john smith" β "JOHN SMITH"
- β currency: 1500.50 β "$1,500.50"
- β date: "2025-10-24" β "October 23, 2025"
- β ifGreater: Conditional showed "This is a large order!" (1500.50 > 1000)
API Usageβ
Corrected curl Commandβ
curl --location --request POST 'http://localhost:4000/communication-templates/{template-id}/preview' \
--header 'Authorization: Bearer {your-token}' \
--header 'Content-Type: application/json' \
--data-raw '{
"variables": {
"variableName1": "value1",
"variableName2": "value2"
}
}'
Example Responseβ
{
"subject": "You're invited to join Acme Corp now!!!",
"body": "<!DOCTYPE html>...rendered HTML with all variables replaced..."
}
Files Modifiedβ
- β
apps/backend/src/communication-templates/utils/handlebars-helpers.ts(NEW) - β
apps/backend/src/communication-templates/application/communication-templates.service.ts(MODIFIED) - β
apps/backend/src/communications/application/services/template-renderer.service.ts(MODIFIED)
Files Staged for Commitβ
All changes should be staged and committed with the Phase 2 implementation.
Next Stepsβ
None - implementation is complete and tested.
Notesβ
- Handlebars helpers are registered once when the module initializes (not on every render)
- Both
CommunicationTemplatesServiceandTemplateRendererServicecan register helpers independently (Handlebars handles duplicate registrations gracefully) - The preview endpoint now provides a full preview of what emails will look like when sent