CloudSignal Docs
Core Concepts

Offline & Retain

How CloudSignal's MQTT infrastructure handles offline message queuing and retained messages - built into the protocol, no extra systems required.

One of the hardest problems in real-time messaging is what happens when a client is not connected. Do messages disappear? Do you need a separate message queue? A database to persist undelivered messages? A background job to retry?

With most messaging approaches - raw WebSockets, HTTP polling, custom pub/sub - the answer is yes. You build and maintain all of that yourself.

CloudSignal is built on MQTT, where offline delivery and message retention are protocol-level features, not application-level afterthoughts. The broker handles offline queuing, message persistence, and replay - no additional infrastructure required.

Offline message queuing

When a client disconnects, the broker does not discard its subscriptions. If the client used a persistent session, the broker continues accepting messages on its behalf and queues them for delivery when the client reconnects.

Offline queuing across a reconnect
Client
Broker
Publishers
Connect (persistent), subscribe agents/#
Subscription accepted
Client disconnects (network loss)
PUBLISH agents/agent-42/state
PUBLISH chat/conv-7/messages
PUBLISH tasks/task-3/progress
Reconnects (same client ID)
Replay queued messages in order
New messages flow normally

How persistent sessions work

A client requests a persistent session by connecting with clean_start=false (MQTT 5) or clean_session=false (MQTT 3.1.1). This tells the broker:

  1. Remember my subscriptions - Even when I disconnect, keep my subscription list
  2. Queue messages for me - Any message matching my subscriptions while I am offline should be stored
  3. Replay on reconnect - When I come back with the same client ID, deliver everything I missed

The client does not need to re-subscribe after reconnection. The broker restores the session state and delivers queued messages in order.

Session expiry

MQTT 5 introduced the Session Expiry Interval, giving you control over how long the broker holds a session after disconnect:

Expiry SettingBehaviorUse Case
0Session removed immediatelyStateless clients, dashboards
3600 (1 hour)Session held for 1 hourMobile apps with brief disconnects
86400 (1 day)Session held for 24 hoursIoT devices with intermittent connectivity
maxSession persists indefinitelyCritical devices that may be offline for days

Choose an expiry that matches your reconnection window. A mobile app that reconnects within minutes needs a short interval. A sensor in a remote location that may go offline for hours needs a longer one. Longer intervals consume more broker resources, so set them intentionally.

Why MQTT's approach is better

Building offline delivery on other transports means assembling multiple systems:

Custom approach
WebSocket Server
Message Queue
Database
Retry Worker
CloudSignal approach
Client
MQTT Broker
All built in

With a custom approach, you maintain:

  • A message queue (Redis, RabbitMQ, SQS) to buffer undelivered messages
  • A persistence layer (database) to survive restarts
  • A delivery tracker to know which clients received which messages
  • Retry logic with backoff and deduplication
  • Connection state management to detect who is online

With CloudSignal, the broker is the queue, the persistence layer, the delivery tracker, and the connection manager. It is a single system that handles offline delivery as a first-class capability because the MQTT protocol was designed for unreliable networks from day one.

Retained messages

Retained messages solve a different problem: what should a new subscriber see when it first connects?

Without retained messages, a subscriber only receives messages published after it subscribes. If an agent updates its presence every 30 seconds and a new dashboard client subscribes, it has to wait up to 30 seconds before seeing the agent's state.

A retained message is stored by the broker and immediately delivered to any new subscriber on that topic.

Without retain
Publisher
Broker
Subscriber
PUBLISH "online" to presence/agent-42
(time passes, then) SUBSCRIBE presence/agent-42
No data until next publish
With retain
Publisher
Broker
Subscriber
PUBLISH "online" to presence/agent-42 (retain=true)
Broker stores as retained for presence/agent-42
(time passes, then) SUBSCRIBE presence/agent-42
Immediately receives "online"

How retained messages work

  1. A publisher sends a message with the retain flag set to true
  2. The broker stores this message as the retained message for that topic
  3. The broker delivers the message to current subscribers normally
  4. When any new subscriber subscribes to that topic, it immediately receives the retained message
  5. A new retained publish to the same topic replaces the stored message
  6. Publishing an empty payload with retain=true clears the retained message

Only one retained message is stored per topic. It always reflects the most recent retained publish.

Practical uses of retained messages

Use CaseTopicRetained Payload
Agent presencepresence/{agent_id}{"online": true, "load": 0.42}
Configurationconfig/{service}/settingsCurrent configuration JSON
Latest agent stateagents/{agent_id}/state{"status": "idle", "last_task": "task_8a3"}
Feature flagsflags/{feature}{"enabled": true}
System announcementssystem/maintenance{"message": "Scheduled maintenance at 02:00 UTC"}

Retained messages are especially valuable for status and configuration topics. New clients get the current state immediately instead of waiting for the next update cycle. This eliminates the "cold start" problem where new subscribers have no data.

Offline queuing + retain together

These two features complement each other:

  • Offline queuing ensures a disconnected client does not miss messages published while it was away
  • Retained messages ensure a newly subscribing client gets the latest state immediately

Together, they cover both scenarios: clients that were connected and went offline, and clients that are connecting for the first time. No application-level caching, no "fetch initial state" API call, no synchronization logic.

Dashboard client lifecycle
Dashboard
Broker
Agents
First connect, SUBSCRIBE presence/+
Replay retained presence for every agent
Live presence updates published
Forward updates
Disconnect (persistent session)
Agents continue publishing
Broker queues updates for this client
Reconnect (same client ID)
Replay queued updates in order

Clean start vs. persistent sessions

The clean_start flag on connection determines which behavior you get:

SettingSubscriptionsOffline QueueRetained Messages
clean_start=trueCleared on connectNo queuingStill received on subscribe
clean_start=falseRestored from sessionMessages queued while offlineStill received on subscribe

Note that retained messages are delivered regardless of session type - they are a property of the topic, not the session. Offline queuing requires a persistent session.


Next steps

On this page