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

If 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].cost

This 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 value

Avoid 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:

ParameterVariable
First name${firstName}
Order number$externalOrderId

Block 2: Order Details

Top-level parameters map directly to variables:

ParameterVariable
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>:

FieldVariable
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:

FieldVariable
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.

  1. Place three separate structures in the template: women's selection, men's selection, general selection.
  1. Select the women's structure and open the code editor.
  1. Add the contact field name and a conditional operator above the tr tag.

General format:

<!--#if($!parameterName == 'value1')-->

In this example:

<!--#if($!personal.gender == 'F')-->
  1. Select the men's structure. Its code will appear in the editor.
  2. Add a conditional operator above the tr tag.

General format:

<!--#elseif($!parameterName == 'value2')-->

In this example:

<!--#elseif($!personal.gender == 'M')-->
  1. Select the general structure.
  2. Add the following operator above the tr tag:
<!--#else-->
  1. 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].price

Testing 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