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
type | Who sends it | body |
|---|---|---|
text (default) | Your users | Plain text, up to 64 KB. @poolse/react-ui renders it as GitHub-flavored Markdown. |
custom | Your users | A 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. |
system | poolse only | The 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 sameidandsequence.
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_idis always the top-level message. reply_counton 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_idandquoted_message_idtogether.
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:newon each mentioned user's user channel;- one
mention.createdwebhook 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_atand erases the body; - deletes the message's attachments, including the stored files;
- sends
message:deletedto the conversation, andconversation:updatedto members so the inbox preview and unread counts catch up; - fires the
message.deletedwebhook.
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.