Template system
Every notification Carriyo sends (email subject, email body, SMS text) is rendered from a template. Templates separate the merchant's editable copy from the underlying shipment data, with a templating language that injects the right values at send time.
This page is the model behind the template editor in CX Apps → Notifications.
Liquid
Carriyo uses Liquid (liquidjs) for templating. The full
default Liquid syntax is available: tags ({% if %},
{% for %}), filters ({{ value | default: "fallback" }}),
object access ({{ a.b.c }}).
WhatsApp doesn't use Liquid. WhatsApp Business templates are pre-registered with Meta; Carriyo sends placeholder values as an array against the approved template name. The placeholder layout is fixed when the template is registered.
The shape of a template
A template has:
- Channel.
email_subject,email_body,sms. - Trigger event. Which shipment / return / order event
fires it (e.g. shipment
OUT_FOR_DELIVERY). - Language. Per locale; one logical template has multiple variants.
- Merchant scope. Per merchant on multi-merchant tenants.
- Body. The editable content with embedded Liquid.
Available data
At render time the template sees a snake-cased view of the event's context. The most-used objects:
shipment: the full Shipment document (carrier, tracking number, pickup, dropoff, parcels, status, dates).customer: the customer's name, email, phone (drawn from the shipment'sdropoff).order: order-level fields when the shipment belongs to one (partner reference, totals).merchant: merchant brand profile (name, support contacts).carrier: carrier name and carrier account name.trigger.status,trigger.channel: the firing trigger and channel.key_milestones: the canonical events on the shipment so far.profile.brand,profile.logo: merchant brand identity.
Link tags for the major customer-facing pages:
tracking_link: the branded tracking page URL.feedback_link: the feedback page URL.return_request_link: the returns portal URL.
For SMS, these are auto-shortened to a Carriyo short-link redirector to keep within character limits.
For return requests, additional context is available:
resolution: refund / replace / etc.items[].return_reason,items[].rejection_reason: translated to the recipient's language.
The Dashboard's template editor shows the full tag tree inline as you author; what's documented here is the shape, not the exhaustive field list.
Fallbacks for missing data
Use Liquid's default filter:
Hi {{ customer.first_name | default: "there" }},
Your order {{ order.partner_order_reference }} is out for delivery.
Track here: {{ tracking_link }}
For conditional sections, use if:
{% if shipment.estimated_delivery.from %}
Expected between {{ shipment.estimated_delivery.from }}
and {{ shipment.estimated_delivery.to }}.
{% endif %}
Translations
Each template is one logical entity with multiple language variants:
- The merchant configures available languages on the brand.
- Each language variant is authored separately. There's no auto-translation. Localized copy is written by the merchant.
- Tags are shared across variants. The same
customer.*/shipment.*paths work regardless of language; only the surrounding copy differs.
The Dashboard's editor shows variants side-by-side so copy stays in sync.
How it fits with other modules
- Notifications. Templates are the content layer of notifications.
- Branded tracking. The tracking page uses brand theme strings, not Liquid templates.
- Post-purchase CX. The parent module.