Skip to content

Repository files navigation

WhatUp logo

WhatUp

What if WhatsApp and iMessage had a child?

Meet WhatUp: it inherited its mother's obsession with being on every phone on Earth and its father's refusal to make anything that isn't a blue bubble. It's SMS, which means it also inherited something from a grandparent nobody talks about.


WhatUp is a conversational SMS platform. You text it, it thinks about what you said for 3-15 seconds (it was raised to think before it speaks), and it texts you back. Every message. Always. Exactly once. An admin web app lets you watch every conversation update live, like a helicopter parent with a dashboard.

Demo

The application

A conversation started from the admin composer: the inbound message, the processing status while the reply is generated, and the reply arriving live over SSE.

whatup-admin.mp4

The observability dashboard

The provisioned WhatUp Overview dashboard: message throughput and outcomes, reply and pipeline latency, queue depths, recent traces, and trace-correlated logs.

grafana-overview grafana-traces-logs

Features

  • Guaranteed replies. Every inbound SMS receives a reply within 3-15 seconds.
  • No message loss. Failed processing is retried with a delay; after three attempts the message is parked in a dead-letter queue for inspection. Nothing is dropped.
  • Duplicate-safe. Whether a message is sent twice or the carrier delivers it twice, Postgres constraints guarantee exactly one reply per inbound message. The mock carrier deliberately duplicates webhook deliveries to exercise this path.
  • Live admin UI. Every conversation updates in real time over Server-Sent Events: message arrival, processing status, and the reply, without polling.
  • Cached reads. Admin list and detail responses are served cache-aside from Redis, invalidated by the same change events that drive the live UI.
  • Optional AI replies. Set REPLY_DRIVER=claude to generate replies with Claude instead of the built-in keyword bot.
  • Full observability. Traces, metrics, and logs for every message's journey, with a provisioned Grafana dashboard. See OBSERVABILITY.md.

How it works

Every message, including the ones sent from the admin UI, travels through the (mock) carrier: there is a single ingestion path.

flowchart LR
  UI[Admin UI] -- "send SMS" --> TW[twilio-mock]
  UI -- "read conversations" --> API[API]
  TW -- "webhook" --> API
  API -- "enqueue" --> MQ[(RabbitMQ)]
  API -- "cache-aside" --> RD[(Redis)]
  API -- "on cache miss" --> DB[(Postgres)]
  MQ --> WK[Worker]
  WK -- "persist + reply" --> DB
  WK -- "send reply" --> TW
  WK -. "change hint" .-> API
  API -. "evict cache" .-> RD
  API -. "SSE" .-> UI
Loading

The webhook answers in milliseconds and enqueues; the worker claims each message atomically, generates the reply, sends it back through the carrier, and records everything in Postgres, which is the arbiter of idempotency, ordering, and truth. Admin reads are served cache-aside from Redis, falling through to Postgres on a miss; the change hints that drive the SSE stream also evict the affected cache keys. The full architecture, trade-offs, and failure walkthroughs live in DESIGN.md.

Tech stack

Layer Tech
Backend NestJS 11 on Node 20+, TypeScript end to end
Database PostgreSQL 16 with TypeORM: entities for schema, raw SQL where the guarantees live
Queueing RabbitMQ 4 via amqplib: a durable work queue for the pipeline, a fanout exchange for live updates
Cache Redis 7 behind a CacheStore port; an in-memory driver is available via CACHE_DRIVER=memory
Frontend React 19 + Vite, live-updated over Server-Sent Events (EventSource)
Shared types whatup-contracts, an npm-workspaces package both apps import, so the wire contract is a compile error, not a runtime surprise
Carrier Express (twilio-mock), implementing Twilio's webhook and Messages API surfaces
AI replies Claude Agent SDK (REPLY_DRIVER=claude)
Observability OpenTelemetry SDK (auto-instrumented http/express/pg/amqplib + custom spans and metrics) → grafana/otel-lgtm: Grafana, Prometheus, Tempo, Loki in one container
Infra Docker Compose for Postgres, RabbitMQ, Redis, and the observability stack
Quality Jest (unit + integration suites), ESLint + Prettier, class-validator at the HTTP boundary

Engineering concepts

WhatUp is a distributed-systems exercise underneath, and these are the ideas doing the work, each one detailed in DESIGN.md:

  • Enqueue-first ingestion. The webhook validates and enqueues, with no database in the path, so it answers in milliseconds, survives a Postgres outage, and beats carrier webhook timeouts. If the broker is down it returns 500 and the carrier retries: the failure mode is the retry mechanism. (§2)
  • Idempotency, because duplicates are the normal case. Carriers re-POST, queues redeliver, workers crash mid-flight. Three Postgres constraints make all of it safe: a unique provider_message_id collapses duplicate deliveries onto one row, an atomic claim (UPDATE … WHERE status IN (…)) lets exactly one worker process it, and a unique in_reply_to guarantees at most one reply per inbound message. The database is the arbiter, not application memory. (§4)
  • Retries with delay. A failed delivery is republished to a TTL retry queue whose expiry dead-letters it back to the main queue: redelivery-after-delay without a scheduler. (§3)
  • Dead-letter queue. After three failed attempts the message is parked in whatup-inbound.dlq with its attempt history: never dropped, never poison-looping, available to an operator. (§3)
  • Stale-claim takeover. A worker that dies after claiming a message holds the claim only for STALE_CLAIM_SECONDS; after that any worker may take the row over. Crashed workers don't strand messages. (§4)
  • Ports & adapters. The application depends on MessageQueue, MessagingClient, ReplyGenerator, ChangeEventBus, and CacheStore interfaces; RabbitMQ, Twilio/Zenvia/fake, Claude/fake, and Redis/memory are swappable drivers behind DI tokens. Repositories own all SQL; adapters translate rows at every boundary. (§6)
  • Live updates without polling. Every write publishes a data-free change hint to a RabbitMQ fanout exchange; every API instance forwards it to its SSE clients, which re-fetch. Hints are best-effort by contract: losing one costs staleness, never correctness. At production scale this becomes CDC. (§6, §9)
  • Cache-aside with event-driven invalidation. The admin read path is cached in Redis; the same change hints that drive the SSE stream evict the affected keys, and a TTL bounds staleness if a hint is missed. Cache operations are best-effort: an unreachable Redis degrades to uncached reads, never to failed requests. Idempotency and claims are never cached. (§6)
  • Horizontal scaling. One codebase, APP_MODE=api|worker|all, so ingestion and processing scale independently; prefetch bounds per-worker concurrency. (§1, §9)
  • Observability as a feature. One distributed trace follows a message from webhook through the queue (context propagated in message headers) to reply generation and the outbound send; RED-style metrics (throughput by outcome, latency histograms, queue depths) and trace-correlated logs land in a provisioned Grafana dashboard. (OBSERVABILITY.md)
  • Tested where the guarantees live. Unit tests mock the ports; integration tests hit real Postgres to prove the concurrency invariants (concurrent duplicate deliveries converge on one row, exactly one claimer wins), because those guarantees are enforced by the database, not the application. (§8)

Repository layout

Package What it is
whatup-backend NestJS API and worker (one codebase, APP_MODE selects the role)
whatup-admin React admin UI
twilio-mock Standalone Twilio impersonator so the full carrier loop runs locally
whatup-contracts Shared TypeScript types for the API wire contract
observability/ Grafana dashboard and datasource provisioning

Quickstart

You need Node 20+ and Docker.

npm install                                    # installs all workspaces, builds the shared contracts
cp whatup-backend/.env.example whatup-backend/.env
npm run dev                                    # postgres + rabbitmq + redis (docker) + backend + twilio-mock + admin UI

Then open:

URL What's there
http://localhost:5173 Admin UI
http://localhost:3000 Backend API (webhook + read-only admin endpoints + SSE)
http://localhost:4010 twilio-mock
http://localhost:15672 RabbitMQ management (whatup / whatup)

Send your first message

Use the composer in the admin UI, which plays the role of the phone. Or deliver an inbound message as the carrier would:

curl -X POST http://localhost:4010/simulate/inbound \
  -H 'Content-Type: application/json' \
  -d '{"from": "+15550001111", "body": "hello?"}'

The conversation appears in the UI, shows processing… for 3-15 seconds, and receives its reply. The default reply driver responds to the keywords BOOK and CANCEL and echoes anything else; set REPLY_DRIVER=claude in whatup-backend/.env for LLM-generated replies (see the notes in .env.example).

Observability

npm run obs        # Grafana + Prometheus + Tempo + Loki, one container

Grafana is at http://localhost:3001, and the WhatUp Overview dashboard is the home page: throughput, reply latency, queue depths, live traces, and trace-correlated logs. Setup, credentials, and troubleshooting are in OBSERVABILITY.md.

Tests

npm test                    # unit suite (mocked ports and adapters)
npm run test:integration    # repository guarantees against real Postgres, in a throwaway whatup_test DB

All the scripts

Command What it does
npm run dev Start everything (infra + backend + twilio-mock + admin)
npm run obs / npm run obs:down Start / stop the observability stack (Grafana data survives)
npm run db:purge Wipe messages, conversations, queues, and cache
npm test Unit tests
npm run test:integration Integration tests against real Postgres

WhatUp is a parody project and is not affiliated with WhatsApp (Meta) or iMessage (Apple). It originated as a take-home technical assessment; the original brief is in ASSESSMENT.md and the technical design in DESIGN.md.

About

Conversational SMS backend with enqueue-first ingestion, an idempotent RabbitMQ worker pipeline, Claude-powered replies, and a real-time admin UI.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages