Skip to main content

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, optional subjectTemplate, and a list of availableVariables for 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

ColumnTypeDescription
idUUIDPrimary key
businessIdUUID / nullnull = global template
namestringDisplay name
codestringUnique identifier (scoped by business + channel)
channelenumemail, sms, whatsapp, messenger, pdf
typeenuminvoice, sale, user_invitation, cash_register_session, etc.
subjectTemplatestring / nullHandlebars subject (email/messenger)
bodyTemplatestringHandlebars body
availableVariablesjsonbArray of variable names
providerTemplateIdstring / nullExternal provider ID (e.g., Twilio WhatsApp SID)
isSystembooleanSystem templates cannot be deleted
isActivebooleanActive flag
tagsjsonbMetadata tags

Unique constraint: (businessId, code, channel)

API Endpoints

MethodRouteDescription
POST/communication-templatesCreate a template
GET/communication-templatesList templates (paginated, filterable)
GET/communication-templates/available/optionsGet active templates by entity type / channel
GET/communication-templates/:idGet template by ID
POST/communication-templates/:id/previewRender template with variables
PATCH/communication-templates/:idUpdate a template
DELETE/communication-templates/:idDelete 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

HelperUsageExample 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

  1. Template code as lookup key: Templates are resolved by code + businessId (with fallback to global). This allows business-specific overrides of global templates.
  2. Entity type matching via code prefix: getAvailableOptions converts camelCase entity types to snake_case and matches against template code prefixes (e.g., debitNote matches debit_note_email_standard).
  3. Handlebars over custom syntax: Handlebars provides block helpers, conditionals, and extensibility via custom helpers.
  4. System template protection: Seeded templates are marked isSystem = true and cannot be deleted via the API, only updated.