Using Velocity in Workflows

In workflows, Velocity goes beyond message personalization — you can use it to transform data, write values to contact fields, and route contacts through different paths based on field values, event parameters, or external data.

This article covers practical workflow use cases. Depending on the scenario, the expressions shown here are used in the Update custom fields, Branch, or Webhook block settings.


Use Case 1: Create a Holdout Group from the Last Digit of Contact ID

$contactId.toString().substring($mathTool.sub($contactId.toString().length(),1))

How to use later:

  • Control 10%

Put only contacts whose controlDigit ends with 0 into the control group. That means: out of all contacts (0–9), you take just one digit (0) → about 10% of the audience.

  • Test 90%

Send campaigns to everyone except the control group. So you include contacts whose controlDigit is any digit from 1 to 9 → about 90% of the audience.

  • Control 20%

Put contacts whose controlDigit ends with 0 or 1 into the control group. That means you take two digits out of 10 possible (0 and 1) → about 20% of the audience.

How this looks in practice:

In the Branch block (or segment condition), you check the saved field controlDigit and route contacts into Control vs Test paths based on which last digit they have.


Use Case 2: Save a Value from TrainingCompleted into a Contact Field

  • Goal: save the last training date from an event into a contact field (e.g., lastTrainingDate) so you can reuse it later.
  • Start: Event-based (TrainingCompleted)
  • Where: Workflow → Update custom fields block
  • Store: last training date into custom field lastTrainingDate
  • Expression:

If you take the value from an object in the lastTraining parameter: $lastTraining.trainingDay

Example event fragment:

"lastTraining": {
  "trainingDay": "2026-01-03",
  "trainingType": "Strength"
}

If you take the value from an array in the trainingSessions parameter: $trainingSessions[0].trainingDay

Example event fragment:

"trainingSessions": [
  { "trainingDay": "2026-01-03" }
]

How to use later:

  • Personalize messages: insert the saved date into email/push content (e.g., "Your last training was on $lastTrainingDate").
  • Segment by recency: create segments like "trained in last 7 days", "inactive 14+ days", "inactive 30+ days".
  • Branch inside workflows: route users into different paths based on how recent the training was (active users get progress tips, inactive users get reactivation offers).

How this looks in practice:

  1. A user completes a workout → Reteno receives the TrainingCompleted event.
  2. The workflow starts and immediately goes to Update custom fields.
  3. Reteno writes the event value into the contact field lastTrainingDate (using $lastTraining.trainingDay or $trainingSessions[0].trainingDay).
  4. Any future workflow, segment, or message can read lastTrainingDate from the contact profile and use it for branching or personalization.

Use Case 3: Use a Webhook to Enrich or Transfer Data

  • Goal:
    • GET: request/enrich data from an external system (for example: eligibility, user score, recommended plan, coupon code) and optionally store the returned values in Reteno.
    • POST: transfer selected event/contact data to an external system (for logging, CRM updates, analytics pipelines, triggering backend processes).
  • Start: Event-based/Regular for segment
  • Where: Workflow → Webhook block (+ Update custom fields block if you want to store GET results)
  • Store:
    • GET: optional — store values returned by the endpoint into custom fields (e.g., couponCode, userScore, recommendedPlan).
    • POST: storing depends on your use case.

Expression:

GET (pass data in the URL, receive response):

https://api.example.com/enrich?contactId=$contactId&lastTrainingDate=$lastTrainingDate

POST (send data in the request body):

{
  "contactId": "$contactId",
  "externalCustomerId": "$externalCustomerId",
  "event": "TrainingCompleted",
  "lastTrainingDate": "$lastTrainingDate"
}

How to use later:

  • After GET:

    • Personalize messages using stored values (e.g., coupon code, plan recommendation).
    • Branch workflows based on returned attributes (eligible vs not eligible, score tiers).
    • Segment audiences using enriched fields (e.g., high score users).
  • After POST:

    • Build external reporting/attribution (events stored in BI/DWH).
    • Keep CRM/CDP in sync (update user state externally).
    • Trigger external actions (generate a coupon, create a task, etc.).

How this looks in practice:

  1. TrainingCompleted starts the workflow.
  2. The workflow reaches the Webhook block.
  3. One of two patterns happens:
    • GET pattern: Reteno calls the URL → external system responds with data (e.g., { "couponCode": "TRAIN10", "userScore": 82 }).
    • POST pattern: Reteno sends a JSON payload → external system stores/logs/acts on it.
  4. If it's GET, the workflow can then go to Update custom fields to save response values into the contact profile.
  5. Future workflows and messages use those saved fields (GET) or rely on external processing (POST).

Use Case 4: Branch by Subscription Period

  • Goal: check the subscription period in the payment event and send the corresponding email.
  • Start: Event-based (InvoiceCommon)
  • Where: Workflow → Branch block → Email block
  • Store: not required (optional — you can save the subscription period to a contact field if you want to reuse it later in segments, workflows, or messages).

In this example, subscription details are passed in the items parameter, where the products are stored inside items.array.

To access the subscription period of the first item, use:

$items.array[0].subscriptionPeriod

Example event fragment:

{
  "eventTypeKey": "InvoiceCommon",
  "params": {
    "items": {
      "array": [
        {
          "subscriptionPeriod": "90"
        }
      ]
    }
  }
}

How to use it in the Branch block

Compare $items.array[0].subscriptionPeriod and route contacts to the correct email path:

  • 30 → send the email for a 30-day subscription
  • 90 → send the email for a 90-day subscription
  • other / empty value → use a fallback branch

How to use later

  • Send different payment confirmation or onboarding emails depending on the purchased subscription term.
  • Keep communication aligned with the actual subscription period from the payment event.
  • Optionally save the value to a contact field if you need to reuse it later outside this event-triggered workflow.

How this looks in practice

  1. A contact triggers the InvoiceCommon event after payment.
  2. The workflow starts.
  3. The Branch block checks $items.array[0].subscriptionPeriod.
  4. The contact goes to the corresponding email branch based on the value.
  5. Each branch sends the appropriate email for that subscription term.

Next Steps