Skip to Content
ChatChat & Online Presence — Overview

Chat & Online Presence — Overview

What we’re building

Two linked features:

  1. Online Presence — Show which users are currently active. When a user logs in and opens any portal, they appear as “online” to relevant peers. When they close the tab or their session expires, they appear “offline”.

  2. Chat (Direct Messages) — A floating chat panel in each portal where users can send direct messages to other online users. Conversations are persisted and retrievable later even if the recipient was offline.


Scope by Portal

PortalWho can seeChat with
ManageAll users across all tenantsAny user (regardless of tenant)
DashboardUsers in the same tenant onlyUsers in the same tenant only
DM PortalUsers in the same tenant onlyUsers in the same tenant only

The tenant boundary is enforced server-side. A dashboard user cannot reach users from another tenant even if they craft a raw WebSocket message.


Framework Decision: Socket.IO

Why not plain WebSockets (ws package)?

Raw WebSockets give you a TCP-like pipe. You would need to build on top of it:

  • Room/channel management (for tenant scoping)
  • Automatic reconnection
  • Fallback for environments that block WebSocket upgrades (corporate proxies, etc.)
  • Broadcasting across multiple API instances

That is weeks of infrastructure work before writing a single product feature.

Why not SSE (Server-Sent Events, already in use)?

SSE is already used for audit log streaming. It is unidirectional — server pushes to client only. Chat requires the client to also send messages. You would still need a REST endpoint for sending and SSE for receiving, which is two separate protocols to maintain.

Why not a managed service (Ably, Pusher, Liveblocks)?

External services add per-message cost, vendor lock-in, and require all message content to leave the infrastructure. Not appropriate for a B2B marketing platform.

Socket.IO — chosen framework

Socket.IO runs on top of WebSockets (with transparent fallback to HTTP long-polling when WebSocket connections fail). It provides:

  • Rooms — map directly to tenants; a message broadcast to tenant:{tenantId} reaches only users in that room
  • Namespaces — separate the manage admin namespace from the tenant namespace
  • @socket.io/redis-adapter — plugs into the existing Redis infrastructure; when the API runs on multiple instances, a message published on instance A is automatically fanned out to clients connected to instances B and C
  • Automatic reconnection — built into the Socket.IO client; no custom retry logic needed
  • Middleware — JWT/session verification at the handshake level (before any room is joined), consistent with the existing API auth pattern
  • fastify-socket.io — official Fastify plugin; attaches Socket.IO to the existing Fastify HTTP server without a separate process

The result is that the same Redis already used for BullMQ and audit-log pub/sub also handles Socket.IO message routing. No new infrastructure required.


Files in this folder

FileContents
index.mdThis file — overview and framework rationale
architecture.mdComponent diagram, auth flow, Redis layout, tenant isolation
schema.mdNew Prisma models (ChatMessage) and Redis key conventions
events.mdFull Socket.IO event catalog (client ↔ server)
implementation.mdPhased implementation plan with file-level task breakdown

© 2026 Leadmetrics — Internal use only