Conversa Labs

Last updated on Oct 13, 2026

Overview

Dynamic variables let you write one message that arrives personalised for every person. Instead of "Hello!", you write Hello {{ contact.first_name }}! and each contact receives their own name.

A variable is always written between double braces and resolved on the server, at send time β€” never in the browser. If the data does not exist, the variable becomes empty text and the rest of the message goes out normally.

Every variable uses the same vocabulary on every surface: what works in the reply composer works identically in an email, a campaign, a follow-up, a flow and a contract.

Prerequisites

  • None. The basic variables (contact, account, inbox, agent) work from day one.
  • A module's variables (charge, booking, contract, order…) only carry a value once that module is in use in the account. With no data they resolve to empty β€” they never break the message.

Step by step

  1. Open any text field that accepts variables (composer, macro, canned response, campaign, follow-up, contract, booking reminder…).
  2. Type {{ β€” the variable list appears automatically.
  3. Or click the {x} Insert variable button when it is available in the field header.
  4. Search by name (for example "amount" or "due") and click to insert.
  5. Check the value before sending. Inside a conversation, the picker shows what the selected variable will produce for that contact β€” the real name, the real charge amount, the real booking date. When you type {{, the value appears in the preview panel under Value; through the {x} button, it appears as a chip on the token's own row. What you see there is exactly what the customer will receive.
  6. Send a test before firing at your whole base.

Settings & options

The variable picker

There are two ways to reach the same list:

  • Typing {{ inside the editor. The picker opens anchored to the point where you are writing, above or below that line β€” whichever side of the screen has more room β€” with the search field already focused: keep typing and the list filters.
  • Through the {x} Insert button, in the header of the fields that offer it (WhatsApp templates, follow-up steps, booking reminders, flow builder fields).

In both cases the search matches the token name and the description β€” searching for "due" finds the variable even when you do not know its name.

The list is never re-sorted. Groups and tokens arrive in the server catalog's own order, which already puts the most-used first (name, first_name, email… before the postal address fields). The custom fields you created join the group of the model they belong to.

The preview panel shows two fields for the selected item:

Field What it carries
Description What the variable means, in the dashboard language
Value What it will produce in this conversation, computed on the server by the same engine that assembles the message at send time

When the token has no value for that contact, the panel says "No value for this conversation" in muted text instead of faking a value it does not have. That is the honest answer β€” and it is exactly what you need to see before sending.

On wide screens the panel sits to the right of the list; on narrow screens it drops below it; and when it fits neither way, the picker shows the list alone, which keeps working the same.

Keyboard: ↑ ↓ (or Tab / Shift+Tab, or Ctrl+P / Ctrl+N) move through the list, which wraps from the last item to the first; Enter inserts; Esc closes and hands focus back to the text; Backspace with an empty search erases the {{ that opened the picker. Moving the mouse over a row selects it, clicking inserts, and clicking outside closes.

Where {{ does not open. The trigger is only recognised at the start of the line or after a space, and the picker does not open in a private note β€” picking a variable from the list is a Reply-mode feature. Once closed with Esc, it only reopens when you type {{ afresh.

The available groups

Group What it is for Example
Contact Who is on the other side {{ contact.first_name }}
Conversation The current conversation {{ conversation.display_id }}
Account / Brand Your company {{ account.name }} Β· {{ brand.name }}
Agent Who is answering {{ agent.available_name }}
Date and time The clock, in the account timezone {{ now.date }} Β· {{ now.weekday }}
CRM deal The deal linked to the contact {{ crm.title }} Β· {{ crm.value_formatted }}
Organization The contact's company {{ organization.legal_name }}
Charge The most recent charge {{ payment.pay_url }} Β· {{ payment.due_date_formatted }}
Subscription The recurring plan {{ subscription.next_due_date_formatted }}
Order The most recent order {{ order.amount_formatted }}
Booking The contact's appointment {{ booking.date_formatted }} Β· {{ booking.manage_url }}
Contract The open contract {{ contract.sign_url }}
Task The task linked to the contact {{ task.due_at_formatted }}
Cart and checkout Sales recovery {{ commerce.pay_url }}
Product The product being quoted {{ product.price_formatted }}
Group WhatsApp group / launch cohort {{ group.invite_url }}
Team Β· SLA Β· Availability Operations {{ team.name }} Β· {{ wfm.online }}
Satisfaction Β· Engagement Relationship {{ csat.rating }} Β· {{ engagement.tier }}
Salesperson Β· Goal Β· Commission Β· Affiliate Sales {{ seller.name }} Β· {{ affiliate.referral_code }}
Ad Β· Lead Paid acquisition {{ lead.headline }}
Article Help Center {{ article.url }}
Account Brain AI signals {{ brain.risk_band }}

The picker only shows the groups that work on that screen. A campaign, for instance, has no conversation, so conversation variables are not offered there.

Account Brain signals

The {{ brain.* }} variables describe the conversation the message is written in:

  • {{ brain.risk_score }} and {{ brain.risk_band }} show the risk of that conversation or, when it has no reading yet, of its contact. With neither, they stay blank β€” they never show the risk of another conversation.
  • {{ brain.insight_type }}, {{ brain.insight_severity }} and {{ brain.department }} show the insight about that conversation or its contact and, when there is none, the latest insight about the account as a whole (for example, a support indicator). Insights about other conversations and insights flagged as sensitive never appear in variables.

The value the picker shows is the one the message gets when it is sent, for anyone who can write in that conversation. In flows that run with no conversation and no contact, the risk is the latest reading of the account and the insight is the latest one about the account as a whole.

Date and time in the account timezone

{{ now.date }}, {{ now.time }}, {{ now.datetime }} and the other now values use the timezone selected in account settings, including in Flow Builder. The *_formatted tokens also display times in that zone. {{ now.timezone }} and {{ account.timezone }} show which timezone applies. Without a configured timezone or one inherited from onboarding or the legacy setting, the account uses UTC, even if the server uses another timezone.

The Liquid filters | date, | datetime and | time_of_day also use the timezone configured for the account, just like {{ now.* }} and the *_formatted tokens. For example, {{ contact.created_at | datetime }} displays the instant on the account clock.

With an explicit format such as | date: '%H:%M', an instant with an offset or Z also uses the account timezone. A date-only value keeps its written day; a date-and-time string without an offset keeps its written digits, without a timezone shift.

Flow Builder date conditions interpret dates without a timezone, such as 2026-10-06 00:00, in the account timezone. An ISO date with an explicit offset keeps its instant. Review comparisons in published flows that previously depended on UTC. The within-N-days condition still compares a duration of N Γ— 24 hours.

Formatted values

Every money and date value exists in two forms:

  • Raw β€” the value as stored: {{ crm.value_amount }} β†’ 1500.0
  • Formatted β€” ready for the customer to read: {{ crm.value_formatted }} β†’ $1,500.00

The same applies to dates: {{ payment.due_date }} β†’ 2026-08-08 and {{ payment.due_date_formatted }} β†’ 08/08/2026, always in your account's currency, language and timezone.

If a value has no formatted sibling, you can format it inline with a filter:

{{ payment.amount | money: 'USD' }}   β†’  $1,500.00
{{ booking.starts_at | datetime }}    β†’  08/08/2026 02:30 PM

Custom fields

The custom fields you created also become variables, in the form {{ contact.custom_attribute.key }}. This works for custom fields on contacts, conversations, organizations, deals, products, tasks, groups, charges, bookings, follow-ups and contracts.

Use cases

  • Overdue charge: Hi {{ contact.first_name }}, your invoice of {{ payment.amount_formatted }} was due on {{ payment.due_date_formatted }}. Pay here: {{ payment.pay_url }}
  • Booking reminder: Your appointment is on {{ booking.date_formatted }} at {{ booking.time_formatted }} with {{ booking.host }}. Need to reschedule? {{ booking.manage_url }}
  • Contract: {{ contact.first_name }}, your contract "{{ contract.title }}" is ready: {{ contract.sign_url }}
  • Group invite: Welcome! Join {{ group.name }}: {{ group.invite_url }}

Tips, limits & best practices

  • Always send a test. It is the fastest way to see whether the variable brought the value you expected.
  • Esc closes, Backspace erases. Esc closes the picker but keeps the {{ in the text β€” handy when you did mean to type braces. To remove the trigger along with it, use Backspace with the search field empty.
  • What you type after {{ goes to the search field, not to the message. The picker's search field takes focus as soon as it opens.
  • Missing data becomes empty. Write the sentence so it still reads correctly without the value β€” avoid "Your order of has arrived".
  • In campaigns, take extra care. If a variable used in the approved template does not resolve for a recipient, that recipient is skipped. Prefer variables you are certain exist.
  • Tax ID: in messages the tax ID only ever appears masked ({{ contact.masked_tax_id }}). The full document is exclusive to contracts, which the person signs themselves.
  • Do not invent variables. If it is not in the picker it does not exist, and it will render empty.
  • Where the value does not show. Outside a conversation (macro, canned response, campaign, contract template) there is no contact yet, so the list shows only the variable name. That is the correct behaviour: the variable has no owner there yet.
  • If your role hides a field, the value shows hidden too. An agent who sees a***@example.com on the contact record sees a***@example.com in the variable list β€” it is the sent message that carries the real value.

Troubleshooting

Symptom Likely cause What to do
The message arrived with a literal {{ ... }} The variable was typed into a field that does not resolve variables Use the picker β€” it only appears where variables work
The variable came out empty The data does not exist for that contact Check the record; rewrite the sentence to work without the value
The value rendered as 1500.0 You used the raw form Switch to {{ ...value_formatted }}
The date is off by one day The account timezone is not what you expected Adjust the timezone in Account settings
The campaign skipped recipients A variable in the approved template had no value Review the template and use safer variables
The "undefined variables" warning appears on a variable that works The variable genuinely has no value for this contact Look at the value next to it in the list: if it is blank, the data is not on record
I typed {{ and the picker did not open You are in a private note, where the picker does not open β€” or the {{ did not come at the start of the line or after a space Switch back to Reply mode to pick the variable from the list; in an internal note, write the value out in full
The panel says "No value for this conversation" The token has no data for this contact Check the record; rewrite the sentence to work without the value, or pick another variable
The picker opened without the preview panel There was no room for it on screen Widen the window or give the editor more height β€” the list works the same without the preview

See also