Skip to content

Send, edit, delete and react

Your app sends, edits and deletes messages, and adds reactions, as the signed-in user. Chat applies the same rules whatever your UI allows, so this guide also shows the errors to handle.

Let a user send a message with a reply and a mention, correct or delete it, react to it, and see a clear state when your backend has archived the conversation.

  • A ChatUserClient. See Get the user’s Chat token.
  • The user is a member of the conversation. Chat does not let a user write into a conversation they have not joined.
Chat permissions (all of): messages.sendmessages.updateUser scope: chat:access

messages.send(conversationId, { body }) needs messages.send and membership. The body can be up to 10,000 characters, otherwise you get body_too_long. A message needs a body or an attachment. Sending is limited to 30 messages per 10 s per user.

Add reply_to_message_id to reply. It must name a message of the same conversation, or you get invalid_reply. Add mentioned_user_ids to mention people. Chat records only the ones that are active members of the conversation.

A user cannot send a SYSTEM message: that is system_message_not_allowed and belongs to your backend. Attachments are references (file_id, name, mime_type, size); Chat stores no files. You can send at most 10 attachments per message.

The author edits or deletes with messages.update. A moderator holding messages.delete can edit or delete anyone’s message. Without either you get missing_capability. An edited message has status: 'EDITED'. A deleted message leaves a tombstone.

addReaction and removeReaction take the emoji. Reactions need membership. Machine clients cannot react.

When your backend archives a conversation, sending answers 422 conversation_archived. Show the thread as read-only and disable the composer.

write.ts
import { ChatForbiddenError, ChatUnprocessableError, type ChatMessage } from '@central-code-station/ccs-chat-user-client';
import { chat } from './fe-guide-context';
export async function replyToClient(conversationId: string, replyTo: string): Promise<ChatMessage | null> {
try {
return await chat.messages.send(conversationId, {
body: 'Great session, John. See you Thursday at 9!',
reply_to_message_id: replyTo,
mentioned_user_ids: ['client-john'],
});
} catch (error) {
if (error instanceof ChatUnprocessableError && error.code === 'conversation_archived') {
return null; // your backend archived it: show the thread as read-only
}
throw error;
}
}
export async function editOrDelete(conversationId: string, message: ChatMessage, mine: boolean): Promise<void> {
try {
if (mine) await chat.messages.edit(conversationId, message.message_id, 'Moved to 10:00');
else await chat.messages.delete(conversationId, message.message_id); // needs messages.delete
} catch (error) {
if (error instanceof ChatForbiddenError && error.code === 'missing_capability') return; // hide the control next time
throw error;
}
}
export async function react(conversationId: string, messageId: string): Promise<void> {
await chat.messages.addReaction(conversationId, messageId, '👍');
await chat.messages.removeReaction(conversationId, messageId, '👍');
}

Send a message from one user. It appears in messages.list for the other member. Edit it and the list shows status: 'EDITED'. Archive the conversation from your backend and send again to see the 422.

You see Cause Fix
403 missing_capability on send The role lacks messages.send in this application. A tenant administrator grants it.
403 not_conversation_member The user is not a member of the conversation. Add the user from your backend, or join a PUBLIC channel.
404 not_found on edit or react The message is deleted or belongs to another conversation. Reload the thread.
429 rate_limited Too many sends. Wait retryAfterSeconds on the ChatRateLimitedError.