Send messages to channels, poll for new messages, and handle message delivery on Sui.
Use `client.sendMessage()` to post a message to a channel. You provide the `channelId` and the `content` string. The method creates an on-chain transaction and returns a `MessageResult` with the message ID, timestamp, and transaction digest. Only active channel members can send messages - the Move contract enforces this.
Messages support plain text content by default. The `sendMessage()` method also accepts optional fields: `replyTo` to reference a parent message ID (for threaded conversations), `metadata` for custom key-value data, and `contentType` to indicate the format (e.g., `'text/plain'`, `'text/markdown'`). Keep content concise - on-chain storage has gas costs proportional to data size.
For files and rich content, use the `attachments` option. Attachment data is stored off-chain (via Walrus or similar storage), and only the reference hash is stored on-chain. This keeps gas costs low while supporting images, documents, and other media. Each attachment has a `name`, `mimeType`, and `blobId` that points to the off-chain data.
Retrieve messages with `client.getMessages()`. It returns a page of messages along with a `cursor` for pagination. Pass `channelId` to specify which channel to read from, and optional `limit` to control page size. Messages are returned in chronological order. The `cursor` field in the response lets you fetch the next page of results.
The messaging SDK uses cursor-based pagination to handle message history efficiently. After your first `getMessages()` call, use the returned `cursor` in the next request to get subsequent messages. This avoids offset-based issues like missing or duplicating messages when new ones arrive. Always store the latest cursor so you only fetch new messages.
Since on-chain messaging does not support push notifications natively, you use a polling pattern to check for new messages. Set up a `setInterval` that calls `getMessages()` with the last known cursor. When new messages arrive (the response contains messages), process them and update the cursor. A typical polling interval is 3-5 seconds on testnet. Clear the interval when the user leaves the channel.
Network requests and on-chain transactions can fail for many reasons: network timeouts, insufficient gas, channel not found, not a member, or channel archived. Always wrap SDK calls in try/catch blocks. The SDK throws typed errors: `ChannelNotFoundError`, `NotAMemberError`, `ChannelArchivedError`, and generic `MessagingError`. Inspect the error type to provide meaningful feedback to users.
For production apps, implement a retry strategy for transient failures. Use exponential backoff: wait 1s, then 2s, then 4s between retries, up to a maximum of 3 attempts. After a successful `sendMessage()`, verify delivery by checking the returned `digest` against the chain using `client.waitForTransaction(digest)`. This confirms the message is finalized on-chain and not just submitted to the mempool.