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