broadcast_message
Send a message through the project's connector to a segment of its members. Destructive and irreversible — preview the audience and get human approval first.
Queue a message through the project's connector to a segment of its members. The send runs in the background at the pace the connector declares (28 messages a second on Telegram); the tool returns the audience it resolved and how many members it will reach.
Destructive and irreversible. Every recipient is a real person and the
message lands in their private chat. Call
preview_broadcast_audience first,
show the human the exact message text, the segment and the recipient count,
and get explicit approval before calling this. A broadcast cannot be recalled.
Not idempotent — calling twice sends twice. There is no de-duplication, because two identical broadcasts minutes apart is a legitimate thing a creator may want.
Requires ability
The token behind the MCP session must hold it, or the call is refused with TOKEN_MISSING_ABILITY.
Runs the same action as
The REST endpoint and this tool share one action, so validation, permissions and events are identical.
Fires events
Delivered to every endpoint subscribed to it once the change is made.
Annotations
A client that honours annotations asks a person before running it. It reaches beyond Subscriby: a connector, a provider or a member.
Arguments
project_id*stringUUID of the project whose members will receive the message.
message*stringThe message body, 1-4096 characters. Canonical HTML subset only (<b>, <i>, <u>, <s>, <a>, <code>, <pre>, <blockquote>, <tg-spoiler>); anything else is stripped before sending.
audiencestringoptionalSegment to address: all, customer, trialing, lead, churned, expiring_soon, cancelled_still_active, paused, trialing_cardless, all_pass_holders, all_pass_holders_not_in_queue, pass_holders, pass_holders_not_in_queue. Defaults to `all`, which reaches every member with a linked chat.
pass_window_idstringoptionalRequired for the single-window pass segments (`pass_holders`, `pass_holders_not_in_queue`); ignored by the others.
plan_idstringoptionalOptional. Narrows the segment to members on one subscription plan, by plan UUID. Composes with the segment rather than replacing it: `customer` plus a plan reaches people paying for that plan right now, `churned` plus a plan reaches people who held it and left. Rejected for `lead` (never subscribed) and for the pass segments (their plan is implied by the window).
expiring_within_daysintegeroptionalOnly meaningful for `expiring_soon`. How many days ahead to look, 1-90, default 7. A member is counted only if their access genuinely lapses: an auto-renewing subscription is not expiring.
What it returns
{ "data": { "project_id": "7f3d1c92-8b45-4e6a-9d21-5c8e0a4b6f13", "audience": "customer", "pass_window_id": null, "plan_id": null, "expiring_within_days": null, "recipient_estimate": 128, "estimated_seconds": 5, "status": "queued" }}status is always queued — this reports what was addressed, not what was delivered. Subscribe to broadcast.completed for the sent and failed tallies.
Narrowing the audience
plan_id composes with audience rather than replacing it: customer plus a plan addresses people paying for that plan right now, churned plus a plan addresses people who held it and left. The plan and the state describe the same subscription, so a member paying for one plan who once trialled another is not matched by the first form.
Four of the segments describe a subscription rather than a member status, which is what a status cannot express — somebody who cancelled but has three weeks left carries the same status as somebody renewing happily:
| Segment | Addresses |
|---|---|
expiring_soon | Access lapses inside expiring_within_days. |
cancelled_still_active | Renewal is off, but time remains. |
paused | Paused rather than ended. |
trialing_cardless | On trial with no card on file. |
An auto-renewing subscription is never `expiring_soon`
A subscription's end date is rewritten to the new period end on every renewal, so a date inside the horizon describes the next invoice, not an expiry — counting it would place every monthly subscriber in this segment once a month. A member appears only once their access genuinely lapses: renewal is off, or the plan does not renew at all.
Refusals
The tool returns a validation error, and sends nothing, when:
- the project has no connected bot
- the body is empty or longer than 4096 characters
- the audience is unrecognised
- a pass segment is requested by a project whose plan no longer includes passes
- a single-window segment is requested with no
pass_window_id - a
plan_idis sent withleador a pass segment, which cannot be narrowed by plan - a
plan_iddoes not belong to the project
A plan filter the segment cannot use is refused rather than ignored: silently dropping it would return a recipient count for a different audience than the one described, and the caller has no way to notice.
How is this guide?
Last updated on