Skip to main content

Message Management

All message operations are executed through the specific Channel instance that the messages belong to.

Sending Messages

Text Messages

await channel.sendMessage({
text: 'Hello from Ermis Chat!',
mentioned_users: ['user_2'],
});

Messages support optimistic updates: the SDK immediately inserts the message into channel.state.messages with status: 'sending', then updates it to 'received' when the server confirms via WebSocket.

Messages with Attachments

The SDK provides uploadAndPrepareAttachments to handle file uploads in parallel, normalize file names, and automatically generate video thumbnails.

const files = [
/* File or Blob items */
];
const { attachments, failedFiles } = await channel.uploadAndPrepareAttachments(files);

if (failedFiles.length > 0) {
console.error('Some files failed to upload:', failedFiles);
}

await channel.sendMessage({
text: 'Check out these files!',
attachments,
});

Supported attachment types: image, video, file, voiceRecording, linkPreview.

Voice Recordings

You can pass voice recording metadata when uploading:

const voiceMetadata = new Map();
voiceMetadata.set(0, { duration: 5.2, waveform: [0.1, 0.5, 0.8, 0.3] });

const { attachments } = await channel.uploadAndPrepareAttachments(files, {
voiceMetadata,
});

Quoting a Message

await channel.sendMessage({
text: 'I agree with this!',
quoted_message_id: 'original_message_id',
});

Stickers

await channel.sendMessage({
sticker_url: 'https://example.com/sticker.webp',
});

Retrying Failed Messages

If the WebSocket is offline while sending, messages are stored locally with a failed_offline status. When the connection recovers, the SDK automatically retries these messages. You can also retry manually:

await channel.retryMessage('failed_message_id');

On connection.recovered, the SDK automatically retries all failed_offline messages that have no attachments.

Editing Messages

Only text and mentioned_users can be edited. Attachments cannot be modified after sending.

await channel.editMessage('message_id', {
text: 'Updated message text',
mentioned_users: ['user_2'],
});

Deleting Messages

Delete for Everyone

Permanently removes the message for all channel members. Permission rules differ by channel type:

  • Team channel: Only the owner can delete any member's message.
  • Messaging channel: Users can only delete their own messages.
await channel.deleteMessage('message_id');

Delete for Me

Removes the message only from the current user's view.

await channel.deleteMessageForMe('message_id');

Forwarding Messages

Forward an existing message to another channel. You can use the createForwardMessagePayload helper function from the SDK to automatically prepare the correct payload, handling edge cases like sticker-only messages and stripping out linkPreview attachments.

import { createForwardMessagePayload } from '@ermis-network/ermis-chat-sdk';

const forwardPayload = createForwardMessagePayload(
message, // The original message object
targetChannel.cid, // The target channel's CID
activeChannel.cid, // The current channel's CID
);

await activeChannel.forwardMessage(forwardPayload, {
type: targetChannel.type,
channelID: targetChannel.id,
});

Pinned Messages

Pin / Unpin

await channel.pinMessage('message_id');
await channel.unpinMessage('message_id');

Pinned messages are accessible via channel.state.pinnedMessages, sorted by pin date (newest first).

Reactions

Send a Reaction

await channel.sendReaction('message_id', 'heart');

Remove a Reaction

await channel.deleteReaction('message_id', 'heart');

Reactions are stored on each message as latest_reactions (all reactions) and own_reactions (current user's reactions).

Polls

Create a Poll

await channel.createPoll({
text: 'What should we do?',
poll_type: 'single', // 'single' or 'multiple'
poll_choices: ['Option A', 'Option B', 'Option C'],
});

Vote on a Poll

await channel.votePoll('poll_message_id', 'Option A');

Searching & Querying

Search Messages

Full-text search within a channel with pagination:

const result = await channel.searchMessage('keyword', 0);
// result.messages - array of matching messages with user info
// result.total - total number of matches

Query Attachment Messages

Retrieve all messages that contain attachments, filtered by type:

const attachments = await channel.queryAttachmentMessages();
// Returns messages with: image, video, file, voiceRecording, linkPreview

Paginated Message Loading

Load older or newer messages relative to a known message ID:

// Load older messages (before a given message)
const olderMessages = await channel.queryMessagesLessThanId('message_id', 25);

// Load newer messages (after a given message)
const newerMessages = await channel.queryMessagesGreaterThanId('message_id', 25);

// Load messages around a specific message (for jump-to-message)
const surroundingMessages = await channel.queryMessagesAroundId('message_id', 25);

Jump to a Specific Message

Load a message into the current state view (used for "jump to message" UI):

await channel.state.loadMessageIntoState('target_message_id');

// Jump back to the latest messages
await channel.state.loadMessageIntoState('latest');

Read Receipts & Typing Indicators

Mark as Read

When the user views the channel, mark it as read:

await channel.markRead();

Unread Count

Calculate unread messages locally:

const unreadCount = channel.countUnread();

// Or get unread count since a specific date
const unreadSince = channel.countUnread(lastReadDate);

Unread Status per Member

const readStatuses = channel.getUnreadMemberCount();
// Returns array of { last_read, unread_messages, user } for each member

Typing Indicators

Broadcast typing events to other members:

// When the user starts typing
await channel.keystroke();

// When the user stops typing or submits
await channel.stopTyping();

The SDK automatically throttles typing.start events to one every 2 seconds and cleans up stale typing indicators after 7 seconds of inactivity.

System Messages

System messages are auto-generated by the server when channel management actions occur (e.g. member added, name changed). They are stored as messages with type: 'system' and contain a coded format.

Use parseSystemMessage to convert them into human-readable text:

import { parseSystemMessage } from '@ermis-network/ermis-chat-sdk';

// Build a user map from channel state
const userMap: Record<string, string> = {};
Object.values(channel.state.members).forEach((m) => {
if (m.user) userMap[m.user.id] = m.user.name || m.user.id;
});

const text = parseSystemMessage(message.text, userMap);
// e.g. "Jane changed the channel name to Project Alpha."

System Message Types

CodeEvent
1Changed channel name
2Changed channel avatar
3Changed channel description
4Member removed from channel
5Member banned
6Member unbanned
7Member promoted to moderator
8Member demoted from moderator
9Member permissions updated
10User joined the channel
11User declined the invitation
12User left the channel
13Chat history cleared
14Channel visibility changed (public/private)
15Cooldown (slow mode) toggled
16Banned words updated
17Member added to channel
18Admin transferred ownership
19Message pinned
20Message unpinned

Signal Messages (Call Events)

Signal messages represent audio/video call events. They are stored as regular messages with a coded text format.

Use parseSignalMessage to parse them into a structured object containing the display text, call duration, call type, and color:

import { parseSignalMessage } from '@ermis-network/ermis-chat-sdk';

const result = parseSignalMessage(message.text, client.userID);
// result = { text, duration, callType, color }

The function returns a SignalMessageResult object:

PropertyTypeDescription
textstringHuman-readable description (e.g. "Incoming audio call...")
durationstringFormatted call duration (e.g. "2 min, 15 sec"), empty if none
callType'audio' | 'video' | ''The type of call
colorstringSuggested display color (#54D62C for success, #FF4842 for missed/rejected)

The displayed text is perspective-aware — it changes based on whether the current user (myUserId) is the caller or the recipient.

Signal Message Types

CodeEvent
1Audio call started
2Audio call missed
3Audio call ended
4Video call started
5Video call missed
6Video call ended
7Audio call rejected
8Video call rejected
9Audio call busy
10Video call busy