Practical MQTT patterns

MQTT retained messages vs message replay

Compare retained messages, persistent sessions, latest-value state, and replayable logs so each MQTT workload uses the right durability model.

An MQTT retained message answers “what is the last value published on this topic?” Message replay answers “what happened after my last processed position?” They look similar only when a topic has received one message.

Choosing the correct model avoids both lost history and unnecessary storage.

Four different delivery questions

MechanismQuestion it answersHistory kept
Live broadcastWho is connected right now?None for ordinary live delivery
Retained messageWhat value should a new subscriber see for this topic?One retained value per topic
Latest-value channelWhat is the current stored value and age for each matching topic?One current value per topic
Append channelWhat records has this consumer not acknowledged?A bounded ordered log plus consumer positions

MQTT persistent sessions add another mechanism: the broker preserves a session's subscriptions and eligible QoS deliveries while that client is away. This is session continuity, not a general-purpose event log that readers can independently seek.

Use retained messages for a published current value

A publisher sets the MQTT RETAIN flag when a message should become the retained value for its topic. A new matching subscriber receives that value immediately. A later retained publish replaces it.

Good examples include:

  • the desired mode of a device;
  • the last status deliberately published as retained;
  • a small configuration value;
  • a presence or discovery record with a clear lifecycle.

Retained messages are part of MQTT and need no Sagüin channel. They are not a historical sequence.

Use latest-value state for a managed topic region

Sagüin's latest channel keeps the current value for each topic claimed by its filter, together with channel position and age. It is useful when the topic hierarchy is a state tree and subscribers care about what is true now.

channels:
  device-state:
    type: latest
    filter: fleet/+/state/#

A subscriber can receive current values on subscribe without replaying every transition that produced them. This is typically a better fit for dashboards, device twins, last-known sensor state, or configuration snapshots.

Use append when the transitions matter

An append channel stores an ordered, bounded history. Each durable consumer has its own position, so one consumer can catch up after an outage without changing what another consumer has read.

channels:
  device-events:
    type: append
    filter: fleet/+/events/#

Choose append for audit trails, telemetry history, billing inputs, maintenance events, and integration feeds where losing intermediate records changes the meaning of the data.

A practical naming split

Many systems need both models. Make that visible in the topic design:

fleet/truck-17/state/location       current value
fleet/truck-17/state/ignition       current value
fleet/truck-17/events/position      historical event
fleet/truck-17/events/engine-start  historical event
fleet/truck-17/work/reindex         one-worker job

Then assign non-overlapping or deliberately prioritised channel filters. The topic name tells an operator whether a value is state, history, or work before they inspect the broker configuration.

Decision checklist

Ask these questions in order:

  1. Is it acceptable for a disconnected consumer to miss the message? Use broadcast.
  2. Does a new subscriber need only one publisher-selected current value? Use a retained message.
  3. Do you need one managed current value per topic, with Sagüin's state semantics? Use latest.
  4. Must each consumer process every retained record in order? Use append.
  5. Must exactly one worker attempt each available job? Use queue.

The detailed comparison and reconnect rules are in RFC 0003: Delivery semantics. Channel routing and overlap rules are in RFC 0002: Channels and configuration.