Messages

A message belongs to one conversation and has one sender. The endpoints are on Messages. The realtime events are on the conversation channel. This page explains how the pieces fit.

Types

typeWho sends itbody
text (default)Your usersPlain text, up to 64 KB. @poolse/react-ui renders it as GitHub-flavored Markdown.
customYour usersA JSON string your app defines, for cards, polls and the like. It must parse as JSON, otherwise 422 validation_failed. poolse does not look inside it.
systempoolse onlyThe call record poolse writes when a call ends. See the call log. Sending type: "system" is refused (422).

A message needs text or attachments. A blank body with at least one attachment is stored as "". A blank body with none is refused.

Only text messages trigger chat notifications.

Message bodies are encrypted at rest. See Encryption & retention.

Order: sequence

Every message gets a sequence: an integer per conversation that goes up by one with every message. It counts thread replies and call-log messages too.

  • Order messages by sequence, not by timestamp.
  • Page back through history with ?before=<sequence>. You get up to 100 messages per page, newest first.
  • De-duplicate on id. A message you get from both REST and the socket has the same id and sequence.

The numbers you see in the main history can skip: deleted messages keep their number but are left out, and thread replies are listed separately.

Sending safely

Pass your own id (a UUID you generate) when sending. If you retry the same send, the second one fails with 422 because the id is taken, instead of posting a duplicate. The SDK generates the id for you and shows the message right away (optimistic send), then replaces it when the server's copy arrives.

The SDK also sends an Idempotency-Key header on every non-GET request. See Errors.

Threads

Set reply_to_id to reply in a thread.

  • Replying to a top-level message starts a thread on it. That message becomes the thread's root.
  • Replying to a reply puts the new message in the same thread. Threads are one level deep: thread_root_id is always the top-level message.
  • reply_count on a root counts its replies that are not deleted.

Thread replies are not in the main history (GET /v1/conversations/:id/messages). Fetch them with GET /v1/messages/:root_id/replies: oldest first, 500 per request by default, up to 1000, paged with ?after=<sequence>.

Over the socket, replies arrive as ordinary message:new events. Use thread_root_id to route them to the thread view. useThread does this for you.

Thread replies do count toward the conversation's unread count.

Quote replies

Set quoted_message_id to quote an earlier message, the way WhatsApp does. The new message stays in the main history. It carries quoted_message, a preview of the original: id, sender_id, sender_external_id, body (first 200 characters), deleted_at, inserted_at.

  • The quoted message must be in the same conversation.
  • You can use reply_to_id and quoted_message_id together.

Mentions

mentions is a list of poolse user ids. Every id must belong to a member of the conversation, otherwise the send fails with 422 and no message is posted.

poolse does not read @names in the text. Your composer decides who is mentioned and sends their ids. MentionInput in the UI kits does this.

A mention causes:

  • mention:new on each mentioned user's user channel;
  • one mention.created webhook per mentioned user;
  • a chat notification for them even if they muted the conversation, or if group notifications are set to mentions only.

Reactions

Any member can react with any emoji string, up to 64 characters.

"reactions": { "👍": ["2b7e…", "9c41…"], "🎉": ["2b7e…"] }

reactions maps each emoji to the poolse ids of the people who used it. Reacting twice with the same emoji does nothing. Removing a reaction you never added succeeds. An emoji with no reactions left disappears from the map. You cannot react to a deleted message (409).

Reactions are on the socket (reaction:added, reaction:removed). They are not webhook events.

Attachments

Files are uploaded first, then linked to the message that carries them. See Attachment uploads.

  • Up to 10 attachments per message, with no duplicates.
  • Each attachment can be linked to one message, and only by the user who uploaded it.
  • Size limits depend on your plan. See Limits & quotas.

Editing

Only the sender can edit, and only body. Type, attachments, mentions and quotes cannot change. An edit sets edited_at and sends message:updated and message.updated. If the message is the conversation's latest, the inbox preview is rewritten too, and members get conversation:updated. A deleted message cannot be edited (409 message_deleted). An edit cannot blank the text of a message that has no attachments (422). poolse keeps no edit history.

The server allows edits at any time. The UI kits are stricter: canEditOwnMessage in @poolse/react allows editing until the message is read while read receipts are visible, and for 15 minutes when they are hidden. That rule lives in the client only.

Deleting

The sender can delete their own message. Owners and admins can delete anyone's. Deleting:

  • sets deleted_at and erases the body;
  • deletes the message's attachments, including the stored files;
  • sends message:deleted to the conversation, and conversation:updated to members so the inbox preview and unread counts catch up;
  • fires the message.deleted webhook.

Deleted messages are left out of the history and thread endpoints from then on. Clients that were connected at the time get the message:deleted event and can show a placeholder. Deleting twice is not an error. Deleted messages cannot be restored.

Not available

  • Pinning, starring, forwarding, or scheduled messages.
  • Edit history.
  • Search.
  • Sending as another user, or sending from your backend with an API key. Every send uses the sender's own JWT.
  • "Delivered" receipts. There are only read receipts.
Messages · Concepts · poolse docs