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.
