Case study · Sayo

Sayo: a multi-tenant RAG AI chatbot for any website, built solo

Multi-tenant RAG chatbot for any website: per-tenant knowledge base, an embeddable chat widget and retrieval evals. Built solo as the only developer at work.

Role
Solo developer at 4Monks Consulting: architecture, frontend, backend APIs, deployment and support
Year
2026
Status
Live at sayo.projects.coblip.com
Stack
Next.js 16 · React 19 · TypeScript · Postgres + pgvector · Redis · Vite · Shadow DOM · MCP

The product

Sayo is a SaaS chatbot that lets a business put an AI assistant on its own website. It is live at sayo.projects.coblip.com. The assistant answers from that business’s knowledge (crawled pages, PDFs, documents, product feeds), shows rich answers such as comparisons and calculators instead of only text, and hands qualified leads to the team.

I built it solo as the only developer at my workplace, 4Monks Consulting, and owned it end to end: architecture, UI, APIs, deployment and production support.

Three hard problems

1. Tenant isolation

Every table carries an organisation id and every query filters by it. Isolation is a rule enforced in one data layer, not a habit developers must remember per endpoint. Roles split site owners from the platform operator.

2. Shipping UI into someone else’s site

The chat widget is a separate Vite build loaded by a single script tag and mounted in Shadow DOM, so the host site’s CSS cannot break it and it cannot break the host. It talks to a public API with origin-restricted API keys, so a leaked key is useless on another domain. Embedding needs no full-page reload and no framework on the host.

3. Frontends that teams ship independently

Each tenant surface is a loosely coupled package shared via npm that can be versioned, tested and deployed on its own, a micro-frontend pattern that removed a lot of cross-team coordination.

The answer pipeline

Ingestion runs as background jobs (one job per page or file): fetch, clean, chunk, embed, store in Postgres with pgvector. A visitor message becomes a retrieval query, the best chunks are ranked, and the model replies with a typed block (text, comparison, finance calculator, booking) that the widget renders. Caches in Redis keep common paths fast, and a worker process handles everything slow or scheduled so chat latency stays low.

Measuring quality

Retrieval quality is benchmarked with Ragas-style evaluation tooling, treated like an automated test suite: a fixed question set, scored for retrieval and answer quality, run before changes ship. Chat changes get the same rigour as code changes.

Security

API key origin restrictions, XSS prevention (answers are rendered as structured blocks, not raw HTML), JWT auth with session rotation, and SSRF guards on the crawler.

Takeaway

Treating the widget as a product in its own right, with its own build, isolation and versioning, is what made the platform embeddable on any site.