Custom AI Software
A clinic loses bookings because nobody answers WhatsApp at eight in the evening. We built a micro-SaaS that replies, checks the business's real calendar and books — and that knows to go quiet when a customer gets angry. This is the technical breakdown of how, and above all why.
A dental clinic, a workshop and a tutoring academy share one problem, and none of them has an IT department to solve it: people ask questions on WhatsApp and nobody answers outside business hours. The enquiry goes cold, the customer calls somewhere else, and the loss never appears in any report because nobody measures it.
The existing options fail at both extremes. Rule-based chatbots require somebody to configure conversation trees that break on the first unanticipated question. Serious customer service platforms cost more than the business’s monthly margin and assume a team to operate them.
Talkify is our answer: a multi-tenant micro-SaaS that connects the business’s WhatsApp, answers with AI grounded in that business’s real information, checks availability against its Google Calendar and books the appointment. And when a conversation turns, it goes quiet and alerts a person.
We built it ourselves, with our own money and our own judgement. Which means the hard decisions could not be dodged by blaming a client requirement.
WhatsApp has no affordable official API for a neighbourhood business. The official WhatsApp Business API was out of MVP scope on budget grounds, so connectivity rests on Baileys, an unofficial library implementing the WhatsApp Web multi-device protocol.
That makes connectivity the product’s number one operational risk, and it was logged as such in the risk register before the first line was written: unofficial library, ban risk, and disconnections as a normal part of the lifecycle rather than an incident.
Everything that follows is a consequence of accepting that constraint with open eyes.
Our internal technology standard mandates Go for backend services, specifically to keep the frontend and backend runtimes separate. Baileys exists only in Node.js, and there is no Go equivalent with comparable coverage.
The decision was to write the WhatsApp worker in Node.js/TypeScript as a separate process, and keep absolutely everything else in Go. They share neither a database connection nor process space: they talk over an internal API.
The result shows in how the code is distributed:
srv-talkify-app repository. The worker speaking WhatsApp's unofficial protocol — the product's number one operational risk — is 4% of the code and runs in its own process.The product’s highest-risk component is 4% of the codebase. A Baileys reconnection storm cannot take down the API Core, because it does not even live in the same process. It is the only place in the whole repository where we depart from the default stack, and it carries its own ADR justifying it.
We pay the cost elsewhere: two ecosystems to maintain, two dependency-update cadences and two sets of conventions. That is written in the negative consequences of that decision, because an ADR that only lists upsides is not an ADR.
Booking appointments is the product’s commercial objective, and it is precisely where a language model is least trustworthy. LLMs are poor at time arithmetic, at respecting business-hours boundaries and at detecting conflicts across multiple events.
So we forbade it. The model is strictly prohibited from estimating or assuming availability.
Every calendar operation goes through native function calling — check_availability, book_appointment, cancel_appointment, list_my_appointments — and the logic lives in Go, not in the prompt. The internal/calendarclient package deterministically merges three sources:
| Source | What it contributes |
|---|---|
tenant_configs.schedule | The business’s real opening hours |
tenant_configs.services | The duration of each service |
| The business’s Google Calendar | Existing events, in real time, via OAuth 2.0 |
The tool returns the computed valid times plus a text template the model relays verbatim. The model converses; it does not decide.
The third point is the one that matters in the real world: the salon owner still books walk-ins straight into the Google Calendar they have always used. Without genuine two-way synchronisation, the system would offer slots that are already taken and create overbooking — which for a small business is worse than having nothing.
Google refresh tokens are stored encrypted with AES-256-GCM.
Each business needs the AI to know its information: where to park, which payment methods it accepts, its cancellation policy, its team’s specialisms. That is retrieval-augmented generation, and the standard temptation is to stand up a separate vector database.
We didn’t. We use pgvector inside the same PostgreSQL, with 1536-dimension vectors, an HNSW index and text-embedding-3-small.
The main reason is not performance, it is isolation. Every knowledge chunk carries a tenant_id foreign key with cascade delete, and every similarity search is scoped by tenant_id in the SQL query itself. Clinic A’s knowledge reaching Clinic B’s customer is not an acceptable bug: it is the end of the product.
Keeping it in the relational database means isolation is guaranteed by the same engine that already guarantees everything else, updates are atomic, and deleting a tenant cleans up its vectors with no extra code. One less piece of infrastructure to operate, monitor and pay for.
WhatsApp conversations run for weeks. Feed the whole history into the prompt and three things happen at once: cost per message grows linearly, latency degrades, and the model gets confused by old appointments when the customer asks about something new.
The fix is two limits, neither sophisticated:
It is the kind of decision that features in no demo and determines whether the product has unit margin.
A bot that argues with an angry customer does more damage than no bot at all.
The model has a transfer_to_human(reason, summary) tool with four categorised reasons: hostile customer, explicit request for a person, out-of-scope enquiry, and complaint or refund.
When it fires, the conversation moves to needs_human and the orchestrator completely suppresses automated replies. The customer’s subsequent messages are stored in PostgreSQL for staff to read, but are never sent to OpenAI. The panel gets a real-time SSE notification. Only the owner can hand the conversation back to the AI from the dashboard.
Silenced means silenced. Not “replies more softly”.
Just as decisive. Talkify has no users of its own, no passwords, and no billing of its own:
srv-accounts over OAuth2 + PKCE.srv-billing with Stripe Checkout and Portal.The web panel never calls the API from the browser: everything goes through a same-origin BFF proxy with an HttpOnly session cookie. The browser never sees a token.
Each of those three is an ADR with its rejected alternatives. Building our own login and billing would have added weeks and two attack surfaces to solve problems already solved in-house.
Three cycles, each with a written definition of done agreed before it started:
| Cycle | Scope | Status |
|---|---|---|
| 1 — Foundation & Core Connect | Multi-tenant infrastructure and WhatsApp connectivity | Complete (Jul 2026) |
| 2 — AI Engine & Human Panel | AI engine, RAG, deterministic booking, handoff | Backend complete (Aug 2026) |
| 3 — Monetization & GTM Launch | Billing, plans, commercial pipeline | Aug 2026 |
First commit on 9 July 2026. The web panel started on 22 August on the “Aurora” design system — warm paper by day, near-black by night, a single orange accent — in Astro with Preact islands, plain CSS in three layers, and a CSP that allows script-src 'self' only.
A product in production with seven functional modules, 125 API routes, 58 database migrations and 74 Go test files. The part carrying the protocol risk stayed confined to 1,505 lines of TypeScript.
And 27 documented architecture decisions, each with its context, its rejected alternatives and its negative consequences in writing. That record is the deliverable we are proudest of: a year from now, when somebody asks why the WhatsApp worker is in Node, the answer is written down, dated, with the arguments that were discarded.
It is exactly how we build for our clients. Talkify is the proof that we do it when the money is ours too.
Every engagement starts with a free 30-minute call. No pitch — just a conversation about your business.
Cookies
We use analytical cookies (Google Analytics) to understand how visitors use the site. They only activate if you accept. See our Privacy Policy for details.
Free resource
The 12 questions our team uses on every client kickoff to find the biggest AI opportunities fast. Free — no spam.
Check your inbox — it's on its way.