Skip to main content

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.
It resolves a 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:
A 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:
It goes through the access lists, the handoff gate and the chat’s FIFO queue, and resolves { 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 a ScheduledTask whose cancel() stops it:
These tasks live in memory: a restart forgets them. Give a task an 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:
A new message from the chat re-arms it, and it never fires for a chat in human handoff. For a timeout inside a conversation (an ask the user does not answer), use c.idle() or .timeout() instead. See bot.onIdle(): inactivity per chat.

Durable sends

With BotConfig.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:
See Retries, deferral and the dead-letter queue.

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