<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/medium-59redrz0q" -->

---
title: Medium | daily.dev
description: HansaChat is an EU-based team chat platform built with flat pricing (no per-user fees). The post covers its technical architecture: each workspace gets a fully...
canonical: https://daily.dev/posts/medium-59redrz0q
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Medium | daily.dev
og:description: HansaChat is an EU-based team chat platform built with flat pricing (no per-user fees). The post covers its technical architecture: each workspace gets a fully...
og:url: https://daily.dev/posts/medium-59redrz0q
og:image: https://api.daily.dev/og/posts/59REdrz0q.png
og:image:alt: Medium
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Medium

**[Medium](https://daily.dev/sources/medium_js)** · 6 min read · 0 upvotes · 0 comments

## Summary

HansaChat is an EU-based team chat platform built with flat pricing (no per-user fees). The post covers its technical architecture: each workspace gets a fully isolated database for security and data isolation, the API is defined in Protocol Buffers using ConnectRPC, messages are queued via NATS JetStream before being written to the database to survive crashes, real-time delivery uses an outbox pattern with Centrifugo, WebRTC calls run on LiveKit, and file storage is proxied through the API to IONOS S3 in Germany. The Go backend is a modular monolith split into four independently scalable processes (API, worker, outbox relay, admin CLI), the frontend is Vue 3 deployed to web, desktop (Tauri), and mobile from one codebase, and secrets are managed via 1Password at deploy time.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://medium.com/@igor_98332/hansachat-is-the-eu-based-chat-platform-for-companies-that-is-built-with-having-no-per-user-pricing-fbf34d9f8483>

## Questions this post answers

### How do you make sure messages aren't lost if the API server crashes while sending a chat message?

Use queue-first processing instead of writing messages directly to the database from the API server. The API validates the request, hands the command to a message queue (NATS JetStream), and acknowledges immediately. A separate worker writes the message to the database and publishes real-time events, so if the API crashes after accepting the message, the command remains safe in the queue and gets redelivered on failure.

_daily.dev surfaces architecture patterns like this for engineers designing resilient messaging systems._

### What's a way to isolate tenant data in a multi-tenant SaaS application to prevent cross-tenant data leaks?

Give every tenant a completely separate database instead of a shared table with a tenant column. This makes cross-tenant leaks architecturally impossible since there is no shared storage for queries to cross, and it lets each workspace be migrated, backed up, or rebuilt independently without affecting others.

_Developers weighing tenant isolation strategies can track approaches like this on daily.dev._

### Why use an outbox pattern with a real-time server like Centrifugo instead of pushing WebSocket events directly from the write path?

Writing an outbox event in the same database transaction as the message write keeps the database as the single source of truth, while a separate relay process publishes those events to WebSocket subscribers via Centrifugo. If Centrifugo goes down, events stay in the outbox and retry later, and reconnecting clients detect sequence gaps and recover directly from the API, so real-time delivery stays an optimization rather than a correctness requirement.

_Engineers designing real-time features can find outbox-pattern breakdowns like this on daily.dev._

## Similar posts on daily.dev

- [I built an anonymous chat site — here’s the full 2-year journey](https://daily.dev/posts/i-built-an-anonymous-chat-site-here-s-the-full-2-year-journey-sozwzml3o) · Medium · 0 upvotes · 0 comments
- [The Rise of Micro-Backends: Why Frontend Devs Love Hono](https://daily.dev/posts/the-rise-of-micro-backends-why-frontend-devs-love-hono-a9wal9hdp) · SitePoint · 0 upvotes · 0 comments
- [Slack Who? This Self-Hosted Chat App Just Went Open Source](https://daily.dev/posts/slack-who-this-self-hosted-chat-app-just-went-open-source-1ph9xjapk) · It's Foss · 9 upvotes · 0 comments
- [How I built Fluxer, a Discord-like chat app](https://daily.dev/posts/how-i-built-fluxer-a-discord-like-chat-app-idic9iitz) · Lobsters · 45 upvotes · 4 comments

---

Tags: [#career](https://daily.dev/tags/career), [#golang](https://daily.dev/tags/golang), [#multi-tenancy](https://daily.dev/tags/multi-tenancy)

[View this post on daily.dev](https://daily.dev/posts/medium-59redrz0q)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"Medium","url":"https://daily.dev/posts/medium-59redrz0q","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/medium-59redrz0q"},"datePublished":"2026-07-23T14:44:27.547Z","dateModified":"2026-09-14T09:16:51.752Z","description":"HansaChat is an EU-based team chat platform built with flat pricing (no per-user fees). The post covers its technical architecture: each workspace gets a fully...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/d6ab0a9a223ee01bc82d647f54feb2d2?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/d6ab0a9a223ee01bc82d647f54feb2d2?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"Medium","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"Medium","logo":"https://media.daily.dev/image/upload/t_logo,f_auto/v1/logos/medium","url":"https://daily.dev/sources/medium_js"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/medium-59redrz0q","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":0},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"career,golang,multi-tenancy","timeRequired":"PT6M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Medium","item":"https://daily.dev/sources/medium_js"},{"@type":"ListItem","position":3,"name":"Medium"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/medium-59redrz0q#faq","mainEntity":[{"@type":"Question","name":"How do you make sure messages aren't lost if the API server crashes while sending a chat message?","acceptedAnswer":{"@type":"Answer","text":"Use queue-first processing instead of writing messages directly to the database from the API server. The API validates the request, hands the command to a message queue (NATS JetStream), and acknowledges immediately. A separate worker writes the message to the database and publishes real-time events, so if the API crashes after accepting the message, the command remains safe in the queue and gets redelivered on failure. daily.dev surfaces architecture patterns like this for engineers designing resilient messaging systems."}},{"@type":"Question","name":"What's a way to isolate tenant data in a multi-tenant SaaS application to prevent cross-tenant data leaks?","acceptedAnswer":{"@type":"Answer","text":"Give every tenant a completely separate database instead of a shared table with a tenant column. This makes cross-tenant leaks architecturally impossible since there is no shared storage for queries to cross, and it lets each workspace be migrated, backed up, or rebuilt independently without affecting others. Developers weighing tenant isolation strategies can track approaches like this on daily.dev."}},{"@type":"Question","name":"Why use an outbox pattern with a real-time server like Centrifugo instead of pushing WebSocket events directly from the write path?","acceptedAnswer":{"@type":"Answer","text":"Writing an outbox event in the same database transaction as the message write keeps the database as the single source of truth, while a separate relay process publishes those events to WebSocket subscribers via Centrifugo. If Centrifugo goes down, events stay in the outbox and retry later, and reconnecting clients detect sequence gaps and recover directly from the API, so real-time delivery stays an optimization rather than a correctness requirement. Engineers designing real-time features can find outbox-pattern breakdowns like this on daily.dev."}}]}
```

