Topzee logo
Open menu
Web and AI Integrations

WhatsApp Bot Node.js Twilio Case Study: Building TwoDo

How I built TwoDo, a WhatsApp-based task tracker, using Node.js, Twilio, and MongoDB - a real case study with code.

Topzee5 min read
Abstract WhatsApp bot cover with webhook arrows, message bubbles, and Node.js backend shapes.

A WhatsApp bot Node.js Twilio project is a good way to learn webhooks because the feedback loop is real: a person sends a message, Twilio calls your endpoint, your backend decides what to do, and the user receives a reply inside WhatsApp. TwoDo is a WhatsApp-based task tracker built around that idea.

The product idea is simple: instead of opening a separate app to capture tasks, users can send task commands to WhatsApp and let the bot store, list, and update them.

WhatsApp Bot Node.js Twilio: Why I Built TwoDo

TwoDo came from noticing how often tasks appear inside chat conversations. Opening a separate task app can feel like friction when the thought is already in WhatsApp. A bot makes capture feel closer to the user’s existing habit.

The first thing I wanted to prove was small: can a user send a simple command, have the backend understand it, store it safely, and reply in a way that feels useful rather than noisy? That question makes the technical architecture clearer because every part of the system has to support a quick chat-based workflow.

Architecture Overview

The basic architecture:

WhatsApp message
  -> Twilio WhatsApp webhook
  -> Node.js Express endpoint
  -> Command parser
  -> MongoDB tasks collection
  -> TwiML reply
  -> WhatsApp user

Twilio receives the WhatsApp message and sends an HTTP POST request to your webhook URL. Your server reads the message body, performs the task action, and returns TwiML so Twilio can send a reply.

Setting Up the Webhook Endpoint

Install the basics:

npm install express twilio mongodb dotenv
npm install --save-dev typescript tsx @types/express

Here is a minimal Express webhook:

import "dotenv/config";
import express from "express";
import twilio from "twilio";

const app = express();
app.use(express.urlencoded({ extended: false }));

app.post("/webhooks/whatsapp", async (req, res) => {
  const from = String(req.body.From ?? "");
  const body = String(req.body.Body ?? "").trim();

  const twiml = new twilio.twiml.MessagingResponse();
  twiml.message(`Received from ${from}: ${body}`);

  res.type("text/xml").send(twiml.toString());
});

app.listen(3000, () => {
  console.log("TwoDo webhook listening on http://localhost:3000");
});

In local development, expose the server with a tunnel such as ngrok, then paste the public URL into Twilio’s WhatsApp sandbox webhook settings:

https://your-tunnel-url.ngrok-free.app/webhooks/whatsapp

Parsing Incoming Task Commands

A task bot needs a small command language. Start with a few commands:

  • add Buy fuel
  • list
  • done 3
  • help

The parser can be plain TypeScript:

type TaskCommand =
  | { type: "add"; title: string }
  | { type: "list" }
  | { type: "done"; taskNumber: number }
  | { type: "help" }
  | { type: "unknown"; input: string };

function parseTaskCommand(input: string): TaskCommand {
  const text = input.trim();

  if (/^help$/i.test(text)) return { type: "help" };
  if (/^list$/i.test(text)) return { type: "list" };

  const addMatch = text.match(/^add\s+(.+)$/i);
  if (addMatch?.[1]) {
    return { type: "add", title: addMatch[1].trim() };
  }

  const doneMatch = text.match(/^done\s+(\d+)$/i);
  if (doneMatch?.[1]) {
    return { type: "done", taskNumber: Number(doneMatch[1]) };
  }

  return { type: "unknown", input: text };
}

This is intentionally boring. Bots become easier to improve when the command parser is separate from the webhook handler.

Storing Tasks in MongoDB

Create a database helper:

import { MongoClient } from "mongodb";

const client = new MongoClient(process.env.MONGODB_URI ?? "");

export async function tasksCollection() {
  await client.connect();
  return client.db("twodo").collection("tasks");
}

Then handle commands:

async function handleCommand(userPhone: string, input: string) {
  const command = parseTaskCommand(input);
  const tasks = await tasksCollection();

  if (command.type === "add") {
    await tasks.insertOne({
      userPhone,
      title: command.title,
      completed: false,
      createdAt: new Date(),
    });

    return `Added: ${command.title}`;
  }

  if (command.type === "list") {
    const openTasks = await tasks
      .find({ userPhone, completed: false })
      .sort({ createdAt: 1 })
      .limit(10)
      .toArray();

    if (openTasks.length === 0) return "No open tasks.";

    return openTasks
      .map((task, index) => `${index + 1}. ${task.title}`)
      .join("\n");
  }

  if (command.type === "help") {
    return "Try: add Buy fuel, list, or done 1";
  }

  return "I did not understand that. Send help for examples.";
}

Then wire it into the webhook:

app.post("/webhooks/whatsapp", async (req, res) => {
  const from = String(req.body.From ?? "");
  const body = String(req.body.Body ?? "").trim();

  const reply = await handleCommand(from, body);
  const twiml = new twilio.twiml.MessagingResponse();
  twiml.message(reply);

  res.type("text/xml").send(twiml.toString());
});

Challenges to Review Honestly

The exact TwoDo details should be replaced with real notes, but the common challenges in this kind of project include:

  • Designing commands that feel natural in chat.
  • Handling duplicate messages or repeated webhook delivery.
  • Mapping WhatsApp sender IDs to users.
  • Keeping replies short enough for chat.
  • Avoiding accidental task updates when parsing is ambiguous.
  • Deciding what belongs in MongoDB versus temporary state.

For a real case study, add the specific bug that took longest, what you changed, and what you would build differently now.

What’s Next for TwoDo

Useful next steps could include reminders, task due dates, natural-language parsing, voice note transcription, or a small web dashboard for reviewing task history.

The important product question is whether WhatsApp remains the fastest place to capture the task. If yes, the bot should stay lightweight and helpful instead of becoming a full app trapped inside chat.

If you are exploring a WhatsApp, chatbot, or workflow automation idea, Beyond Just Digital can help turn the webhook sketch into a real service with fewer sharp edges.