Audience messages
Message everyone in a data table who matches your filters — one message per row, no AI processing.
Most scheduled tasks message one contact. An Audience Message messages everyone in a data table who matches your filters — one message per matching row, on WhatsApp or Telegram.
Create one from the agent's Scheduled Tasks tab, the same place as any other task, by choosing the Audience Message action.
How it differs from a single-contact task
| Agent message | Audience message | |
|---|---|---|
| Who receives it | One contact | Every row matching your filters |
| The agent runs | Yes — it writes the reply | No — you write the message |
| Recipients must be contacts | Yes | No, a table row is enough |
| Billing | One interaction | One interaction per recipient |
Because no model runs, an audience message says exactly what you wrote. It is a way to reach a list, not a way to hold a conversation — though if a recipient replies, the agent picks up the conversation the message opened, with the campaign for context, rather than starting cold.
There is no "payment reminder" or "birthday" feature. Those are a filter and a template, and building them is the point.
Choosing who receives it
Pick a Data table, then add Filters. Everyone in the table who matches them all will be messaged. Only tables the agent has read permission on are offered — see permissions if the table you want is missing.
Filter values can use date tokens, resolved against the task's timezone on the day it runs:
| Token | Resolves to |
|---|---|
{{today}} | The run date, as YYYY-MM-DD |
{{today+7d}} | Seven days after the run date |
{{today-3d}} | Three days before the run date |
{{today:MM-DD}} | Month and day only — for anniversaries, ignoring the year |
{{today:YYYY}} | The year only |
So "invoices due in a week" is due_date is {{today+7d}}, and "birthdays today" is
birthday is {{today:MM-DD}} against a column holding MM-DD.
Which column can be addressed
Send to picks the column holding each recipient's address. Only two column types can be messaged, and which one depends on the channel.
| Channel | Column type that works |
|---|---|
| WhatsApp ID | |
| Telegram | Telegram ID |
A Phone column does not work for WhatsApp. The two are different identities even when the number looks the same, and a phone column is reserved for a future SMS channel. A Telegram handle cannot be messaged either — it can authorise someone who messages your agent, but the Telegram Bot API cannot start a conversation from an @username. An Email column has no channel at all.
If your table stores numbers in a phone column, add a WhatsApp ID column and populate it with the digits-only form. See field types.
Rows that match your filters but have nothing in the Send to column are skipped, and counted separately in the preview.
Addressing through a linked table
The address does not have to live on the table you are filtering. If your table has a relation column linking each row to one record in another table — a Payments table where every payment links to a Contacts record — Send to also offers that table's columns, shown as Contact → WhatsApp ID.
That is what lets you filter payments and message people: the filters run on the payments row, the message goes to the number on the linked contact. You do not have to copy the number onto every payment, so it stays correct when the customer changes it.
Two limits:
- The relation must link to one record, not many. A relation that holds several records names no single recipient, so it is not offered.
- Filters still run on the table you picked. You cannot filter on a linked table's columns — filter on Payments, address through Contacts.
A payment whose relation is empty, or points at a record that has since been deleted, is skipped and counted separately in the preview from rows whose contact simply has no WhatsApp ID. The two have different fixes: link the record, or fill in the number.
A table with no usable column — its own or through a link — cannot be an audience, and the editor says so rather than letting you save.
Writing the message
On WhatsApp you must select an approved template. Meta only allows a business to start a conversation with one, and there is no guarantee the 24-hour window is open when the task runs. See templates and quality.
On Telegram you write the message text directly.
Template parameters can be filled three ways:
| Parameter type | What it fills in |
|---|---|
| Static text | The same value for everyone |
| Column | A value read off each matching row |
| Recipient number | The number the message is being sent to |
Column is what makes the message personal — bind a parameter to first_name or
amount_due and every recipient gets their own. There is no contact name or agent name token
here, because an audience has no single contact behind it.
Columns on a linked table are offered here too, on the same terms as Send to above — so a
payment reminder can greet people by the name held on their contact record without copying it
onto every payment. In Telegram message text they are written {{contact.first_name}}.
Attaching an image
An audience message can carry one image, sent as a single message with your text as its caption — not a photo followed by a separate bubble. Everyone in the audience gets the same image; it cannot vary per row.
Only images shared with all conversations can be attached. Open the file in Assets, choose Share with all conversations, and give the agent permission to send it. Files belonging to one customer — anything they uploaded in a chat, or an image the agent generated for them — are deliberately not offered: an audience message has no single customer behind it, so there is nothing to check a private file against.
On Telegram you can attach an image to any message. Keep the text under 1,024 characters — past that, the remainder arrives as a second message.
On WhatsApp the image travels in the template's header, so it only works with a template that Meta approved with an Image header. Create one in WhatsApp Manager with header type Image; the Image field appears once you select it, and is then required. A template's layout is fixed when Meta approves it, so an image cannot be added to a text-only template.
A message with an image counts as two interactions per recipient instead of one. A 500-person birthday campaign with a photo uses 1,000. See interaction counting.
Checking who matches
Select Check who matches before saving. It reports how many people will be messaged, lists the first few with their addresses partly hidden, and tells you how many matching rows were skipped — separately for rows with no address and rows not linked to a record in the table being messaged.
The preview stores nothing and sends nothing. Use it — a mistyped filter is invisible until the messages have gone out.
Updating the row after sending
After sending, update lists the columns to write back to once the message has gone out. Add as many as you need — the rows stack the same way filters do.
This matters more than it looks. A recurring task has to change something about the rows it just messaged, or it matches the same people again on the next run.
Each row picks a column and what to write into it. What is on offer depends on the column:
| Write | Available on | What it writes |
|---|---|---|
| the send time | date, date-and-time, text | The moment the message actually went out |
| set to | any column you can type a value in | A fixed value you choose |
| move forward by | date | The row's own date, plus N months |
| add to it | number, currency | The row's own number, plus (or minus) an amount |
Record that they were reached
Write the send time into a column, so a task filtered on "reminder sent is empty" stops matching that row.
Move a date forward to the next cycle
Move forward by N months sets the column to its own current value plus that many months, instead of the send time. This is the recurring-due-date shape: a subscription renewal, an appointment, an insurance premium.
Say policies carry a next_premium_due_date. One task covers every quarterly policy:
| Setting | Value |
|---|---|
| Filter | next_premium_due_date is {{today+7d}} |
| Filter | premium_frequency is Quarterly |
| Filter | policy_status is Inforce |
| After sending, update | next_premium_due_date, forward by 3 months |
Each run messages whoever is due in a week and rolls their date to the next quarter, so the following quarter is picked up with no further setup. Author one task per frequency — monthly forward by 1, semi-annual by 6, annual by 12 — each filtered to its own frequency value.
Month-ends clamp rather than overflow: a due date on 31 January moves to 28 February (29 in a leap year), not 2 March. A date that was clamped returns to its original day as soon as the target month is long enough, so a policy due on the 31st stays on the 31st.
Only date columns can be moved forward. Date-and-time and text columns can still be stamped with the send time. If a row's date column is empty or unreadable, that row is still messaged but that column is not written — writing the send time there would silently re-date the record to today. The rest of the row's write-backs still apply.
Multi-step follow-ups
Stacking is what makes a follow-up sequence possible: a column records which step someone is on, the filter reads it to decide who gets which message, and the write-back advances it.
Say contacts carry a follow_up_msg number. Author one task per step:
| Setting | Step 1 task | Step 2 task |
|---|---|---|
| Filter | follow_up_msg is 1 | follow_up_msg is 2 |
| WhatsApp template | follow_up_1 | follow_up_2 |
| After sending, update | follow_up_msg set to 2 | follow_up_msg set to 3 |
| After sending, update | last_contacted_at the send time | last_contacted_at the send time |
After step 1 goes out, those contacts stop matching step 1 and start matching step 2, so the
next run picks them up with the next message. Someone at 3 matches neither and drops out of
the sequence.
Add to it does the same job without naming the next number — follow_up_msg plus 1
reads whatever the row holds and adds one. A row where the column is empty counts as zero, so
it becomes 1. Use whichever reads more clearly; the filter still decides the template either
way.
What applies to every write-back
A write-back happens only if the agent has update permission on that column, and only after the message has gone out. If one fails, the message is not re-sent — the send is the thing that matters, and a failed write-back is recorded in the logs. Columns are handled independently: a column the agent may not write is skipped while the rest still apply.
Moving a date forward happens when the reminder is sent, not when the customer acts on it. After a payment reminder goes out, the due date already points at the next cycle — so if you also need to know who has not paid, track that in a separate column rather than reading it off the due date.
Limits
- An audience is capped at 5,000 recipients. Over that, the task sends nothing rather than an arbitrary subset — messaging half a list is worse than messaging none of it.
- If the audience is larger than your workspace's remaining monthly allowance, it also sends nothing. Check interaction counting and your plan's allowance before scheduling a large run. An image doubles what each recipient costs, so it halves how large an audience your remaining allowance covers.
- Messages go out steadily rather than all at once, because WhatsApp and Telegram both pace what a business can send. A large audience takes a while to work through.
- Failed sends are retried a few times with an increasing gap. Someone who has blocked your bot, or whose number is not reachable, is not retried.
Who can be messaged
The channel's allow and deny lists apply to every message an audience sends, exactly as they do to someone messaging in. If a channel is Restricted, only people its allow list covers are messaged. See access control.
On WhatsApp, a customer who has opted out of marketing is skipped for marketing templates, but still receives utility ones. See opting out of marketing messages.
An agent cannot create an audience message for itself. They are made in the dashboard only.
Troubleshooting
| Symptom | Likely cause |
|---|---|
| Audience Message is not offered | The agent has no active WhatsApp or Telegram channel |
| The data table is not in the picker | The agent has no read permission on that table — or on the table it links to |
| "This table has no WhatsApp ID column" | The addresses are in a phone column, which WhatsApp cannot use |
| A linked table's columns are not listed | The relation links to many records, not one — or the agent cannot read the linked table |
| "Filters cannot reach through a relation" | A filter names a linked table's column. Filter on the table you picked; address through the link |
| The preview finds nobody | A filter names a renamed column, or a date token resolved to a different day in the task's timezone |
| Saved, ran, nothing sent | Every match was skipped — no address in the Send to column, no linked record, or the channel's lists exclude them |
| Fewer messaged than the preview showed | Recipients opted out of marketing, or were added to the deny list since |
| The same people are messaged every run | The filter has nothing to exclude them — set After sending, update |
| "Move forward by" is not offered | The chosen column is not a date column. Each column offers only what its type supports |
| A date column was not moved forward | That row's date was empty or unreadable, so that column was left alone rather than re-dated to today |
| One column updated but another did not | That row could not supply a base value, or the agent has no update permission on that column |
| The task failed with "invalid payload" | A write-back names a renamed, encrypted or linked-table column, or a value the column will not accept |
| Nothing sent at all on a large audience | The audience is over 5,000, or over your remaining monthly allowance |