WA Daily Scrum Bot
A WhatsApp bot that chases missing standup reports for a 14-person team and flags tasks stuck for days

- team members monitored every working day
- 14
- reminder checks a day, each one firmer
- 4
- days before a stuck task is flagged
- 3
- self-service chat commands
- 3
On this page
The problem
The OIKN THD team, a 14-person software team, runs its daily standup in a WhatsApp group. Each member posts what they did yesterday and what they plan today. Without help, the team lead has to check who hasn't posted and remind them, several times a day, every day. For 14 people that is tedious and easy to forget. Reports got missed or arrived too late to be useful.
The team needed something that watches the group, sends reminders that get firmer through the day, and closes each day with a ranked summary. Reports come in Indonesian and English, so it had to understand both.
What I built
The bot drives WhatsApp Web, because there is no bot API
WhatsApp has no official API for group bots. The business API needs Meta's approval and costs money, which doesn't fit an internal team tool. So the bot runs WhatsApp Web in a browser inside a container. It checks four times a day, at 09:00, 10:00, 11:00 and 14:00, and each reminder is firmer than the last. It only sends a message when someone is still missing. When everyone has reported, it stays quiet.
A report counts when it has both halves
A message counts as a standup report if it contains a pair of phrases, such as "yesterday" and "today", or "kemarin" and "hari ini". That works in both languages and in any format. A second check removes those keywords and looks at what's left. If almost nothing remains, the bot calls out the bare-keyword report in the group. National holidays and each member's leave dates are set in the config, so nobody gets chased on a day off.
It notices tasks that stop moving
The bot also reads each bullet point and follows it across days. It treats "fix login bug" and "fixing the login bug issue" as the same task, based on how many words they share. A task that stays under "today" for three days or more without being reported done gets flagged. Routine duties like piket, cuti and standby are never flagged. For the team's QA lead, who carries a heavy load, a stuck testing task comes with an offer of help from a named teammate.
Leaving a stuck task out doesn't reset it
At first the stuck-task count reset every Monday. A member could also dodge it by leaving the task off one day's list. I changed it so the count carries across weeks and clears only when the same task is reported done. To keep that safe across restarts, the bot rereads the last 500 group messages on startup and replays them in order. A redeploy never loses the tracking.
I tested the Friday recap before letting it post
The Friday recap combines a completion rate, an on-time streak, a team-health comparison with last week and the list of stuck tasks. Any one of them could put a wrong number in front of the whole team. I shipped it with automatic sending switched off and a manual send. After checking the output against a full cycle of team data, I turned automatic sending back on.
Result
The bot runs in production for the team's daily standups and monitors 14 members on working days. I built it in 18 days in June 2026. It started as report detection and reminders, then grew into task tracking, weekly analytics and self-service commands. Members type !mystats for their own on-time rate, streak and completion rate, !rank for the leaderboard, and !help for the list. State lives in plain files on a mounted volume, so a restart keeps the day's progress. I also built a React dashboard as a demo of the schedule and messages. It isn't connected to the live bot.
Under the hoodTechnical detail for engineers
- Node.js 18 with
whatsapp-web.js(Puppeteer and Chromium),node-cronfor scheduling andqrcode-terminalfor linking the phone. The session depends on a linked phone staying online and can break when WhatsApp updates its web client. - The Docker image is
node:18-bookworm-slimwith system Chromium. Puppeteer's bundled download is skipped and the container hostname is pinned to avoid profile-lock conflicts. Anentrypoint.shdeletes stale ChromiumSingletonlock files before Node starts. - Mid-project, WhatsApp moved contact IDs from the
@c.usformat to opaque@lidIDs, which broke member matching. I logged sender IDs from live messages and updated the member config. - Detection uses two keyword strings plus three bilingual keyword pairs.
detectSection()classifies lines as Yesterday, Today or Impediment.parseSectionTasks()extracts bullets.normalizeTask()andtaskSimilarity()use Jaccard token overlap with a 0.7 threshold.CONFIG.staleTask.minDaysis 3..week-state.jsonholdsreports,carry,ledgerandhistory. Daily state lives in.bot-state.json.- Eight daily cron jobs: kickoff, an 08:02 carry nudge at a two-day threshold, four checks, the end-of-day summary and the weekly recap slot. The Friday recap posts at 17:05. A Monday recap re-posts last week's stuck tasks.
- On-time means before 09:00 WITA, over a rolling 4-week, 20-working-day window. Streaks tolerate leave days.
- Per-member settings
staleHelpandstalePriority;weeklyRecapAutoSendgates the recap.SIGUSR1fires a reminder,SIGUSR2prints the recap to the logs, andSIGHUPsends it. !mystatsreplies in the group with an @mention.!leaderboardis an alias for!rank.- GitHub Actions builds the image to GHCR.
bot.jsis 1,166 lines and the dashboard 386. 25 commits from 10 to 28 June 2026.
Screenshots
Hiring, or building something similar?
I'm open to full-time roles and select freelance work.