Skip to main content
Saving bookmarks a message for the logged-in user. Unlike a pin, a save is private and cross-conversation: nobody else can see it, no role is required, and the saved list spans every conversation the user is part of.
savedAt is per-viewer. It is only ever populated in the acting user’s own context — you will never see another user’s saves on a message, so the same message reads saved for one user and unsaved for everyone else.

Save a Message

Call saveMessage() with the message’s ID. It resolves with the full updated message, with savedAt set.
Saving is idempotent — saving an already saved message succeeds rather than failing. There is no role gate. On a cap breach the rejection carries the server-owned ceiling in errorParams.limit.
A just-sent message may not be pinnable or savable yet. On an app with moderation enabled, the server stamps a new message moderation.status: "pending" and clears it to "approved" a moment later. While it is pending, saveMessage() rejects with 403 ERR_MESSAGE_NO_ACCESS — the same code it returns for a genuine permission refusal, so you cannot tell the two apart from the error alone.Measured on a moderated app, the window runs from send, not from the user’s tap, and clears within a few seconds. Do not disable the control on this error: by the time someone opens a menu and taps, moderation has usually finished. Prefer a retry or a transient “not ready yet” message over telling the user they lack permission. Moderation is configured per app, so this never reproduces on an app that has it switched off.

Unsave a Message

The resolved message comes back with savedAt cleared, never left stale.

Fetch Saved Messages

Build a MessagesRequest with setSavedOnly(true). The saved list is user-level, so unlike the pinned list you do not set a UID or a GUID — leaving both unset is what makes it cross-conversation.
The default value for setLimit is 30 and the max value is 100. The list comes back newest save first. Call fetchPrevious() again on the same object to page through older entries — this filter changes what the list contains, not how it pages.
Do not combine setSavedOnly(true) with setUID() or setGUID(). Saves are account-wide; adding a scope narrows the list to one conversation and quietly hides the rest.
Because the list spans conversations, every row carries its own context — use getConversationId(), getReceiverId() and getReceiverType() to route a tap on a saved message back to the right conversation.

Check if a Message is Saved

The React Native SDK has no isSaved() helper — the presence of savedAt is the boolean, and an unsaved message simply has no save attribute.
Do not port isSaved() from the JavaScript SDK — it does not exist in the React Native SDK. Derive it from getSavedAt() as shown above.

Real-time Save Events

Save and unsave are private multi-device events: they are delivered to the user’s other logged-in sessions so a save on the phone shows up on the desktop. Add the callbacks to your existing MessageListener.
These callbacks fire for saves made on your other devices, not the one that performed the save — that device already has the message resolved from saveMessage(). Update your saved list from the promise there, and from these callbacks everywhere else.

Save Limit

A user may save a capped number of messages across all conversations. Read the cap from app settings rather than hard-coding it.
It resolves to null when the app settings carry no value — show generic copy in that case rather than guessing a number.

Feature Availability

This resolves false rather than rejecting when the flag is missing or settings are unavailable.

Next Steps

Pin A Message

Highlight a message for everyone in a conversation

Pin A Conversation

Pin a conversation to the top of the list

Retrieve Conversations

Fetch and order the conversation list

Additional Message Filtering

Filter messages by saved, pinned, type, tags and more