On this page

No lost messages

No lost messages: clients that reconnect get the broadcasts they missed, in order, with no duplicates.

Phones go through tunnels, laptops sleep, tabs lose their connection. With message history, a channel that reconnects first receives the broadcasts it missed, in the order they were sent, then carries on live. Your listeners get each message once.

#Turn it on

In Studio open the Realtime inspector, choose Message history and Turn on message history. Then create channels with history: true:

JavaScript
const chat = stitchdb.channel('chat:42', { config: { private: true, history: true } })

chat
  .on('broadcast', { event: 'message' }, ({ payload, meta }) => {
    // meta.replayed is true for messages delivered after a reconnect
    showMessage(payload)
  })
  .subscribe()

// Send a message that is kept for clients that are offline right now.
// Resolves once it is stored: that is your delivery acknowledgement.
await chat.publish('message', { text: 'On my way' })

To also get recent messages on the first join (for example to show the last minute of a chat), pass history: { since: Date.now() - 60_000 }.

#Which messages are kept

Sent withKept
channel.publish(event, payload)Yes
realtime.send() in SQL, triggers, Edge FunctionsYes, see Broadcast from the database
channel.send() over the socketNo: fast and ephemeral (cursors, typing indicators). Use ack: true to know the server got it.
Postgres changesNo: send them with realtime.send() from a trigger to make them replayable
PresenceNot needed: the current state is sent on every join

Messages are kept for a few minutes, depending on your plan:

PlanHistory
Free2 minutes
Pro10 minutes
Scale30 minutes
Enterprise60 minutes

A client that was away longer gets everything still kept, and should reload its data from the database for the rest.

#Who can read and send

Public channels: anyone with your project key, as on the socket. Private channels (private: true): your Realtime policies on realtime.messages decide, for history exactly as for live messages. A read policy lets a user receive and replay; an insert policy lets them publish().

SQL
-- Members of a chat room read and send its messages (topics like chat:<room id>)
create policy "room members read" on realtime.messages for select to authenticated
  using (exists (select 1 from public.room_members m where m.user_id = auth.uid() and 'chat:' || m.room_id = realtime.topic()));
create policy "room members send" on realtime.messages for insert to authenticated
  with check (exists (select 1 from public.room_members m where m.user_id = auth.uid() and 'chat:' || m.room_id = realtime.topic()));

#HTTP API

Other languages can use the same endpoint the SDK uses, with the apikey header and, for a signed-in user, Authorization: Bearer <access token>:

RequestDoes
GET /history/v1/messages?topic=chat:42&after=<message id>Messages after that one, oldest first (since=<ms> instead of after, limit up to 500, private=false for public channels). Returns messages, more and gap (true if some messages were older than the history window).
POST /history/v1/messages with { topic, event, payload, private }Stores and broadcasts a message; returns its id

Sends count toward your Realtime messages and are limited to your plan's messages per second.