Relay is a minimal, streaming-first team chat. One server per workspace, your data on your terms. No integrations nobody uses. No enterprise pricing. Just a clean API and conversations that move fast.
Pricing
A 10-person team on Slack Pro pays $72.50/month. That same team on Relay BYOI rides a single small server in the single-digit dollars per month — pass-through infra, not per-seat rent. Want zero ops? Relay Cloud is the same transparent server bill plus 20% — we run it 100% (uptime, backups, patches, upgrades). Talk to us for sizing and the underlying numbers.
The default team chat playbook: every hire raises the tab, history and APIs follow paid tiers, and there is no self-hosted escape hatch — you rent the app, the roadmap, and the integration marketplace forever.
Add-on
An optional managed Postgres archive sits behind your room API for the long tail of message history, plus point-in-time recovery snapshots. Pick who holds the connection string: you, or us. Pricing and the underlying provider are part of the conversation when you sign on — get in touch for a quote.
Paste a Postgres URL into Relay. You pay your provider directly; we route cold archive and DR targets to your database — no markup on the underlying line items.
We provision, monitor, and operate the archive side — pass-through pricing on the underlying database plus 20% for our lifecycle work (scaling cues, failover drills, credential rotation, on-call). Same transparency model as Relay Cloud on the chat tier.
Optional on every workspace. Stacks with Relay BYOI or Relay Cloud — flip the toggle above; this add-on is orthogonal.
While we’re here: enterprise Slack is a procurement saga, a SSO tax, a 90-day history ransom on “free,” and a marketplace of integrations you could ship yourself before the kickoff call ends. Relay is chat infrastructure — not a platform rent-seeking on every seat.
Same headcount · different physics
Illustrative 500-person org. Slack bar uses common published Business+–style list pricing (~$12.50/user/mo). Relay bar: one Scale-tier server on your infrastructure — $48/mo illustrative list, whole workspace. Your Slack contract may differ; the shape of the chart won’t.
Illustrative 500-person org. Slack bar uses common published Business+–style list pricing (~$12.50/user/mo). Relay bar: same box fully managed — list $48/mo × 1.2 = $57.60/mo. Your Slack contract may differ; the shape of the chart won’t.
Bar = 100% — this is the burn you’re normalizing.
~0.77% of the Slack bar — true proportion ($48 ÷ $6,250). Bar is barely visible on purpose.
~0.92% of the Slack bar — coffee budget for one VP; chat for five hundred people.
Baseline Slack burn here: $6,250/mo (500 × $12.50 list). For Relay BYOI at $48/mo: monthly difference (Slack − Relay), yearly cash kept (×12), and percent vs that Slack line — plus the ratio.
Baseline Slack burn here: $6,250/mo (500 × $12.50 list). For Relay Cloud at $57.60/mo: monthly difference (Slack − Relay), yearly cash kept (×12), and percent vs that Slack line — plus the ratio.
$6,202/mo less than Slack (monthly difference)
$74,424/yr total not spent vs Slack, same headcount
99.23% lower than Slack · Relay is 0.77% of Slack’s tab · ~130.2× cheaper
$6,192.40/mo less than Slack (monthly difference)
$74,308.80/yr total not spent vs Slack, same headcount
99.08% lower than Slack · Relay is 0.92% of Slack’s tab · ~108.5× cheaper
Checks: $6,250 − $48 = $6,202; ×12 = $74,424; savings ÷ $6,250 = 99.23%. Ratio: $6,250 ÷ $48 ≈ 130.2×. Not financial advice — illustrative list math.
Checks: $6,250 − $57.60 = $6,192.40; ×12 = $74,308.80; savings ÷ $6,250 = 99.08%. Ratio: $6,250 ÷ $57.60 ≈ 108.5×. Not financial advice — illustrative list math.
Infrastructure
Your workspace runs on a single server. Pick a starter size and scale vertically as you grow — each workspace gets its own isolated instance. Sizing and provider are part of the onboarding conversation.
One workspace per boxAll live message delivery runs through Redis. Channel subscriptions, presence, typing indicators, and SSE event fanout. Sub-20ms delivery.
Hot pathRecent messages, channel metadata, and user state stored in SQLite on the server's local SSD. Blazing-fast local reads. WAL mode for concurrent writes.
Hot storageMessages older than your retention threshold are archived to a managed Postgres database. Cold reads on-demand via the same API — zero data loss. Optional DR add-on lets you bring your own connection string or have us run it for you.
Cold archiveEvery feature is a first-class API endpoint. Build your own client, automate workflows, or integrate with anything. OpenAI-compatible tool schemas for AI agents.
API-firstRelay's API follows OpenAI function-calling conventions. Drop an AI agent into any channel as a participant. Webhooks, tool schemas, and structured responses built in.
OpenAI-compatibleSizing
Most small teams run comfortably on a single low-end server. As your team grows, you resize the box vertically — your data stays put, no migration. Picking the right starting size, the right provider, and the right region is part of the onboarding conversation. Reach out for a sizing recommendation tailored to your team and traffic profile.
Real-time Architecture
Clients subscribe to an SSE stream. Redis Pub/Sub fans out events across all connected clients in a workspace. No WebSocket complexity — just a persistent HTTP connection.
Data Architecture
API Reference
Every endpoint below is mounted in the running Hono server in apps/agents. Public traffic enters at https://dev3lop.com/relay-api/* and is reverse-proxied to the workspace's chat tier. Programmatic API tokens for non-browser clients are on the roadmap; today the same browser session cookie issued by GitHub OAuth carries every call.
Cookie: relay_session=<signed> · obtained via GitHub OAuthOpen /auth/github?next=/relay/app/<slug> to start the OAuth dance. The callback at /auth/callback upserts your GitHub profile and sets a 30-day signed session cookie. Every /relay-api/* request reuses that cookie — there is no separate Bearer key today.
Sets a short-lived signed state cookie and 302s the browser to github.com/login/oauth/authorize. After the user approves, GitHub redirects to /auth/callback, which exchanges the code, upserts the GitHub profile in SQLite, and writes a 30-day signed session cookie.
| Query Param | Type | Required | Description |
|---|---|---|---|
| next | string | optional | Path to land on after callback. Must be same-origin and not under /auth/; defaults to the lobby room. |
Reads the session cookie and returns the authenticated user's profile (id, githubId, login, name, avatarUrl, email). Responds 401 with null body when no valid session is attached.
Removes the signed session cookie. The user record stays in SQLite — the next OAuth round trip will reuse it.
Resolves the seeded default room (the lobby) for the signed-in user. Used by the SPA on first load to know where to send people who haven't picked a room yet.
Returns the room's branding, owner, and settings. Whitelisted rooms reject non-members with 403; the lobby is always public.
Owners can rename the room (collision-checked against reserved slugs), update its display name, description, accent colors, and avatar. Updates are broadcast over Redis pub/sub so every connected member's UI updates live.
Flips the room between open and whitelist-only. When enabled, only allow-listed GitHub logins or verified emails can read/write; everyone else hits the access-request flow.
Returns channels in the room — public ones plus any private/group channels and DMs the caller is a member of. Each row carries id, slug, name, visibility, memberCount, and createdBy.
Owner-only. The lobby has no admin, so its create-channel UI is hidden and this endpoint refuses calls there. visibility is public | private | group | personal; inviteLogins[] seeds co-admin members.
{
"name": "Engineering",
"slug": "engineering",
"visibility": "public",
"inviteLogins": ["alice", "bob"]
}Available to anyone with room access. Creates (or reuses) a private DM channel between the caller and the supplied GitHub logins. Returns the channel DTO and broadcasts a direct_created event so the other parties' tabs add it without refresh.
Allowed if the caller owns the room or created the channel. Wipes the channel's messages from SQLite and publishes a deleted event to all subscribers.
Returns every user who has spoken in or been invited to the room. Used by the mention picker and the DM flow.
Reads from SQLite (the hot store). Use channelId=__lobby for the room-wide thread, a real channel id to scope to one channel, or omit to get everything visible. Pagination uses a (beforeCreatedAt, beforeId) keyset so concurrent inserts can't shift pages.
| Query Param | Type | Required | Description |
|---|---|---|---|
| channelId | string | optional | Channel id, __lobby, or omit for all |
| beforeCreatedAt | ISO 8601 | optional | Used with beforeId for keyset pagination |
| beforeId | string | optional | Tiebreaker for messages with identical timestamps |
| limit | integer | optional | Default 20, capped at 100 |
The HTTP send path mirrors the WebSocket path: it builds the message row, publishes to Redis first (so other tabs see it in one RTT), then persists to SQLite asynchronously. Mentions are processed off the response path. Content is trimmed and capped at 4,000 characters.
{
"content": "Deploy looks green. ",
"channelId": "ch_8fd2…"
}Only the original author may edit. Updated body is published as a message_updated event so connected clients can rerender in place.
Author or room owner. Removes the row from SQLite and publishes a message_deleted event with the channel id so clients can drop it from the relevant view.
The primary live transport. On connect the server replays the last 20 messages as a sync frame, then forwards every Redis pub/sub event for the room. Clients send {"type":"send","clientMsgId","content","channelId"} frames; the server fans out via Redis and acks on the same socket so optimistic UI can promote in place.
// server → client (initial)
{ "type": "sync", "messages": [...] }
// client → server
{ "type": "send", "clientMsgId": "c_42", "content": "hi", "channelId": null }
// server → client (echo on every connected tab)
{ "type": "message", "clientMsgId": "c_42", "message": { ... } }
// server → sender only
{ "type": "ack", "clientMsgId": "c_42", "serverId": "msg_…", "createdAt": "…" }Read-only SSE stream for environments where WebSockets are blocked. Sends an initial sync with the last 20 messages, then relays every message, message_updated, message_deleted, and channel/branding event the room publishes. A ping event fires every 20 seconds to keep proxies open.
event: room
data: {"type":"sync","messages":[ ... ]}
event: room
data: {"type":"message","message":{ ... }}
event: room
data: {"type":"message_updated","message":{ ... }}
event: room
data: {"type":"message_deleted","messageId":"msg_…","channelId":"ch_…"}
event: ping
data:Returns every login or email allowed into a gated room.
Owner-only. Accepts a GitHub login or an email address and a free-form note.
Owner-only. The user keeps any session they already have until it expires, but new joins are blocked.
Owner-only. Returns the queue of would-be members the room hasn't yet approved or denied.
Any signed-in user can call this against a gated room. The request becomes a notification on the owner's dashboard.
Owner-only. Marks the request approved and inserts the requester into the room's whitelist atomically.
Owner-only. The requester is not notified beyond the existing dashboard state change.
Returns mentions, access requests, and other room-scoped notifications addressed to the caller.
Updates the notification's lifecycle state.
Pin a notification so it shows up in the saved tab regardless of read state.
Used by room owners to mark a notification public or private inside the room.
Idempotent. Returns the existing thread if one already exists, otherwise creates a new one and returns its id.
Returns thread replies in chronological order with author profile data joined in.
Posts a reply on behalf of the caller. Thread replies aren't broadcast on the room socket — clients refetch the thread on focus.
Returns the first board for a room. Creates a default Todo · Doing · Done board the first time it's hit.
Spins up another board with default columns alongside the room's first one.
Returns the board with its columns and cards inlined.
Drops the new card into the board's first column.
Updates the card's column and position. The board owner can move any card; other members can only move their own.
Saved cards float to the top of their column for the caller.
Multipart form upload. Images are converted to WebP under 70 KB before storage. Bind the result to a message or other entity by sending entityType + entityId in the same form.
Streams the stored bytes back with the right content-type. Access is gated by the entity the attachment belongs to.
Backed by Redis TTL keys. Connected clients heartbeat every 30 seconds; stale entries fall off automatically.
Bumps the caller's presence key and broadcasts an online event if they were stale.
Used by the AI add-on's private chat surface. Publishes a short-lived typing event for the named conversation.
Unauthenticated. Pings Redis and returns a small JSON document used by the workspace server's external uptime probe.
{
"ok": true,
"ts": "2026-05-04T23:30:00.000Z",
"checks": { "server": "ok", "redis": "ok" }
}Programmatic API tokens (so bots and CI pipelines can auth without a browser session), HMAC-signed outbound webhooks, and a public bot registration endpoint with OpenAI-compatible tool schemas. The architecture already supports them — Redis pub/sub fan-out, mention-driven webhooks, and the AI add-on are all wired up internally — they just don't have stable public routes yet.
Integrations
AI makes integrations trivial to build.
We don't charge for them.
Slack's pricing model bundles integrations you'll never use into the cost of messaging. In 2026, connecting two services is a 10-minute GPT task. Relay provides the webhook surface and the bot API — you own the integrations.
Want us to build one? We can. Want to build it yourself? The API is everything you need. The architecture is intentionally open-ended: bots are just webhook listeners that speak the Relay message format.
There's no app directory to unlock, no per-seat fee for GitHub notifications, no enterprise tier required to connect your CI pipeline. Register a bot, point it at your server, and write 20 lines of code.
GPT-4o-powered assistant in every workspace. @mention to ask questions, summarize threads, draft replies, or run custom tools.
HMAC-signed outbound webhooks for any event. The foundation for every integration you'll ever build.
Post PR, issue, and deployment events to channels. Open-source template — deploy in minutes on your own infra.
Route on-call alerts to Relay channels. Acknowledge directly from the API. Template available.
Register a bot, point a webhook at your server, use the /bots API. You build it in an afternoon. We're here if you need help.