How ThaiPo is built
A case study: one developer, one production system.
ThaiPo is an AI translator that lives inside LINE chats. Add it to a chat and every message is translated both ways between Thai and the other person's language, in the chat itself, with no copying and no switching apps. It is designed, built and operated by Steven Acreman, working alone: the product, the backend, the web app, the prompts, the billing and the on-call.
Live usage
These figures are read from the production database when this page loads, rounded down.
The system at a glance
- Three apps on Fly.io, five machines, one region (Singapore). A Next.js web app (marketing site, dashboard, admin console and the only public ingress), a Go serving plane (ingest, dispatch and translation) and a Go learning plane (compaction, memory building, evals and cleanup jobs). The two Go planes have no public address.
- Object storage is the system of record. Every inbound message and every translation is written to an S3-compatible bucket as an append-only log before anything else happens. Everything derived from messages can be rebuilt by replaying it.
- Postgres holds account and derived state: users, subscriptions, chats and their settings, learned terms, corrections and costs. It runs as a primary with a streaming standby.
- Redis holds the hot queues, leases and caches, one instance per shard. Chats are spread over eight shards by a jump consistent hash of the chat id, and Redis can be wiped and refilled from the log.
- Models are routed per job and per plan.Anthropic Claude and DeepSeek translate text. Google Gemini transcribes voice notes, watches video, reads PDFs, speaks replies and captions live calls. Call media runs through Cloudflare's realtime network.
- Two languages. Go owns everything a chat message touches; TypeScript owns everything a signed-in person does on the website.
The diagram shows the pattern in generic terms, with placeholder names. ThaiPo is this shape: a fast path that answers in seconds and holds no truth, a slow path that learns from the archive, and one log between them.
What was built
- Translation in the chat. Both ways between Thai and any of 40 languages, in one-to-one chats and LINE groups, keeping tone, slang and politeness.
- Per-chat memory. Names, nicknames and terms are learned from each chat and used only in that chat. Memory never crosses chats, even for the same person.
- Corrections you teach it. A command or a plain sentence fixes a term for good, and every correction becomes a test case.
- Voice notes. A voice note comes back as a transcript and its translation.
- Photos and documents. Text in a photo or a PDF is read and translated.
- Video. A clip is watched in full and its meaning translated.
- Live translated video calls. Up to four people in their phone browsers, each reading captions in their own language. Nothing is recorded.
- Tone and intent. On request, a private read of what a message really means: tone, register and subtext.
- Native and spoken replies. English rewritten as natural Thai, and any translation played back as audio.
- Learning mode. Thai output carries a romanized line with tone marks for people learning to read it.
- Industry term packs. Nine opt-in vocabularies, from healthcare and legal to logistics and engineering.
- Billing and plans. A free plan and a paid plan in Thai baht through Stripe, with a trial, quotas and in-app cancellation.
- Sign-in and a dashboard. LINE, Google, Facebook or email, and a dashboard that shows a person only the chats they are in.
- An admin console. Users, chats, model routing, feature flags, and a credit panel that tracks model spend, burn rate and runway and raises an alarm when runway is short.
- Evals and release checks for prompts. Prompts are versioned and pinned byte for byte in tests, learned memory is published behind an eval gate with one-step rollback, and each deploy runs its test gate first.
- Disaster recovery. The database is dumped every six hours and the bucket mirrored daily to a second cloud provider on a separate account.
Engineering writing
The decisions, the outages and the numbers behind them are written up on the blog.
- We asked 200 chats what our app was for. They disagreed with our marketing.
- One CPU core, one debug script, four-minute translations
- The glossary that taught itself nonsense: 111,798 rows down to 10,844
- Telling the model not to say a word makes it say the word
- Ordered delivery on top of an unordered world
- Prepaid credits are a circuit breaker
- Postgres told me I had no replica. It was lying politely.
- Six apps down to three: retiring features without deleting them
- 148 trials, 1 paid: an honest conversion post-mortem
- The landing page counts itself
- A translator bot must never answer the question
- The boring knobs that cut our LLM bill by two thirds
- The cursed inputs of chat translation
- CI for prompts: how we stop translation quality walking backwards
- We built live translated calls with no server in the hot path. Then we put two back.
- Our whole backend is files in a bucket: a Kappa architecture on object storage
- How we make English sound like native Thai (and prove it)
Get in touch
ThaiPo is in production and in daily use. To talk about the system or the person who built it, use the contact page.