saascode

Velo: a self-hosted AI WhatsApp platform for operators

builds · sep 01, 2026 · 8 min read · product: Velo

A preventista at a wholesale distributor's dispatch desk listening to a customer's WhatsApp voice-note order with Velo's operator console open on his laptop

Velo's last serious design defects were invisible in every code-shaped proof the build had accumulated. The application compiled, route inventories reconciled, and style tokens appeared present. Only after 27 screens were rendered did a second typeface, a 24-pixel blur across every route, and a forbidden toast shadow become visible.

Velo is a self-hosted, multi-tenant AI WhatsApp platform that an operator buys and installs. Its build centers tenant-isolated conversations and the operating surfaces around them. This account follows the architectural decisions behind that model, then the visual comparison that exposed what static validation could not.

Starting point

The familiar surface of a WhatsApp product is a conversation. The harder object to build is the business behind many conversations: an installation owner, separate client organizations, tenant staff, customers who never need an account, credentials that cannot cross organizational boundaries, and background work that must survive beyond a browser request. Velo began by treating those boundaries as the product model rather than as infrastructure to add later.

That choice had an early consequence. The planning record initially mapped a tenant owner to a reserved platform-level admin role. A role-layer check rejected the mapping before it hardened into the database. The correction moved tenant ownership to an organization-scoped role and changed ten write policies with it. This was not terminology cleanup. The original mapping would have blurred the person who owns the installation with the person who manages one tenant inside it.

The same separation shaped the rest of the system. WhatsApp transport sits behind a provider-independent interface. Provider credentials belong to each organization rather than to one shared deployment secret. Retrieval uses a tenant-scoped vector store and refuses when no result clears the stored threshold. Voice processing moves through a durable queue instead of asking an inbound webhook to hold a long-running job open. Each decision reduces a different way in which one customer's state could leak into another customer's operation.

The visual starting point was narrower, but no less deliberate. The operator had already rejected an earlier dark industrial identity in favor of a light, restrained WhatsApp-green system. The design exploration therefore did not pretend the hue was still open. It tested structure: density, typography, edge shape, warmth, and how a serious operating console could use green without becoming a generic chat clone. The selected direction combined a flat editorial identity with the denser conversation architecture of another candidate. Green was the signature; it was not allowed to become the whole surface.

Reading the market

That distinction also clarified which market Velo was entering. A single-business messaging tool can organize one team's inbox. A multi-tenant source platform has to let an operator run the layer above those inboxes: provision organizations, isolate data, choose capabilities by business type, manage provider connections, and expose honest states when a tenant is not ready. The buyer is not looking for one more place to answer their own messages. The buyer is installing the system from which client businesses will operate.

This owner-level model changed what counted as a complete interaction. An end customer could remain a contact rather than becoming an application user. A tenant administrator needed authority inside one organization without inheriting platform power. Orders, knowledge retrieval, and scheduling could be enabled according to a tenant's business type rather than treated as universal screens. Provider-specific rules still mattered, but they could not be permitted to dictate the core data model.

The decisions that shaped it

Make the tenant boundary observable

Row-level isolation was necessary, but the build record did not treat policy text as proof. The security pass created a second organization inside a transaction, attacked its data as users from the first, ran positive controls against the same tables, and rolled the transaction back. The measured result was 28 passing legs, with no failed or unmeasured leg in that probe. A zero-row response only counted when the harness had also proved that it could see the caller's own row.

That discipline caught a high-severity flaw that a foreign key could not prevent. A price-list line could reference a catalog item belonging to another organization because the reference proved that the item existed, not that it belonged to the caller. The write path gained an explicit organization check before mutation, and the read path replaced an implicit embedded join with a second organization-scoped query. The negative control demonstrated that the old shape leaked a foreign item; the corrected shape resolved none of it while still resolving the tenant's own item.

Keep provider choices at the edge

The transport decision was to support multiple WhatsApp adapters behind one contract while keeping the rest of the product provider-neutral. Even the database columns use provider-oriented names rather than baking one vendor into every record. A less conventional adapter remained opt-in and warned, while account-level onboarding and template approval were left explicitly unverified where the build could not exercise them. The useful boundary was not “supported” versus “unsupported.” It was what the code could abstract, what an operator still had to configure, and what the run had actually measured.

Prefer an honest state to a convenient fiction

Open registration produced a subtle product decision. A new user could exist before an operator attached that user to an organization. Automatically launching onboarding would have let the user create a tenant, contradicting the operator-controlled provisioning model. Letting the normal dashboard render was worse: organization-scoped panels failed without explaining why.

Velo kept self-service tenant creation off and built a dedicated organization-required state at the dashboard boundary. The runtime record measured the before-and-after behavior on a real organization-less account. Before the change, three panels showed errors and the browser logged eight console errors. After it, the shell was replaced by a clear notice, the broken panels disappeared, and the console count fell to zero. An existing tenant administrator still saw the full dashboard, which made the probe a regression check rather than a one-sided screenshot.

Treat the visual system as behavior

The selected design specified more than a palette. It used a square geometry with no shadows, a single type family, a light-only canvas, and black text over the green primary color. The contrast measurement made that last choice concrete: black over the selected green measured 11.06:1, while white measured 1.90:1 and failed the required threshold. The interface could not inherit conventional white-on-primary defaults without contradicting the approved system.

What fought back

Several failures appeared only when assumptions met live state. A global feature-flag trigger inserted Velo's new keys as disabled before the project seed ran. The seed used conflict-ignore behavior, so its intended enabled values could not replace the rows that already existed. The demo tenant started with ordering and its default transport silently off. A live webhook probe surfaced the condition as an adapter-disabled response; the seed was changed to update the declared value on conflict, then read back to confirm the intended state.

Runtime debugging found a different form of false confidence. An administrative tenant page returned HTTP 200 while its only data request returned 500 because the data layer saw two possible relationships between organizations and plans. The detail endpoint swallowed the same query error and reported an existing tenant as missing. Naming the intended relationship fixed both routes, and the verification required the list and detail pages to render the known tenant—not merely for the page shell to answer.

The final visual pass then exposed the failures that gave this build its clearest lesson. Before it began, the stylesheet inventory reported every declared design utility as ported, the application compiled, and the route set had been graded. The comparison still rendered each approved HTML reference and each application route at the same desktop size, with motion disabled and authenticated and public routes handled separately.

Three high-severity discrepancies survived the static evidence. A font-sans class inside the agent console fell through to an inherited Geist definition, so one panel rendered a second typeface instead of Archivo. A cookie banner applied a 24-pixel backdrop blur to all 27 routes even though every reference rendered with none. A vendored toast carried a box shadow where the overlay specimen required a flat surface. The font utility was made explicit, the blur was removed through a fix that actually changed the rendered result, and the toast override was scoped to the toast rather than flattened globally, where it could have damaged focus rings.

The run record measured that final design-and-visual pass at 7,591 seconds of wall-clock time, with 350,963 agent tokens and 93 tool uses recorded. Those numbers are not estimates and do not imply that every visual state was observed. They show the cost of comparing the built interface to its source material and rechecking the fixes instead of declaring success from the token layer alone.

Even the visual probe needed correction. Its first dark-band detector counted transparent black as a dark surface because it ignored alpha. The detector was fixed and the full set was measured again: zero dark bands in the application and zero in the references. The broken reading was not preserved as a product finding. That distinction—repair the instrument, rerun the observation, then report—matters as much as the CSS fixes themselves.

What shipped

The build produced an operator platform whose strongest claims are backed by different kinds of evidence: tenant boundaries exercised against a second organization, runtime states checked with real sessions, provider edges kept explicit, and a visual system compared as rendered pixels rather than inferred from class names. It also preserved limits. Several capability-gated pages could show only their off state for the available demo tenant. Three overlay types were unreachable. Responsive behavior was not asserted class by class. None of those absences was rounded into a pass.

Velo's build story is therefore not that one late review polished a finished interface. It is that each layer needed an observation suited to the thing being claimed. Types can show that code fits together. Runtime probes can show that permissions and errors behave under real identities. Render comparison can show that the browser expresses the chosen design. The last three high-severity defects survived because the earlier evidence was answering different questions.

See it

See Velo →

end