Using Velocity in Messages
See how to create email template with dynamic variables
Velocity lets you render contact data, event parameters, and external source data directly in message templates. This article walks through the core techniques — variables, objects, arrays, loops, and conditions — using an invoice email as a practical example.
All directives in email HTML must be wrapped in comments to keep the markup valid: <!--#if(...)-->, <!--#foreach(...)-->, <!--#end-->. In SMS, Mobile Push, Web Push, and App Inbox, directives are used without comments.
Data Sources in Messages
Three types of data are available in message templates:
Contact fields — available in any message, pulled from the contact profile at send time.
Event parameters — available in triggered messages, passed through the workflow that the event launched.
External sources — available when a message uses a connected external data source, such as Google BigQuery, Google Sheets, or HTTP-based sources.
All of these values are accessed with Velocity.
The main difference is not the syntax itself, but where the data comes from, how it is structured, and in which context it is available.
Note
- Event parameters are available only in the triggered context where the event was passed.
- External source data is available only when the source is connected to the message.
Use Case: Invoice Email
We'll build a subscription invoice email using the following event payload:
{
"eventTypeKey": "InvoiceCommon",
"keyValue": "87420539",
"params": {
"time": "2026-01-03T10:00",
"externalCustomerId": "87420539",
"billingCountry": "US",
"firstName": "John",
"lastName": "Item",
"email": "[email protected]",
"items": [
{
"product": "One-month main_subscription",
"afCurrency": "USD",
"dateOfTransaction": "2026-01-03",
"paymentType": "auto-renewal",
"billingType": "paysafecard",
"orderId": "6c60e1a3",
"priceWithoutTaxes": 15.19,
"renewalDate": "2026-02-03",
"price": 15.19,
"priceToBeBilled": 30.99,
"subscriptionPeriod": "30"
}
]
}
}The email has two blocks: contact details and invoice items.
Block 1: Contact Details
Simple top-level parameters map directly to variables:
| Parameter | Variable |
|---|---|
firstName | $firstName |
lastName | $lastName |
email | $email |
billingCountry | $billingCountry |
Example in email:
Block 2: Invoice Items
The items parameter is an array — it can contain one or many products. Use #foreach to loop through all items regardless of how many there are.
Step 1: Loop boundaries
<!--#foreach($item in $items)-->
...
<!--#end-->
Step 2: Variables inside the loop
Inside the loop, reference each field using $item.<fieldName>:
| Field | Variable |
|---|---|
| Product name | $item.product |
| Order ID | $item.orderId |
| Price | $item.price |
| Currency | $item.afCurrency |
| Transaction date | $item.dateOfTransaction |
| Price without taxes | $item.priceWithoutTaxes |
| Renewal date | $item.renewalDate |
| Amount to be billed | $item.priceToBeBilled |
| Subscription period | $item.subscriptionPeriod |
The result will look as follows:
Conditions
Use #if / #elseif / #else to show or hide content based on a value:
<!--#if($billingCountry == 'US')-->
<p>Billed in USD. Tax rules apply.</p>
<!--#elseif($billingCountry == 'DE')-->
<p>Billed in EUR. VAT included.</p>
<!--#else-->
<p>See your local billing terms.</p>
<!--#end-->
Use #if to hide a block entirely when a value is missing:
<!--#if($!{item.renewalDate})-->
<p>Next renewal: $item.renewalDate</p>
<!--#end-->
Below are two rendered email variants — one with renewal date block visible, one without.
Accessing a Single Item from an Array
If you need only the first item from an array without a loop, use the index:
$items[0].product$items[0].subscriptionPeriod
This is useful when you know the array always contains exactly one item, or when you only need to reference the first entry.
Note
In short-form channels such as Mobile Push, SMS, Web Push, and App Inbox, it is often better to reference a specific array element instead of rendering the full array with
#foreach. This keeps the message concise and better suited to the channel format.
Safe Output
Apply the same safe patterns as with contact fields:
$!{item.renewalDate}— silent if missing${item.product|'Subscription'}— fallback value
Use these patterns whenever a value may be missing or optional in the payload.
Testing Your Template
Before sending, use Additional settings → Configuring dynamic content in the message editor to preview the result with real data. Paste a JSON object with the parameters you want to test and click View message.
You can copy parameter values directly from Automation → Event history — open any event and copy its parameters. This is the easiest way to verify field names, array paths, and conditions before sending.
Next Steps
- Using Velocity in Workflows — branching, calculations, and writing values to contact fields
- Velocity Reference — full syntax reference
- Testing & Troubleshooting Velocity — how to debug substitution issues
Updated 12 days ago
