WA Daily Scrum Bot
WhatsApp bot that monitors a team group chat and sends escalating standup reminders with a weekly leaderboard
Developer
Jun 2026
On this page
- The problem
- The approach
- Puppeteer-based WhatsApp automation
- Bilingual keyword-pair detection, with an anti-cheat pass
- Tracking not just whether someone reported, but whether their work is actually moving
- Closing the gaming loophole in the carry state
- Gating risky features before trusting them to broadcast
- Self-service, on demand
- Outcome
The problem
Scrum teams that run daily standups over WhatsApp rely on members self-reporting. Without automation, a team lead has to manually check who hasn't posted and ping them — every day, multiple times. For a 14-person team this is tedious and easy to forget, leading to reports being missed or submitted too late to be useful.
The OIKN THD Team needed an automated system that monitors the group chat, sends escalating reminders through the day, and ends each day with a ranked summary — in both Indonesian and English, since reports arrive in both languages.
The approach
Puppeteer-based WhatsApp automation
WhatsApp has no official bot API. The standard approach is to automate the WhatsApp Web browser interface using Puppeteer — whatsapp-web.js wraps this and provides a Node.js event API. Running a full browser inside a Docker container required installing system Chromium, skipping Puppeteer's bundled download, and pinning the container hostname to prevent Chromium profile lock conflicts across restarts. An entrypoint.sh deletes stale Singleton lock files before starting Node — without it, any unclean container exit blocks the next startup.
Bilingual keyword-pair detection, with an anti-cheat pass
A message counts as a standup report if it contains known phrase pairs — yesterday + today, kemarin + hari ini, and similar bilingual combinations — making detection language-agnostic and robust to different report formats. A second pass strips those trigger keywords and flags messages with too little content remaining, catching members who game the bot with bare-keyword submissions and calling them out publicly in the group.
Tracking not just whether someone reported, but whether their work is actually moving
Beyond report detection, the bot tracks individual bullet items across days. detectSection() classifies each line as Yesterday/Today/Impediment, and a Jaccard-similarity taskSimilarity() check (0.7 threshold) recognises that "fix login bug" and "fixing the login bug issue" are the same task reappearing under "Today" without ever graduating to "done" — flagged once it's carried three or more days. A whitelist excludes recurring duty words (piket, cuti, standby) from ever being flagged, and a per-member staleHelp config can reframe a stuck task for an overloaded QA lead as an offer of named-helper support instead of a bare callout.
Closing the gaming loophole in the carry state
The stale-task day-count originally reset every Monday, which meant a member could dodge the flag simply by leaving a stuck task off one day's list — resetting the streak for free. The fix moved from a per-week bucket to a persistent cross-week carry structure where the counter only clears when the same task is later reported done via similarity match, not by omission. Making that safe required a matching idempotency guarantee: on every restart, the bot re-fetches the last 500 group messages and chronologically replays them to rebuild its carry, ledger, and history state, so a redeploy never silently loses in-progress tracking.
Gating risky features before trusting them to broadcast
The Friday weekly recap aggregates several independently fallible calculations — completion rate, an on-time consistency streak, a team-health digest, stale-task carry — any one of which could produce a wrong or embarrassing number in front of the whole team. It shipped behind a weeklyRecapAutoSend config flag plus a manual SIGHUP-triggered send, so the aggregated output could be sanity-checked against real data for a full cycle before automation was trusted to run unattended.
Self-service, on demand
!mystats lets any member query their own on-time percentage, current streak, and weekly completion rate without waiting for Friday; !rank posts the same consistency leaderboard on demand; !help advertises both from the morning kickoff message.
Outcome
Deployed and running in production on a GCP VM for the OIKN THD Team's daily standups. What began as report-detection and escalating reminders grew well past that scope — 25 commits over 18 days, more than ten of them landing in a single three-day stretch once the task-tracking and analytics layer took shape. The Friday recap is now a composite of several independently built subsystems (completion rate, consistency streaks, team-health digest, stale-task carry) rather than a single ranking, all served from flat JSON state files with no database, mounted as a Docker volume so restarts never lose a day's progress.
- 14
- 8
- 3
- 25
Have a project like this?
If you need a system built with the same care — clear scope, solid execution — let's talk.
Start a project