MQTT offline messages are the publications a broker holds for a subscriber that is temporarily disconnected and delivers once it comes back. This article explains exactly which messages qualify, what limits apply on real brokers, and walks you through a reproducible test so you can watch queuing and delivery happen yourself.
The Four Conditions for MQTT Offline Messages
Queuing is not automatic. A broker queues a message for an offline client only when all of these are true:
| Condition | What to check |
|---|---|
| The client has a persistent session | MQTT 3.1.1: cleanSession = false. MQTT 5: Clean Start = false and Session Expiry Interval > 0 |
| The session has not expired | MQTT 5 expiry not yet elapsed, broker limits not exceeded |
| The client was already subscribed to a matching topic filter | The subscription must exist before it went offline |
| The effective QoS is 1 or 2 | Delivery QoS is the lower of the publish QoS and the subscription QoS |
If any condition fails, the message is simply not delivered to that client. There is no error for the publisher, because MQTT decouples publishers from subscribers by design. That is why "my device missed a command" bugs are so common: the publisher sees a successful PUBACK from the broker, but the broker had no reason to keep the message for that particular subscriber.
What About QoS 0?
QoS 0 is "at most once". The specification allows a broker to queue QoS 0 messages for offline sessions, but it does not require it, and most brokers do not. Treat QoS 0 as live-only. The differences between levels are covered in detail in MQTT QoS levels explained.
What About Retained Messages?
Retained messages are a separate mechanism. The broker keeps the last retained message per topic and sends it to anyone who subscribes, whether or not they have a session. They solve "what is the current value?", not "what did I miss?". A reconnecting client with a clean session still gets the latest retained value on subscribe, but none of the intermediate updates. A good design often uses both: retained messages for state, queued QoS 1 messages for events and commands.
Limits That Apply to Offline Queues
Every broker puts bounds on queues, because a disconnected client that never returns would otherwise consume memory forever.
- Session lifetime. In MQTT 5, the session expiry interval decides how long the queue survives. When it expires, queued messages are discarded. On the public MQTT test broker broker.connectmq.com, session expiry is capped at 30 minutes. Choosing values is covered in MQTT session expiry interval.
- Queue depth. Brokers cap the number of queued messages or bytes per session. When the cap is hit, new messages are dropped or old ones evicted, depending on the broker.
- Message expiry. An MQTT 5 publisher can set a Message Expiry Interval. If a message sits in a queue longer than that, the broker discards it instead of delivering stale data.
- Message size. Plan and broker limits on message size apply to queued messages too (64 KB on the public broker, 256 KB on the ConnectMQ Serverless Free plan, 1 MB on the paid Serverless plan).
- Broker restarts. Where queues live depends on the broker. ConnectMQ keeps sessions and their queues in memory: they survive client disconnects for the granted session lifetime, but they are not kept across a broker restart. Clients should detect a lost session (Session Present = 0 in CONNACK), re-subscribe, and rely on retained messages or application-level resync for state.
Reproducible Test: Watch Offline Delivery Happen
This test takes about five minutes with the mosquitto command-line tools. Replace q5t9 with your own random string, and keep payloads harmless: the public area is readable by everyone.
- Create the session. Connect a subscriber with a fixed ID, Clean Start false, a 15 minute session expiry and QoS 1:
mosquitto_sub -h broker.connectmq.com -p 1883 -V mqttv5 \
-i offline-sub-q5t9 -c -x 900 -q 1 -v \
-t 'connectmq/demo/q5t9/cmd/#'
Go offline. Press Ctrl+C.
Publish a mix of QoS levels while the subscriber is away:
H="-h broker.connectmq.com -p 1883 -V mqttv5 -i offline-pub-q5t9"
mosquitto_pub $H -q 0 -t 'connectmq/demo/q5t9/cmd/a' -m 'qos0 message'
mosquitto_pub $H -q 1 -t 'connectmq/demo/q5t9/cmd/b' -m 'qos1 message'
mosquitto_pub $H -q 1 -t 'connectmq/demo/q5t9/cmd/c' -m 'short-lived' \
-D publish message-expiry-interval 5
mosquitto_pub $H -q 1 -t 'connectmq/demo/q5t9/other' -m 'not subscribed'
- Wait at least 10 seconds, then rerun the command from step 1.
You should see only connectmq/demo/q5t9/cmd/b qos1 message. Here is why each of the others is missing:
cmd/awas QoS 0, so it was not queued.cmd/chad a 5 second message expiry and was discarded in the queue.otherdid not match the subscription filter.
- Repeat without
-cin step 1. Nothing is delivered on reconnect, because the session was discarded when the connection closed.
The Same Pattern in Python
A device that reconnects and processes everything it missed:
import paho.mqtt.client as mqtt
from paho.mqtt.properties import Properties
from paho.mqtt.packettypes import PacketTypes
TOPIC = "connectmq/demo/q5t9/cmd/#"
def on_connect(client, userdata, flags, reason_code, properties):
if reason_code.is_failure:
print("Connect failed:", reason_code)
return
print("Connected. Session present:", flags.session_present)
if not flags.session_present:
# New or lost session: subscribe and fetch state another way
client.subscribe(TOPIC, qos=1)
def on_message(client, userdata, msg):
print(f"Processing {msg.topic}: {msg.payload.decode()}")
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
client_id="offline-py-q5t9",
protocol=mqtt.MQTTv5)
client.on_connect = on_connect
client.on_message = on_message
client.reconnect_delay_set(min_delay=1, max_delay=60)
props = Properties(PacketTypes.CONNECT)
props.SessionExpiryInterval = 900
client.connect("broker.connectmq.com", 1883, keepalive=60,
clean_start=False, properties=props)
client.loop_forever()
Designing Around Offline Clients
Queuing helps with short gaps, but it should not be your only reliability tool. A robust design combines several mechanisms:
- Use QoS 1 for commands and events, QoS 0 for high-frequency telemetry where the next reading replaces the last.
- Keep session expiry realistic. Long enough to cover typical outages, short enough that dead devices do not accumulate queues.
- Set message expiry on time-sensitive commands, so a device that returns after an hour does not execute an outdated instruction.
- Publish state as retained messages, so any client can resync instantly without depending on a queue.
- Make handlers idempotent. QoS 1 can deliver duplicates after reconnects; include a message ID or sequence number in the payload.
- Handle Session Present = 0 as a normal event, not an error.
- Expect a burst on reconnect. Clients that were offline may receive many messages at once; MQTT 5 Receive Maximum limits how many unacknowledged messages arrive in parallel.
For background on sessions themselves, read MQTT persistent sessions and the clean session flag. If you are just getting started, the MQTT tutorial covers the publish/subscribe basics first.
Troubleshooting Missing Offline Messages
- Nothing arrives after reconnect: confirm the client ID is identical, Clean Start is false and, in MQTT 5, the session expiry is non-zero.
- Some messages arrive, others do not: compare QoS of each publish and the subscription, and check for message expiry.
- Messages arrive twice: expected with QoS 1 after an interrupted acknowledgement; deduplicate in the application.
- Session Present is 0 unexpectedly: the session expired, the client ID changed, or the broker restarted.
Queue Offline Messages with ConnectMQ
ConnectMQ is a hosted MQTT broker that supports persistent sessions, session expiry and message expiry on MQTT 5.0 as well as MQTT 3.1.1 clients. The Serverless Free plan supports QoS up to 1, which is all offline queuing requires, and QoS 2 is available on paid plans. Create your free broker with no credit card, connect with YOUR_USERNAME, YOUR_PASSWORD and YOUR_CLIENT_ID, and run the test above against your own private endpoint. If you are comparing providers, see what to look for in a cloud MQTT broker.
Frequently Asked Questions
Does MQTT store messages when the subscriber is offline?
Only if the subscriber has a persistent session, was already subscribed to a matching topic, and the effective QoS is 1 or 2. Otherwise the message is not kept for that client.
Why are my QoS 0 messages lost while the device is offline?
QoS 0 messages are normally not queued for offline sessions; the specification makes it optional. Publish and subscribe with QoS 1 if the message must survive a disconnect.
How long does a broker keep offline messages?
Until the session expires, a queue limit is hit, the message's own expiry passes, or the broker restarts if sessions are held in memory. On ConnectMQ sessions live in memory, and the public test broker caps session expiry at 30 minutes.
Do retained messages replace offline queuing?
No. A retained message gives a new subscriber the latest value on a topic, while offline queuing delivers every QoS 1 or 2 message the client missed. Most systems use both.



