Skip to main content
With offline support enabled, a message you send while the device has no connection is durably queued on the device instead of failing. It keeps its place across an app restart and is sent automatically once the connection is back and stable (with autoSendPendingOnReconnect on, the default). This page shows the offline-aware send listener, how to read a message’s status, how to inspect and recover the send queue, and the queue limits.
Offline support is off by default and requires SDK 4.2.0 or newer. Queuing only happens once you enable it; with offline off, a send over no connection fails exactly as before.

Send a Message

Pass a MessageSentListener as the second argument to sendMessage. Its lifecycle is normally onMessagePending first (the message is durably queued), then its outcome: onMessageSent (the server accepted it) or onError. Offline, onError is a queued-not-failed notice and onMessageSent still fires later on reconnect, so both can fire for one send (see below). If the message cannot be queued at all, for example because its media could not be copied (reason PERMANENT), only onError fires.
The same MessageSentListener is accepted by all four send methods:
Passing a listener does not change the return value — each method still returns its Promise, and the Promise settles on the server outcome. Consume the final result from one channel, the Promise or the listener, not both. onMessagePending fires only while offline support is active.Only a send with a listener is queued. A send with no listener goes straight to the server and is never queued, so while offline it rejects and is not sent later. If you pass a listener and do not use the Promise, add a .catch(() => {}) to it so a rejection is not reported as unhandled.

Queued, Not Failed

When the device is offline, the send still fires onMessagePending, then fires onError once with the not-connected error: code FAILED_TO_FETCH, errorParams.queued === true, and reason equal to null. The Promise rejects with the same error. This is a notification, not a failure: the message stays durably PENDING, and onMessageSent fires later, on reconnect, when it actually goes. Do not send it again. This is the default behaviour, with autoSendPendingOnReconnect on. With it off, an offline send becomes FAILED (TRANSIENT) right away: onError fires with a reason, and you recover the message with retryMessage. So for a single send, onError and onMessageSent can both fire (offline, then reconnect), and onError does not always mean the message failed. Use reason (or error.errorParams.queued) to tell the two apart:

Message status and failure reason

Every message exposes its offline state. getLocalMessageId() returns the SDK-generated handle that is stamped at compose (while offline is running) and stays stable across the whole lifecycle — use it to retry or discard the message.
Read the offline status with getMessageStatus(). BaseMessage.getStatus() is the existing call/message status field and is not the offline status.
MessageFailureReason values:

Inspect the Queue

getPendingMessages returns the entire un-acked outbox — every PENDING, SENDING, and FAILED message, ordered by local compose time. Sent messages are not included. Read each message’s status to group them. It returns an empty list when offline support is off.
If the local store is briefly unavailable, the call rejects with ERROR_OFFLINE_MESSAGE_PERSIST_FAILED rather than returning an empty list — try again.

Observe the queue app-wide

The per-call MessageSentListener lives only as long as the call. To watch every queue transition — a live send, a reconnect auto-flush, a manual retry, or a resumed send after an app restart — register a global OfflineMessageListener. Its failed callback carries the MessageFailureReason. After an app relaunch with a saved login, add this listener straight after await CometChat.init(...) resolves, before you await anything else. Messages that fail during startup, for example when the SDK has to rebuild the cache, are delivered just after init resolves. A listener added later does not receive them.

Retry and Discard

There is no automatic retry of a FAILED message — recovery is explicit and user-invoked. A FAILED message stays in the outbox until you retry or discard it. Use the message’s local id. Each method takes an optional listener as a second argument; omit it and you get a Promise instead (never both).
  • retryMessage(localMessageId) re-attempts one message. A FAILED message is reset to PENDING and sent; a PENDING message is attempted now rather than waiting for a reconnect. The result is that attempt’s outcome.
  • retryAllFailedMessages() retries transient failures only; permanent ones are skipped. The Promise form resolves once the retries are issued; pass a MessageSentListener to see each outcome.
  • discardLocalMessage(localMessageId) deletes an unsent (PENDING or FAILED) message from the device, including its copied media. The discard’s own listener (DiscardMessageListener) gets onSuccess(localMessageId); the original send’s listener is released with no final outcome.
retryMessage and discardLocalMessage reject, changing nothing, when: These codes are available as CometChat.OfflineErrorCode.MESSAGE_IN_FLIGHT, MESSAGE_NOT_FOUND, DISABLED and MESSAGE_PERSIST_FAILED.

Auto-send on Reconnect

By default, queued PENDING messages are sent automatically when the realtime connection is re-established. This is controlled by OfflineConfig.autoSendPendingOnReconnect (default true); see Enable Offline Support and the reference. A queued message that keeps getting no answer is retried on each reconnect, up to 5 times, then becomes FAILED (TRANSIENT). With it off, nothing retries a queued message on reconnect — a send that gets no answer becomes FAILED (TRANSIENT), which you recover with retryMessage. Reconnect also runs a catch-up sync for received messages — see Stay in Sync.

Queue Limits

The outbox holds at most maxPendingMessages messages still trying to send (PENDING + SENDING); FAILED messages do not count. When the queue is full, OfflineConfig.pendingQueueOverflowPolicy decides what happens: A media send whose bytes do not fit in maxMediaCacheSize, even after cached received media is evicted, is refused with reason BYTE_OVERFLOW. Queued media is never evicted to make room. A refused send (QUEUE_FULL / BYTE_OVERFLOW) is never queued, so it fires no onMessagePending; it reports onError with the code ERROR_PENDING_QUEUE_LIMIT, and the global OfflineMessageListener receives onOutboxMessageFailed(message, reason) carrying the exact reason.
A refused or evicted message (QUEUE_FULL, QUEUE_OVERFLOW, BYTE_OVERFLOW) has no durable row — it does not appear in getPendingMessages, and calling retryMessage on its id rejects with ERROR_OFFLINE_MESSAGE_NOT_FOUND. Capture these from the failure callback and handle them at that moment. Prefer the default REJECT_NEWEST, which never silently drops an already-queued message from another conversation.

Edits, Deletes, and Reactions Need a Connection

Offline queuing applies to sending new messages only. Changes to existing messages — edit, delete, add or remove a reaction — and other own actions are not queued offline. They are sent to the server whenever you trigger them, and they reject with the not-connected error if the request gets no answer. Their effect reaches the cache only once the server accepts them.

Durable Media Uploads

A multi-attachment upload survives an app restart, but it never resumes on its own. Fetch the existing request for a conversation with getExistingUploadFileRequest (a Promise that resolves null when there is none), and read each file’s progress through getItems(), which returns a list of items, each with a state of QUEUED, UPLOADING, UPLOADED, FAILED, or REJECTED (which cannot be retried). Retry an individual file with retryAttachment(fileId). With offline on, each item also carries localCopyPath, the SDK’s copy of the bytes, for previews of a restored tray.
See Media Caching for downloading and caching received attachments.
For the full list of offline methods, listeners, enums, model fields, and error codes, see the Offline Configuration & API Reference.