Send to one chat
bot.send(chatId, content, options?) sends to any chat from anywhere (a job, a
webhook, an admin panel). A string is sent as text; anything else is the
transport-neutral content union ({ kind, ... }). It runs the
before('send') / after('send') hooks like a reply from a handler.
SentMessage ({ ref, id, chatId, raw, skipped? }); skipped is
set when a before('send') hook aborted the send. chatId is the opaque id the
channel uses (store the ctx.from of an earlier message). See
Sending outside a handler.
Broadcast to many chats
bot.broadcast(chatIds, content, options?) sends one content to many chats in
batches, without holding the per-chat inbound queues:
RateLimitedError pauses the whole broadcast and retries that chat; other
errors are recorded and the broadcast goes on. From inside a handler, launch it
with void bot.broadcast(...). Every option (concurrency, retries,
signal, onProgress, onFail…) is in Broadcasts.
Start a flow from code
bot.trigger(name, { chatId, userId?, isGroup?, data?, ifActive?, body? })
starts a registered flow or bot.conversation without an inbound message. The
user’s replies then reach it as usual. data seeds c.state:
{ started, name, chatId, userId, reason? } once the conversation
is entered (not when it ends). A subject that already has a conversation is
left alone (reason: 'busy') unless you pass ifActive: 'replace'.
bot.triggerFlow() is a deprecated wrapper over it. Details in
Starting a flow programmatically.
Scheduled jobs
The bot has a scheduler for code that runs on a timetable. Each call returns aScheduledTask whose cancel() stops it:
id to make
it durable (stored schedule, missed-run policy, one replica per run). See
Scheduler & durable tasks.
Follow up on inactivity
bot.onIdle(duration, handler) runs once when a chat stays quiet for
duration after its last processed message; ctx is that message’s context,
so ctx.reply() answers in the right chat:
ask the user does not
answer), use c.idle() or .timeout() instead. See
bot.onIdle(): inactivity per chat.
Durable sends
WithBotConfig.durability on, bot.enqueue(chatId, content, options?)
journals the send and returns its outbox key at once; a background sweeper
delivers it with retries and a dead-letter queue. An idempotencyKey that
already exists is a no-op, which makes jobs that may run twice safe:
Channel limits
Not every channel lets a bot write first:- WhatsApp Cloud API, Twilio and Gupshup (WhatsApp): outside the 24-hour
customer-service window a free-form message rejects with
OutsideSessionWindowError; reopen the chat with an approved template. See 24-hour window and pricing. - Messenger: after the 24-hour window, send with a message tag (see Messenger).
- TikTok (experimental): the bot cannot start a conversation.
- Baileys (WhatsApp Web): unsolicited bulk messages are the fastest way to
get a number banned. Keep the rate limiter on and pace
broadcasts with
delayBetween.
Next steps
- Messages & media: every content kind
bot.send()accepts. - Scheduler & durable tasks: cron syntax, time zones and durable tasks.
- Durable processing: the inbox/outbox journals behind
bot.enqueue(). - Example: reminder bot and scheduled daily digest.