Skip to main content
Level: Intermediate · Tags: WhatsApp, Forms, Persistence, SQLite Features shown: persistent flows · validation and maxAttempts · forms · menus with choose · SQLite store · user data (ctx.user) · slash commands

Prerequisites

  • Node.js 22.12 or newer and an empty folder with "type": "module" in its package.json (Getting started).
better-sqlite3 is a native module: with pnpm, approve its build script (Installing better-sqlite3).

The complete bot

How it works

  1. The store. SQLiteStore writes chats, flow checkpoints and user data to bookings.db. Without it the bot uses an in-memory store and forgets everything on restart.
  2. A persistent flow. .persistent() saves a checkpoint when the flow starts and after every step. After a restart, the user’s next message wakes the flow and the pending question is asked again: that message is not taken as the answer. .timeout('10m') ends a booking nobody finishes.
  3. Validation. validate returns true or the text to send before asking again. maxAttempts: 3 counts every answer, the first one included; after the third wrong answer the conversation ends with the built-in “too many attempts” message.
  4. A form. .form() collects typed fields: date accepts YYYY-MM-DD and DD/MM/YYYY and checks min, select shows a numbered menu and stores the chosen option. flatten: true copies each answer to c.state.day and c.state.time. On the WhatsApp Cloud API the same form can go out as one native WhatsApp Flow; everywhere else it is asked field by field. A restart in the middle of the form asks it again from its first field, because the form is one step.
  5. Confirmation. .choose() stores the chosen key (yes or no). In the .do() step, c.end(text) stops the flow with a last message, so nothing is saved when the user says no.
  6. Saving the booking. In flow steps ctx is a plain runtime message, so userOf(ctx) gives you the same profile that handlers reach as ctx.user. my appointment reads it back, also after a restart.
min: today() is computed when the bot starts. For a bot that runs for days, check the date again in a .do() step, or restart the bot daily.

Try it

:reset in the simulator builds a fresh bot from the factory, which is what a restart does: the new SQLiteStore opens the same file and the flow resumes.

Run it on WhatsApp or Telegram

node bot.js shows a QR code: scan it from WhatsApp → Linked devices. For Telegram, install @botmesh/telegram and swap the provider in the start block for new TelegramProvider({ token: process.env.TELEGRAM_TOKEN ?? '' }), as in Or connect Telegram.

Next steps