---
title: "Real-Time Messaging"
description: "Conversations REST + Tunnel Protocol — channel types, tool-chat subtypes, and live deal chat without page reloads"
locale: "en"
---
# Real-Time Messaging

> **Info**
> Use **Founder** / **Developer** tabs in the docs sidebar to filter this page. This article is the SSOT for **Conversation.type** (channel) vs **metadata.kind** (subtype) and inbox hygiene for tool chats.

Ring messaging combines **REST conversations** with **Tunnel Protocol** fan-out so members chat in context of opportunities, entities, and store deals without refreshing the page.

| Layer | Where |
|-------|--------|
| REST | `app/api/conversations/**` |
| Realtime | Tunnel channels `conversation:{id}` — see [Tunnel Protocol](/docs/features/tunnel-protocol.md) |
| UI | `features/chat`, `features/messages` |
| Types | `features/chat/types/index.ts` |
| API reference | [Messaging API](/docs/api/messaging.md) |

## Channel vs subtype (shared)

Ring does **not** invent a new top-level conversation type for every feature. Two layers stay separate:

| Field | Meaning | Example |
|-------|---------|---------|
| `Conversation.type` | **Channel / domain routing** — where the thread lives in the messaging system | `direct`, `entity`, `opportunity`, `product`, `group` |
| `metadata.kind` | **Optional subtype** within that channel | `generative_gallery` |
| `metadata.hiddenFromInbox` | **Inbox contract** — hide from Messages list | `true` for tool editors |

Verified channel enum (TypeScript + create zod in `app/api/conversations/route.ts`):

`direct` | `entity` | `opportunity` | `product` | `group`

> **Warning**
> Do **not** add `type: 'generative'`. Generative Gallery reuses `type: 'product'` plus `kind: 'generative_gallery'` and `hiddenFromInbox: true`. Extending the channel enum for one feature breaks product lookup helpers and invents parallel plumbing.

```mermaid
flowchart TB
  subgraph channel [Conversation.type]
    direct
    entity
    opportunity
    product
    group
  end
  subgraph subtype [metadata.kind]
    generative_gallery
  end
  product --> generative_gallery
  product --> storeAgent[Store product agent]
  generative_gallery --> hide[hiddenFromInbox]
  storeAgent --> inbox[Messages visible]
  hide --> noInbox[Hidden from Messages]
```

Same `product` channel, different visibility:

| Use | `type` | Metadata highlights | Messages inbox |
|-----|--------|---------------------|----------------|
| Store product agent (DAGI) | `product` | real `productId`, no tool `kind` | Visible |
| Generative Gallery tool chat | `product` | `kind: 'generative_gallery'`, `hiddenFromInbox: true`, `productId`/`subject` = `genmedia:…` | Hidden |

Conversation `metadata.kind` is **not** the same namespace as **message** `metadata.kind` (e.g. `payment_request`, `env_request`, `task`, `poll`, `rsvp`, `dao_jar`, `share_card`, `gallery_upload`, `ghost_write_result`, `game_request`).

Interactive types share one lifecycle kit (`features/chat/lib/interactive-kind.ts` + bubble registry + notify helper): server-action create → dual-gate widget (`type` OR `metadata.kind`) → `updateMessage` / tunnel `message:update` → typed notify. Domain owns money for `dao_jar` via public-pools: **native** treasury chip-in (`contributeToPool`) **and** card/PayPal (`public_pool_contribution` → desk-oracle FX). Chat owns the snapshot — always `refreshOpenDaoJarMessages` after totals change. `share_card` reuses `MESSAGE_RECEIVED` + `data.kind`. Tier A interactive UX is remediated (2026-07-21); see [Public Pools & DAO Jars](/docs/features/public-pools.md).

**Peer games** add `game_request` (allowlisted in `interactive-kind.ts`): session SSOT in `PeerGameService.createInvite`, Tunnel `game:invite` / `games:incoming` (incl. terminal clear), and `IncomingGameBanner` on Messages + `/games`. Titles: tic-tac-toe · chess · checkers. Direct conversations only. See [Peer Games](/docs/features/peer-games.md).

Structured work inside a thread: [Ring Tasks](/docs/features/tasks.md) (`type: 'task'` + `/tasks` tree).

### For founders

## Why messaging matters for your clone

Instant conversation shortens the path from match → trust → deal. Members stay inside your Ring instead of dropping to email or third-party chat. Tool editors (image generation history) stay out of the main inbox so operators are not flooded with system threads.

  
- **[Tunnel Protocol](/docs/features/tunnel-protocol.md)** — How live updates reach the browser (WSS / SSE / poll).

  
- **[Generative Gallery](/docs/features/generative-media.md)** — Upload \| Generate media — history in a hidden product-tool chat.

  
- **[WebRTC Calls & STUNner](/docs/features/webrtc-calls.md)** — 1:1 audio/video in direct chats — ICE + TURN.

  
- **[Messaging API](/docs/api/messaging.md)** — Integrator contracts for conversations and typing.

### Typical scenarios

- Two matched professionals open a **direct** conversation from an opportunity — it appears in Messages.
- A buyer asks a store **product agent** a question — that product chat stays visible in Messages.
- A vendor or member generates NFT/product art in Generative Gallery — history lives in a **hidden** tool chat, not the inbox.
- Typing indicators and read state keep negotiations moving on mobile PWA.

### For developers

## Implementation

### Verified routes

| Method | Path | Role |
|--------|------|------|
| GET, POST | `/api/conversations` | List / create conversations |
| GET | `/api/conversations/[id]` | Conversation detail |
| POST | `/api/conversations/[id]/typing` | Typing signal |
| POST | `/api/conversations/[id]/read` | Mark read |
| POST | `/api/conversations/upload` | Attachment upload |
| MCP | `/api/mcp/v1/messaging/conversations` | Agent tooling |

After writes, publish on Tunnel channel `conversation:{id}` via `publishToChannel` — clients subscribe with **`useTunnelChannel`** (not raw `useTunnel().subscribe()` in effects).

Create body `type` must be one of: `direct` | `entity` | `opportunity` | `product` | `group` (`z.enum` in `app/api/conversations/route.ts`). Optional metadata includes `productId`, `groupName`, `kind`, `hiddenFromInbox`, and domain ids (`entityId`, `opportunityId`, …).

### Inbox hide contract

`ConversationService.getConversations` skips rows where `isHiddenToolConversation` is true (`features/chat/services/conversation-service.ts`):

1. `metadata.hiddenFromInbox === true` (preferred long-term gate)
2. `metadata.kind === 'generative_gallery'`
3. Legacy: `productId` or `subject` starts with `genmedia:` or `imggen:`

### Modules

| Path | Responsibility |
|------|----------------|
| `features/chat/types/index.ts` | `Conversation`, `CreateConversationRequest`, filters |
| `features/chat/services/conversation-service.ts` | CRUD + `isHiddenToolConversation` |
| `features/chat/`, `features/messages/` | Chat UI |
| `features/generative-media/service.ts` | Creates hidden `product` + `generative_gallery` chats |
| `features/store/services/product-agent-service.ts` | Creates visible `product` agent chats |
| `lib/tunnel/` | Hub, publisher, transports |
| `hooks/use-tunnel-channel.ts` | Stable channel subscribe |

### Transports

- **k8s / self-hosted:** native WSS `/api/tunnel/ws` primary
- **Vercel:** SSE + long-poll (`/api/tunnel/poll`)
- Optional Supabase Realtime when configured

Full endpoint samples: [Messaging API](/docs/api/messaging.md). Architecture: [Real-time](/docs/architecture/real-time.md).

## Related

  
- [features/tunnel-protocol](/docs/features/tunnel-protocol.md) — Depends-on: live message and jar updates publish on conversation tunnel channels.

  
- [features/public-pools](/docs/features/public-pools.md) — Same-workflow: dao_jar money, dual-currency contribute, and jar refresh after chip-in.

  
- [features/generative-media](/docs/features/generative-media.md) — See-also: product-channel tool chats stay hidden from the Messages inbox.

  
- [features/store](/docs/features/store.md) — See-also: product agent chats share type product but stay inbox-visible.

  
- [features/webrtc-calls](/docs/features/webrtc-calls.md) — Next-step: 1:1 audio/video in direct chats.

  
- [features/peer-games](/docs/features/peer-games.md) — Same-workflow: game_request interactive type, IncomingGameBanner, and call/game mutex.

  
- [api/messaging](/docs/api/messaging.md) — Deep-dive: REST + tunnel contracts for conversations.

  
- [features/push-notifications-fcm](/docs/features/push-notifications-fcm.md) — Next-step: reach members when the tab is closed.
