Mohith Korangi Work with me

All work

AI productDec 2025 – nowLive

AskMyChats

Save anything from WhatsApp, find it later and ask about it.

AskMyChats saved inbox in dark mode, showing notes, links and forwarded messages from a demo account

The link from three months ago, the address someone dropped in a group, who owes whom: important things get buried in busy WhatsApp chats. AskMyChats lets you forward a message to a WhatsApp number or import a whole chat export, then search, organise and ask questions about it.

Measured

to store 40,000 imported messages
1.5 s
added when the same chat is imported twice
0 rows
spec scenarios, each proven by a named test
574

Problem

I measured the first architecture from India before scaling it: about 220 ms per database query, a 2.4 s cold first byte, and imports at roughly 220 ms per message, because the app ran in US East with the database in Singapore. Re-importing a chat also duplicated every message. The fix was architectural, not a patch.

Architecture

server functions gzip batches events event + job, one txn jobs media summaries, answers Web app React 19, TanStack Parse worker export unzipped on device WhatsApp Meta Cloud API App server auth, limits, entitlements Webhook signed, stores first Postgres data, queue, meters LLM layer Claude, OpenAI, GLM Job worker pg-boss queues Object storage media on R2
How a message gets saved, imported and answered
  • Interface
  • External
  • Service
  • Storage
  • Model
  • Worker

Messages arrive two ways. A forwarded message hits a signed webhook that stores the event and queues its job in the same transaction, then answers Meta immediately. A chat export is unzipped and parsed in a Web Worker on the user's device, and only the chosen date range is sent, in gzip batches of about 2,000 rows. Every server function and job passes through one middleware chain: auth, rate limits, input validation and an entitlement check.

Decisions

  • One region, one long-running server, Postgres for everything

    Queue, search and data all live in Postgres (pg-boss, full-text and trigram search) in a single India region. That removed the cross-continent round trips behind the 220 ms queries, and one database is far less to run than Redis plus a search engine.

  • Server functions as the only API

    The first version had four API styles, so auth and limits lived in four places. Now there is one typed entry point with one middleware chain, and a static check fails the build if any function or job skips its entitlement gate.

  • Store first, work later

    Meta can send the same webhook up to three times. Each item gets one row keyed by a SHA-256 hash and its job is queued in the same transaction, so processing happens exactly once in our database. Replies go out through a transactional outbox because Meta's send API has no idempotency key.

  • Parse imports on the device

    Exports with media can be hundreds of megabytes. Parsing in the browser means no huge upload, the raw file never leaves the phone, and content hashes make a re-import add zero rows.

  • Entitlements before features

    A feature catalog, plans, usage meters and kill switches came straight after auth. A kill switch pauses any feature for everyone within 30 seconds, with no deploy.

Hard parts

  • Imports that can't double-count

    Each batch runs in one transaction with a fixed lock order, inserts with ON CONFLICT DO NOTHING and records its offset, so a retried batch is a no-op. Storing 40,000 rows takes 1.5 s on the database side, and importing the same 40,000 again inserts nothing in 383 ms.

  • A memory attack found in audit

    One signed-out 512 MiB request pushed the server from 59 MB to 1.47 GB. Every server-function request now has a 2 MiB cap that is checked before the body is read.

  • A deadlock between two features

    Cancelling an import while deleting the account could deadlock Postgres. An audit caught it, and both paths now take locks in the same order.

  • Privacy by construction

    Raw webhook content is erased once processed, WhatsApp identity uses Meta's user id instead of the phone number, logs carry ids only, and every query is scoped to the signed-in user.

  • AI that treats chat text as data

    The LLM layer sits behind one provider interface for Claude, OpenAI and GLM, with a prompt-injection sanitizer, weighted hybrid search for context and a per-call cost log.

Quality

  • 145 Vitest files run against a real Postgres, and 23 Playwright specs run on the production build against a fake Meta server.
  • 10 capability specs with 574 scenarios. A scenario only counts when a running test names it.
  • Local CI on every pull request: types, lint, tests, production build, semgrep, gitleaks and a dependency audit.