Communication Templates Module
Manages Handlebars-based communication templates used across email, SMS, WhatsApp, Messenger, and PDF channels.
Domain Concepts
- Template: A Handlebars-based document with a
bodyTemplate, optionalsubjectTemplate, and a list ofavailableVariablesfor rendering. - System template: A template seeded via migrations (
isSystem = true). Cannot be deleted via API. - Global template: A template with
businessId = null, available to all businesses. - Business template: Scoped to a specific business. When querying by
businessId, global templates are also included.
Architecture
Follows hexagonal architecture:
communication-templates/
domain/ Port: ICommunicationTemplatesRepository
application/ Service: CommunicationTemplatesService (use cases + Handlebars rendering)
infrastructure/ Adapter: CommunicationTemplatesRepository (Kysely + PostgreSQL)
interfaces/ Controller + DTOs + pagination query
utils/ Handlebars helpers (currency, date, uppercase, lowercase, ifEquals, ifGreater)
The service is exported from the module and consumed by CommunicationsModule (event handlers, template renderer).
Database
Table: communication_template
| Column | Type | Description |
|---|---|---|
| id | UUID | Primary key |
| businessId | UUID / null | null = global template |
| name | string | Display name |
| code | string | Unique identifier (scoped by business + channel) |
| channel | enum | email, sms, whatsapp, messenger, pdf |
| type | enum | invoice, sale, user_invitation, cash_register_session, etc. |
| subjectTemplate | string / null | Handlebars subject (email/messenger) |
| bodyTemplate | string | Handlebars body |
| availableVariables | jsonb | Array of variable names |
| providerTemplateId | string / null | External provider ID (e.g., Twilio WhatsApp SID) |
| isSystem | boolean | System templates cannot be deleted |
| isActive | boolean | Active flag |
| tags | jsonb | Metadata tags |
Unique constraint: (businessId, code, channel)
API Endpoints
| Method | Route | Description |
|---|---|---|
| POST | /communication-templates | Create a template |
| GET | /communication-templates | List templates (paginated, filterable) |
| GET | /communication-templates/available/options | Get active templates by entity type / channel |
| GET | /communication-templates/:id | Get template by ID |
| POST | /communication-templates/:id/preview | Render template with variables |
| PATCH | /communication-templates/:id | Update a template |
| DELETE | /communication-templates/:id | Delete a template (403 if system) |
Example: Create template
POST /communication-templates
{
"businessId": "uuid",
"createdBy": "uuid",
"name": "Invoice Email",
"code": "invoice_email_standard",
"channel": "email",
"type": "invoice",
"subjectTemplate": "Invoice #{{invoiceNumber}} from {{businessName}}",
"bodyTemplate": "<h1>Invoice</h1><p>Amount: {{currency amount currencyCode}}</p>",
"availableVariables": ["invoiceNumber", "businessName", "amount", "currencyCode"]
}
Example: Preview template
POST /communication-templates/:id/preview
{
"variables": {
"invoiceNumber": "INV-1234",
"businessName": "ACME Corp",
"amount": 1500,
"currencyCode": "USD"
}
}
Response:
{
"subject": "Invoice #INV-1234 from ACME Corp",
"body": "<h1>Invoice</h1><p>Amount: $1,500.00</p>"
}
Handlebars Helpers
| Helper | Usage | Example Output |
|---|---|---|
| currency | {{currency 1500 "USD"}} | $1,500.00 |
| date | {{date "2025-01-01" "long"}} | January 1, 2025 |
| uppercase | {{uppercase "hello"}} | HELLO |
| lowercase | {{lowercase "HELLO"}} | hello |
| ifEquals | {{#ifEquals status "paid"}}Paid{{/ifEquals}} | Paid |
| ifGreater | {{#ifGreater amount 0}}Positive{{/ifGreater}} | Positive |
Design Decisions
- Template code as lookup key: Templates are resolved by
code+businessId(with fallback to global). This allows business-specific overrides of global templates. - Entity type matching via code prefix:
getAvailableOptionsconverts camelCase entity types to snake_case and matches against template code prefixes (e.g.,debitNotematchesdebit_note_email_standard). - Handlebars over custom syntax: Handlebars provides block helpers, conditionals, and extensibility via custom helpers.
- System template protection: Seeded templates are marked
isSystem = trueand cannot be deleted via the API, only updated.