Template versioning
Channel templates — WhatsApp HSMs, SMS templates with DLT registration, approved email designs — have an external lifecycle (Meta / TRAI / SMTP-provider approval) that doesn’t align with a typical software-release cadence. Active Reach handles this by versioning every template, keeping in-flight journeys on the version they entered on, and rolling new versions out only to new entrants.
Why versioning
Without versioning, editing a template breaks two things:
- In-flight journeys: a contact mid-sequence may have a wait-then-send pattern; the send arrives with the new template, which doesn’t match the trigger-time intent.
- External approval state: editing a WhatsApp HSM resets its Meta approval status — the template is unsendable until re-approval, which can take hours.
Versioning gives every edit a stable identifier. In-flight journeys hold the version they entered with. New entrants get the latest approved version. Approvals are per-version, not per-template.
Template lifecycle
Draft → Submitted → Approved → Active → Superseded
↓
Rejected| State | Behaviour |
|---|---|
| Draft | Editable; not sendable; not submitted upstream |
| Submitted | Sent to upstream (Meta WABA, TRAI DLT, etc.); awaiting verdict |
| Approved | Upstream said yes; sendable; will be assigned to new journey entries |
| Active | A specific version that’s the current default for new entries |
| Superseded | An older approved version; still sendable for in-flight journeys; not picked for new entries |
| Rejected | Upstream rejected; not sendable; the operator must edit and resubmit |
The Active version is implicit: the most-recently-approved version of the template, unless an operator pins an older version.
What gets versioned
Every property of the template that affects its rendering or upstream approval:
- Body text (with variable placeholders)
- Header (text / image / video / document)
- Footer text
- Button definitions (URL / phone / quick-reply)
- Category (marketing / utility / authentication for WhatsApp)
- Language
Variables themselves are NOT versioned — they’re resolved from contact context at send time. Updating the variable schema (adding customer_first_name, deprecating customer_full_name) is a separate migration tracked at the template variable registry level.
In-flight journey pinning
When a contact enters a journey, the journey snapshot records the template version IDs for every send node it’ll hit. Subsequent template edits don’t touch this snapshot — the contact will see the templates the operator approved at the time the contact entered.
The exception: forced rollout can override pinning. An operator can mark a new version as force_rollout: true, which makes in-flight journeys switch to the new version on their next send. This is destructive — used only when the previous version is genuinely broken (e.g. legal-mandated text correction).
Cross-channel templates
The unified channel template lifecycle covers templates that exist across multiple channels. For example, a “welcome” template might have:
- A WhatsApp HSM (Meta-approved)
- An SMS variant (DLT-registered)
- An email design (HTML + DKIM-signed sender)
These are three separate version trees, but the unified template ties them together so an operator can edit “the welcome template” and see all three channels in one view. Approval is independent per channel — the WhatsApp HSM can be approved while SMS waits on DLT.
Catalog architecture
Templates that drive automations live on the control plane alongside the rest of the catalog:
component_templates— reusable journey components (use the graph schema)playbook_templates— full playbook definitionsad_campaign_templates— paid-domain campaign skeletonschannel_templates— per-channel message templates (WhatsApp HSM, SMS DLT, email designs)unified_template_catalog— index over all of the above
sync_catalog.py upserts every template into unified_template_catalog within the same transaction as the source write (via SQLAlchemy after_insert / after_update listeners — no cross-plane HTTP). The admin catalog drawer reads metadata from unified_template_catalog and fetches full source data via /api/v1/admin/catalog/entries/{id}/source-detail.
When a new template type is added, it must (a) be synced into unified_template_catalog and (b) have a visual renderer in admin-template-preview.tsx.
Operational instances of these templates — journeys, journey_instances, playbook_execution_instances, ad_campaign_instances, ad_campaign_stage_state, ad_campaign_audience_flows — live on the cell plane.
API shape
Templates are exposed under /api/v1/templates/:
| Endpoint | Purpose |
|---|---|
GET /templates/{id} | Returns the template with its current Active version and all approved versions |
GET /templates/{id}/versions | Lists every version with its state |
POST /templates/{id}/versions | Creates a new draft version (does not submit) |
POST /templates/{id}/versions/{version_id}/submit | Submits to upstream for approval |
POST /templates/{id}/versions/{version_id}/activate | Marks an approved version as Active |
Send-time version resolution
When a journey send fires, the engine resolves the template version in this order:
- The version_id pinned in the journey snapshot for this contact (if pinned)
- The version_id pinned by
force_rollout(if any) - The current Active version of the template
Resolution emits a record so you can later audit which version any historical send used.
Failure modes the lifecycle handles
| Scenario | Behaviour |
|---|---|
| Upstream approval times out | Template stays in Submitted; alerts the operator after N hours |
| Upstream rejects the version | State → Rejected; send nodes pointing at this template fall back to the previous Active version |
| Operator deletes a template | Refused if any in-flight journey is pinned to it; otherwise marked Archived (not actually deleted) |
| Variable schema change breaks an in-flight pin | Detected at version-pin time; in-flight contacts are migrated to the latest version with a per-contact note in the timeline |
What’s next
- Channels overview — channel-level concepts
- Event governance — variable schema governance
- WhatsApp guide — operator-facing WhatsApp template flow