Velocity in Messages
Velocity lets you use contact data, event parameters, and external source data directly in message templates. This article covers the core techniques — variables, objects, arrays, loops, and conditions — using an order confirmation email as a practical example, along with specifics for short-form channels.
All directives in email HTML must be wrapped in comments: <!--#if(...)-->, <!--#foreach(...)-->, <!--#end-->. In SMS, Mobile Push, Viber, Telegram, and App Inbox, directives are used without comments.
How Velocity Reads Data
All data in Yespo is passed in JSON format — structured text with key-value pairs:
{
"firstName": "Helen",
"discount": "15%",
"deliveryMethod": "express"
}To insert a value into a message, place $ before the key name:
Hello, $firstName! Your discount: $discount.
Result: Hello, Helen! Your discount: 15%.
If data is nested — for example, a product inside an order — use dot notation or an array index. Details in the sections below.
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 an HTTP source
All of these are accessed through Velocity. The key difference is not the syntax itself, but where the data comes from, how it is structured, and in which context it is available.
Data Techniques
#foreach loop for arrays
#foreach loop for arraysIf an event parameter contains an array — for example, a list of products in an order — use #foreach to iterate over all elements regardless of how many there are.
In email:
<!--#foreach($item in $items)-->
<p>$item.name — $item.cost</p>
<!--#end-->In short-form channels (without comments):
#foreach($item in $items)
$item.name — $item.cost
#end$foreach.count is the iteration counter, starting from 1.
Conditions
Use #if / #elseif / #else to show or hide content based on a value:
<!--#if($deliveryMethod == 'express')-->
<p>Express delivery: 1–2 days.</p>
<!--#elseif($deliveryMethod == 'Standard')-->
<p>Standard delivery: 3–5 days.</p>
<!--#else-->
<p>See your delivery confirmation for timing.</p>
<!--#end-->Use #if to hide a block entirely when a value is missing:
<!--#if($!{discount})-->
<p>Discount: $discount</p>
<!--#end-->Accessing a single array element
If you need only the first element of an array without a loop, use the index (0-based):
$items[0].name
$items[0].costThis is useful when the array always contains exactly one element, or when you only need to reference the first entry.
Handling missing values
$!{item.imageUrl} ← output nothing if missing
${item.name|'Product'} ← fallback valueAvoid bare $variable in production messages — if the value is missing, contacts will see the raw variable name.
Examples
Order Confirmation Email
We'll build an order confirmation email using the following event payload:
{
"eventTypeKey": "OrderCreated",
"keyValue": "380501234567",
"params": {
"phone": "380501234567",
"externalOrderId": "12345679",
"externalCustomerId": "AV13760",
"totalCost": 680,
"status": "INITIALIZED",
"date": "2020-05-14T10:11:00+02:00",
"currency": "USD",
"shipping": 50,
"discount": 10,
"deliveryMethod": "Standard",
"paymentMethod": "MasterCard/Visa",
"deliveryAddress": "First str. 1",
"items": [
{
"externalItemId": "200600",
"name": "Super Device",
"category": "devices",
"quantity": 2,
"cost": 300,
"url": "http://example.com/item/200600",
"imageUrl": "http://example.com/item/200600/image.png",
"description": "High quality"
}
]
}
}Note: To transfer order data to the system, use Add orders or Generate event.
The email consists of four blocks: greeting, order details, product list, and order total.
Block 1: Greeting
The contact's name comes from the profile; the order number comes from the event:
| Parameter | Variable |
|---|---|
| First name | ${firstName} |
| Order number | $externalOrderId |
Block 2: Order Details
Top-level parameters map directly to variables:
| Parameter | Variable |
|---|---|
| Order number | $externalOrderId |
| Full name | $firstName $lastName |
| Delivery method | $deliveryMethod |
| Delivery address | $deliveryAddress |
| Payment method | $paymentMethod |
Block 3: Product List
The items parameter is an array that can contain one or many products. Wrap the product block in a #foreach loop:
<!--#foreach($item in $items)-->
...
<!--#end-->
Inside the loop, access each field using $item.<fieldName>:
| Field | Variable |
|---|---|
| Product name | $item.name |
| SKU | $item.externalItemId |
| Price | $item.cost |
| Quantity | $item.quantity |
| Image | $item.imageUrl |
| URL | $item.url |
To insert dynamic image parameters, open the Image block settings and add:
$!item.imageUrl— image path$!item.url— link$!item.name— alt text
Block 4: Order Total
Overall values are inserted directly:
| Field | Variable |
|---|---|
| Total cost | $totalCost |
| Discount | $discount |
| Shipping | $shipping |
To calculate the items subtotal (cost × quantity per item), place the following code before the total block. The directives execute but produce no output — after this block, $total holds the calculated value:
<!--#set($total=0)
#foreach($item in $items)
#set($total=$!total + $mathTool.mul($!item.cost, $!item.quantity))
#end
-->Output $total in the "Items subtotal" row.
Conditional Block Display Based on Contact Profile
With Velocity, you can show different email blocks depending on a value in the contact profile. For example, if gender is stored in the profile, men see a men's product selection, women see a women's selection, and contacts without a value see a general selection.
- Place three separate structures in the template: women's selection, men's selection, general selection.
- Select the women's structure and open the code editor.
- Add the contact field name and a conditional operator above the
trtag.
General format:
<!--#if($!parameterName == 'value1')-->In this example:
<!--#if($!personal.gender == 'F')-->
- Select the men's structure. Its code will appear in the editor.
- Add a conditional operator above the
trtag.
General format:
<!--#elseif($!parameterName == 'value2')-->In this example:
<!--#elseif($!personal.gender == 'M')-->
- Select the general structure.
- Add the following operator above the
trtag:
<!--#else-->
- After the closing
</tr>tag of the general structure, add:
<!--#end-->
Checking for values in different cases
If values may be stored in different cases — e.g., M or m, F or f — use an extended check:
#if($!personal.gender == 'm' || $!personal.gender == 'M')Or more concisely:
#if('m'.equalsIgnoreCase($!personal.gender))Velocity in Short-Form Channels
In short-form channels — Mobile Push, Viber, SMS, Telegram — directives are used without HTML comments:
#foreach($order in $!orderData)
$!order.name — $!order.cost
#end
If the dynamic content may exceed the channel's length limit, access a single element by index instead of using a loop:
$!orderData[0].name
$!recommendationsData[0].name — $!recommendationsData[0].priceTesting Your Template
Go to Additional settings → Configure dynamic content, paste JSON with the required parameters, and click Preview message.
You can copy parameters directly from any order event: go to Triggers → Event history, click an order event, and copy its parameters. This is the easiest way to verify field names, array paths, and conditions before sending.
Next Steps
- Velocity in Workflows — branching, calculations, and writing values to contact fields
- Velocity Reference — full syntax reference
- Testing & Troubleshooting Velocity — how to diagnose substitution issues
Updated 7 days ago
