# cm WhatsApp Reminder Bot Self-hosted WhatsApp reminder bot. Pair multiple WhatsApp accounts via a browser-based PWA, schedule recurring reminders to groups, and watch the run history all from a phone home-screen icon. ## Status **Plans 1, 2, and 3 complete.** The web app at `wabot.04080616.xyz` is the primary control surface; the Telegram bot has been removed. What's working today: - **Self-hosted Next.js 16 PWA** — installable on a phone home screen. Mobile-first single-row header with a slide-out drawer; desktop sidebar. - **Live QR pairing** — server-side Baileys session feeds the QR payload directly into the browser via Server-Sent Events. Scan, see "✅ Connected" within seconds, auto-redirect. - **Multi-account, multi-group reminders** — 5-step wizard (Account → Message → When → Groups → Review) plus per-section edit pages so you don't have to walk the wizard end-to-end to fix one field. Active recurrence picker covers Daily / Weekly / Monthly / Yearly with multi-rule support and per-rule fire-time pickers; the rendered description reads as plain English ("Every week on Mon, Wed, Fri at 09:00") not raw cron. - **Multi-message stacks** — a reminder can carry multiple ordered parts (text + media), fired in sequence with a 1.5 s gap. Media files swap at any time from the Edit Message page. - **Smart media handling** — per-kind WhatsApp size caps (5 MB image, 16 MB video/audio, 100 MB document). HEIC photos and `.mov` videos fall back to the document delivery path so they reach the recipient as a downloadable file instead of failing silently. - **Swipe-to-act rows** — on mobile, swipe a reminder or activity row left for Delete or right for Pause/Restart/Archive. iOS-Mail style. - **Activity tab** — last 200 runs with status filters (Success / Partial / Failed / Skipped) plus an Archived tab. Archive a noisy run to keep the main list readable; restore later. Hard-delete always available. Run history survives a reminder deletion. - **Auto-reconnect on transient drops; restart-survival via Baileys session persistence.** Pair once, the device stays linked across container restarts. - **All actions audited.** Reminder run history queryable from the UI; per-run target results (sent / failed / skipped) preserved even when the underlying group is removed. Test count: **249 web + 31 shared + 26 bot = 306** passing. ## Host requirements Only Docker. No host Node, pnpm, or any other language toolchain — everything runs in containers via the long-lived `tools` sidecar. ## Architecture in one paragraph Two app containers and one external dependency. `bot` (Node.js) holds the live Baileys WhatsApp sessions, the pg-boss scheduler, and a Postgres `LISTEN bot.command` consumer. `web` (Next.js 16 App Router + React 19) is stateless UI: Server Components for reads, Server Actions for mutations, an SSE endpoint for live updates, `@serwist/next` for the PWA shell. `tools` is a long-running Node 22 + pnpm sidecar used for installs / tests / typechecks / migrations so the host doesn't need a Node toolchain. Postgres lives external at `192.168.0.210` in a `wabot` database. All cross-service communication goes through Postgres (`LISTEN/NOTIFY` for events, table writes for state). Full design spec: [`docs/superpowers/specs/2026-05-09-web-app-design.md`](docs/superpowers/specs/2026-05-09-web-app-design.md) ## Quick start (dev) Prerequisites: Docker, the `wabot` database + `waBot` role on `192.168.0.210` (with a `pg_hba.conf` line permitting `192.168.0.0/24`). ```bash # 1. Configure env cp envs/.env.example .env.development # edit .env.development: real DATABASE_URL, plus the LAN host to expose scripts/gen_auth_secret.sh --write # 2. Bring up the stack, install deps NO_SUDO=1 scripts/dev.sh up NO_SUDO=1 scripts/dev.sh pnpm install # 3. Apply migrations and seed your operator row NO_SUDO=1 scripts/db.sh migrate NO_SUDO=1 scripts/db.sh seed # 4. Open the web app # Local: http://localhost:9000 # LAN: http://:9000 (e.g. http://192.168.0.253:9000) # Public: https://wabot.04080616.xyz (whatever your reverse proxy serves) ``` Pair an account: `/accounts` → "New Account" → enter a label → "Pair WhatsApp" → scan the QR with WhatsApp's "Linked Devices". PWA install: phone Chrome → menu → "Install App" / "Add to Home Screen". Launches fullscreen. `NO_SUDO=1` is the right setting if your user is in the `docker` group (the default for this repo). Drop it if you need `sudo docker`. ## Manual test runbook End-to-end checks that unit tests can't cover (live Baileys, WhatsApp delivery, swipe gestures): [`docs/superpowers/specs/manual-test-web.md`](docs/superpowers/specs/manual-test-web.md). ## Layout - `apps/bot/` — Baileys WhatsApp + pg-boss scheduler + LISTEN/NOTIFY command consumer - `apps/web/` — Next.js 16 App Router PWA - `packages/db/` — Drizzle schema and migrations - `packages/shared/` — cross-app helpers (rrule, media paths, timezones, WhatsApp media classifier) - `docs/superpowers/specs/` — design specs and manual test runbooks - `docs/superpowers/plans/` — implementation plans - `docker/` — Dockerfiles (`tools.Dockerfile`, `bot.Dockerfile`, `web.Dockerfile`) - `scripts/` — `dev.sh`, `db.sh`, `gen_auth_secret.sh` ## Scripts All `pnpm`/`tsx`/`drizzle-kit` invocations run inside the `tools` container, so no host Node is needed. | Script | Purpose | |---|---| | `scripts/dev.sh up\|down\|logs\|status\|build\|exec\|pnpm\|shell\|restart-bot` | Stack lifecycle and tools-container shell | | `scripts/db.sh migrate\|generate\|studio\|seed\|reset` | Drizzle migration helper | | `scripts/gen_auth_secret.sh [--write]` | Generate `AUTH_SECRET` (host-only, no Node needed) | Set `NO_SUDO=1` if your user is in the docker group (recommended). ## Deferred - **Standalone media library** browser (currently media is uploaded per-reminder). - **E2E browser tests** (Playwright) on the swipe and pairing flows. - **Auth** (passkeys / email-password) — bring back if URL exposure becomes a concern. Today the app trusts whatever's in front of the reverse proxy. - **Multi-operator** — schema supports `operator_id` on every row, but the seed runs as a single operator and there's no /signup or invite flow yet.