Skip to content

Messages

A message is a post in a conversation. A conversation is a text channel, the chat of a voice channel, or a thread inside a forum channel. This page explains a message’s lifecycle. The command pages hold the exact fields.

{
"cmd": "message_new",
"channel": "general",
"message": {
"id": "5b1f7c2e-8d0a-4f5e-9a51-3c2d7e8f9a10",
"user": "mist",
"timestamp": 1760000000.123,
"content": "Release notes are up: https://example.com/notes",
"type": "message"
}
}

The server assigns the id (a UUID) and the timestamp (Unix seconds with a fractional part). user is the author’s username. A message must have at least one of text content, an embeds entry, or an attachments entry.

See the message schema for every field, including embeds, reactions, mentions, and signing fields.

Task Command
Send text, files, embeds, or a poll message_new
Change a message message_edit
Remove a message message_delete
Load older history messages_get
Jump to a known message messages_around
Load one message or a list of IDs message_get, messages_batch
List replies to a message message_replies
Search text or filter by author, mentions, and content type messages_search
Pin, unpin, or list pins message_pin, message_unpin, messages_pinned

Every message command takes channel, thread_id, or both. For a thread message, send thread_id. If you also send channel, it must be the thread’s parent forum.

When a client sends message_new, the server:

  1. Checks that the caller can view the channel and has its send permission. In a thread, it also checks that the thread is not locked.
  2. Validates embeds, attachments, and the optional alias, and checks role mentions.
  3. Applies slow mode. A slowed message gets a rate_limit frame instead of an error.
  4. Checks the length against limits.post_content (2000 characters by default).
  5. Runs automod.
  6. Saves the message, then sends it back to the caller and broadcasts it to everyone else who can view the channel.
  7. Reopens the thread if it was archived.
  8. Queues push notifications and link previews in the background.

The caller’s copy carries the request listener. Other connections receive the same message_new without it. Users severed from the author don’t receive it.

Send reply_to with a message ID to reply. The stored message gets a reply_to object with the target’s ID, author, and the first 40 characters of its text. Send ping: false to reply without notifying the author.

Mentions are written in the message text:

  • @username mentions a user.
  • @&<role_id> mentions every member of a role. The sender needs the role to have is_mentionable, or needs mention_roles or administrator. The server owner can mention any role. Otherwise the send fails with You do not have permission to mention the '@&<role_id>' role.

The server records who was mentioned when it saves the message. pings_get returns that history, and unread counts include a per-conversation ping count. See read state and mentions.

Channel permissions decide who can change a message. See roles and permissions for the defaults.

Action Your own message Someone else’s message
Edit edit_own edit
Delete delete_own delete

An edit sends message_edit with the complete updated message, so replace your copy. The message gets edited: true. When someone edits another user’s message without signing the edit, the server removes the author’s signature and adds edited_by.

A delete sends message_delete with only the id and the conversation. Deleting a message also deletes its attachments and its poll.

When a message contains web links, the server can fetch a preview for each one and attach it as an embed after the message is saved. Clients don’t need to fetch pages themselves.

Link previews are on by default. Operators control them with embeds.enabled and embeds.proxy_url in the server configuration. When they are on, the handshake lists the server_side_embeds capability.

The server queues a preview job after a message_new or message_edit sent over the WebSocket, including public slash command replies. Webhook posts and system messages don’t get previews.

For each job, the server:

  • Takes up to five http or https URLs from the message text. It skips URLs inside code spans and code blocks, and URLs wrapped in angle brackets such as <https://example.com>. Trailing punctuation is trimmed.
  • Skips a URL if the author already supplied an embed with the same url.
  • Treats URLs ending in an image, video, or audio file extension as media. These become image, video, or audio embeds without fetching anything.
  • Fetches other pages through the configured proxy, with an 8-second timeout and a 1 MiB limit. It never fetches localhost, private network ranges, or hosts ending in .local or .internal.
  • Builds a link embed from the page’s Open Graph and Twitter tags, falling back to <title> and the description meta tag. A page with no title, description, or image gets no preview.
  • Stops once the message has 10 embeds in total.

The job only runs if the message still has the same text. Jobs are dropped if the queue already holds 64 jobs.

Generated embeds are saved into the message’s embeds array after any embeds the author supplied. Each one has generated: true:

{
"id": "3f9a1c0b7d2e4a5b6c",
"type": "link",
"url": "https://example.com/notes",
"generated": true,
"title": "Release notes",
"description": "What changed in this release.",
"image": { "url": "https://example.com/cover.png" },
"site_name": "Example",
"footer": { "text": "Example" },
"provider": { "name": "Example" }
}

When a message is edited, the server rebuilds its generated embeds. Previews for links that are still present are kept without refetching. Previews for removed links are dropped.

If the generated embeds change, the server sends a message_edit with the full updated message. It goes only to connections that can view the channel and have reported server_side_embeds with the capabilities command. Other clients see the previews the next time they load the message.

This message_edit is not a user edit. The message’s edited flag doesn’t change.

A sticky message stays at the bottom of a text or voice channel. Moderators set it with the built-in /sticky command, and it’s stored as the channel’s sticky_message ID.

When someone posts in that channel with message_new, the server moves the sticky message to right after the new message. It marks the message edited: true and sends two events after the new message:

  1. message_delete for the sticky message’s ID.
  2. message_new with the sticky message and its new timestamp.

Handle these like any other delete and create. The sticky message keeps its ID.

Pinning needs the channel’s pin permission. Each pin or unpin also posts a system message that replies to the pinned message and says who pinned it. messages_pinned lists the pins in a conversation.

A message can carry an alias object that sets the name, avatar, and color shown for that one message. A bridge can use it to show who a relayed message came from. alias.name is required, up to 80 characters. The message’s user stays the real author.

When a user sends a message with message_new, the server can send a Web Push notification through Rotur to people who aren’t looking at the app. Edits, webhook posts, slash replies, and system messages don’t trigger pushes.

The operator chooses who gets notified with notifications.mode:

  • mentions (default): people mentioned directly, members of mentioned roles, and the author of the message being replied to.
  • all: every member who can view the channel.

The server never notifies the author, members who have left, banned users, users who can’t view the channel, or users severed from the author. The notification text turns mentions, channel links, custom emoji, and timestamps into readable text.

Delivery needs notifications.enabled and a Rotur token on the server. It happens in the background after the message is saved, so a successful message_new doesn’t mean a push was delivered. See notifications to register a client for pushes, and server configuration for the settings.