Skip to content

Read conversations and messages

messages() returns enriched messages on all five platform SDKs. Each message includes reactions, a reply count, and its reply parent when available. There is no separate enrichedMessages() method.

OptionDefaultDescription
limit100 newestMaximum result count, 1 to 1000
sentBefore, sentAfterNoneFilter by sent time
insertedBefore, insertedAfterNoneFilter by local insertion time
sortBySent timeSort by sent or inserted time
directionAscendingAscending or descending
deliveryStatusAllPublished, unpublished, or failed
kindAllApplication or membership change
contentTypes, excludeContentTypesAll / noneInclude or exclude content types
excludeSenderInboxIdsNoneExclude sender inbox IDs

With no limit, messages() returns the 100 newest matching messages by the sort field, in the requested order. An explicit limit returns the first limit messages in the requested order. A limit below 1 or above 1000 fails with InvalidArgument. To read more messages, page with messageHistoryPage.

Time filters use Timestamp values. A Timestamp has an ns field for nanoseconds. Pass direction when order matters.

countMessages(options) returns a count without loading messages. It accepts ListMessagesOptions, but ignores limit, direction, and sortBy.

const
const messages: Message[]
messages
= await
conversation: Conversation
conversation
.
function messages(options?: ListMessagesOptions): Promise<Array<Message>>
messages
({
limit?: number | undefined
limit
: 10 });

Read the message’s content value and handle its content kind. For a reply, also inspect replyContent or inReplyToContent.

message.reactions holds at most the 50 latest reaction messages to a message, oldest first. reactionMessageCount counts every unexpired reaction message to it, including removals, so it is not a per-emoji tally. A UI that tallies from message.reactions undercounts a message with more than 50 reaction messages.

To read the rest, call messageReactions(messageId, before, limit) on the conversation. It returns reaction messages newest first, at most limit (1 to 1000, default 50). For the next page, pass the last returned reaction’s ID as before. A deleted or expired message has no reactions to read.

The cursor is the reaction’s current position. If that reaction expires or is deleted between two calls, the call fails with InvalidArgument; start again from the newest reaction. If it is your own reaction and it is published between two calls, it moves to the time the backend gives it, so the next page can repeat or skip reactions.

Use messageHistoryPage to paginate Published history by sent time and local delivery order. Use messageRecoveryPage for Failed and Unpublished rows. The time bounds on legacy messages() are strict selection filters, not paging cursors: a timestamp alone can skip messages that share a page-boundary timestamp.

Resume in the same direction with the page’s raw lastPosition unchanged, and use hasMore to decide whether to continue. Do not stop just because messages is empty: a page can consume rows that the SDK cannot convert and still have a position and more history.

A message can arrive after a newer message. For recent history followed by live delivery, use a message history snapshot and delivery cursor. A snapshot is a bounded recent tail. To replay all retained delivery history, start an independent reader at client.conversations.beginningDeliveryCursor() in each SDK.

Here is how to paginate Published history:

const
const options: {
limit: number;
direction: "descending";
}
options
= {
limit: number
limit
: 20,
direction: "descending"
direction
: "descending" as
type const = "descending"
const
};
const
const firstPage: MessageHistoryPage
firstPage
= await
group: Group
group
.
Group.messageHistoryPage(options?: ListMessagesOptions, before?: MessageHistoryPosition, after?: MessageHistoryPosition): Promise<MessageHistoryPage>
messageHistoryPage
(
const options: {
limit: number;
direction: "descending";
}
options
);
if (!
const firstPage: MessageHistoryPage
firstPage
.
hasMore: boolean
hasMore
|| !
const firstPage: MessageHistoryPage
firstPage
.
lastPosition?: MessageHistoryPosition | undefined
lastPosition
) return [];
// Keep the SDK position unchanged; it also covers unreadable rows.
const
const secondPage: MessageHistoryPage
secondPage
= await
group: Group
group
.
Group.messageHistoryPage(options?: ListMessagesOptions, before?: MessageHistoryPosition, after?: MessageHistoryPosition): Promise<MessageHistoryPage>
messageHistoryPage
(
const options: {
limit: number;
direction: "descending";
}
options
,
const firstPage: MessageHistoryPage
firstPage
.
lastPosition?: MessageHistoryPosition
lastPosition
,
);

We recommend listing allowed conversations only. This ensures that spammy conversations don’t appear in the main inbox.

MethodReturns
conversations.list(options)Groups and DMs
conversations.listGroups(options)Groups only
conversations.listDms(options)DMs only
const
const conversations: Conversation[]
conversations
= await
client: Client
client
.
ClientMembers.conversations: Conversations
conversations
.
Conversations.list(options?: ListConversationsOptions): Promise<Array<Conversation>>
list
({
consentStates?: ConsentState[] | undefined
consentStates
: ["allowed"],
});
OptionDefaultDescription
consentStatesAllowed and unknownConsent states to include
kindBothGroups or DMs
limit100Maximum result count, 1 to 1000
orderByLast activityCreation or last activity
createdBefore, createdAfterNoneCreation-time bounds
lastActivityBefore, lastActivityAfterNoneLast-activity bounds
beforeIdNoneResume after this conversation
includeDuplicateDmsfalseInclude every underlying stitched DM

These options are available on all five platform SDKs. Time bounds use Timestamp values. A list returns at most 100 conversations when you pass no limit. A limit below 1 or above 1000 fails with InvalidArgument.

A list returns conversations newest first, by listing time and then by conversation ID. To read every conversation once, set orderBy to creation time. For the next page, pass the last conversation’s createdAt as createdBefore and its ID as beforeId. A time alone skips conversations that share the boundary time.

In last-activity order, pass the last conversation’s lastActivityAt() as lastActivityBefore and its ID as beforeId. A conversation whose activity changes between two pages, because a message arrives or expires, can be returned twice or skipped. The cursor is read after the page, so a change to the last conversation moves it: a message that arrives there makes the next page repeat conversations, and its newest message expiring makes the next page skip conversations whose activity did not change.

Creation order can also return a DM twice or skip it when a message in another group of the same DM changes which group the list shows. A restored group whose Welcome arrives between two pages can be skipped. beforeId without the before time that matches orderBy fails with InvalidArgument.

lastActivityAt() with no content types ignores expired disappearing messages, so it is the time a last-activity list sorts by.

Do not use with streaming

Do not paginate a conversation list while a conversation stream adds items to that same list. Keep the stream and paginated result separate until pagination ends.