AI Integration Quick Reference
AI Integration Quick Reference
autoSendPendingOnReconnect on, the default). This page shows the offline-aware send overloads, 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.
sendTextMessage(message:onSuccess:onError:) and its media, custom and interactive siblings are unchanged. They never queue and never report a pending stage; to get the offline behaviour, call the overloads below.
Send a Message
Call the overload that takesonMessagePending, onMessageSent and onError. 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 (code ERROR_OFFLINE_MEDIA_COPY_FAILED, reason .permanent), only onError fires.
TextMessage, MediaMessage, and so on). An invalid send — empty text, or a custom message with no customData — is rejected with onError before anything is stored: it fires no onMessagePending and never enters the outbox.
All callbacks are delivered on the main thread.
Queued, Not Failed
When the device is offline, the send still firesonMessagePending, then fires onError once with the code ERROR_INTERNET_UNAVAILABLE. This is a notification, not a failure: the message stays durably pending, and onMessageSent fires later, on reconnect, when it actually goes. This is the default behaviour, with autoSendPendingOnReconnect on. With it off, an offline send becomes failed (.transient) right away and onError carries the same code, so read the outcome from the OutboxListener or from getPendingMessages, and 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. Read the error code to tell the two apart:
The closures you pass to a send live in memory. If the app is killed while a message is queued, the send still completes on the next launch, but your closures are gone. Use the app-wide
OutboxListener to hear about every outcome, including those.Message status and failure reason
Every message exposes its offline state.localMessageId is the SDK-generated handle stamped at compose time. It stays stable across the whole lifecycle — use it to retry or discard the message.
MessageFailureReason values:
Inspect the Queue
getPendingMessages(onSuccess:onError:) returns the entire unsent outbox — every pending, sending and failed message, ordered by local compose time. Sent messages are not included. Read each message’s messageStatus to group them.
onError with ERROR_OFFLINE_MESSAGE_PERSIST_FAILED rather than answering an empty list — try again.
The synchronous
getPendingMessages() -> [BaseMessage] still exists but is deprecated: it cannot tell an empty outbox from one that is momentarily unreadable.Observe the queue app-wide
The per-call closures live only as long as the app does. 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 globalOutboxListener. Its failed callback carries the MessageFailureReason.
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’slocalMessageId.
retryMessage(_:onSuccess:onError:)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.onSuccessfires when the message is sent;onErrorreceives a rejection or that attempt’s error.retryAllFailedMessages()retries transient failures only; permanent ones are skipped. It has no callback — watch the outcome on yourOutboxListener.discardLocalMessage(_:onSuccess:onError:)deletes an unsent (pending or failed) message from the device, including its copied media. The original send closures get no final outcome, and no outbox event fires.
retryMessage(_ message: BaseMessage) that takes the message itself and reports nothing. The callback forms of retryMessage and discardLocalMessage are Swift-only.
retryMessage and discardLocalMessage reject, changing nothing, when:
Auto-send on Reconnect
By default, queued pending messages are sent automatically, in the order they were composed, when the realtime connection is re-established. This is controlled byOfflineConfig.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 mostmaxPendingMessages unsent messages (pending and sending); failed messages do not count. When the queue is full, OfflineConfig.pendingQueueOverflowPolicy decides what happens:
A media send whose bytes would push the media cache over
maxMediaCacheSize is refused with reason .byteOverflow. The SDK first evicts cached received media, least recently used first, and refuses the send only if that cannot make it fit.
A refused send still reaches a final outcome. It fires onMessagePending, then onError with the code ERROR_QUEUE_FULL (or ERROR_OFFLINE_MEDIA_BYTE_OVERFLOW), and the OutboxListener receives onOutboxMessagePending followed by onOutboxMessageFailed(_:_:) carrying the exact reason. On the message you sent, messageStatus is .failed. An evicted message reports onError with ERROR_QUEUE_OVERFLOW on its own original send closures.
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 report an error if the request gets no answer. Their effect reaches the cache only once the server accepts them.Durable Media Uploads
Send aMediaMessage built with files and the SDK copies the file bytes to the device before it reports pending. The message then survives an app restart and is sent on reconnect. Only files you pass as files are copied. Files you already uploaded need no copy. Uploading new files to hosted storage does need a connection (see Upload Files).
An in-progress multi-file upload batch survives an app restart, but it never resumes on its own: the SDK keeps the batch on disk and you can re-attach to it. Fetch the existing request for a conversation with getExistingUploadFileRequest, and read each file’s progress through getItems(), which returns a list of UploadFileItemStatus (each with a state of type UploadFileState: .queued, .uploading, .uploaded, .failed, or .rejected, which cannot be retried). Retry an individual file with retryAttachment(fileId:); the SDK retries from its own copy, so you do not pass the file again.
getExistingUploadFileRequest returns nil when offline support is off, when nobody is logged in, or when no batch was kept for that chat. Files that were still uploading when the app was killed come back as .failed. For a restored item, file has a name but no bytes, and is nil once the file is uploaded — show a preview from localCopyPath instead.
See Media Caching for downloading and caching received attachments.