Skip to main content

Open navigation

Back to blog

MQTT QoS 0, 1 and 2: An Industrial Guide with a Mqttable Lab

Follow telemetry, alarms and work orders through MQTT QoS 0, 1 and 2. Use Mqttable screenshots, packet captures and controlled faults to separate protocol delivery from business execution.

Author:
Mqttable Editorial
Published:
Hand-drawn concept illustration of MQTT QoS 0, 1 and 2 with telemetry, alarms and work orders; not a product screenshot or a guarantee of exactly-once device execution.

A temperature sample misses the dashboard, but the next reading arrives a second later. An overheating alarm never reaches the event system, so a technician misses a diagnostic clue. A production work order runs twice and changes a process, counter or material state incorrectly. All three messages can use MQTT, but their tolerance for loss, duplication and delay is different.

MQTT QoS 0, 1 and 2 provide at-most-once, at-least-once and exactly-once protocol delivery mechanisms. Choosing one requires more than remembering those names: identify which hop sends the acknowledgement, whether session state survives, and whether the receiving application has its own deduplication and result handling.

This tutorial follows a simulated production line and uses an isolated Mqttable Web Runtime for publishing, subscribing and controlled packet-loss experiments. The scenario, temperatures, work orders and Client IDs are teaching data, not customer production records. Screenshots demonstrate protocol behavior, not real machinery actions, desktop-package acceptance or production-deployment acceptance. This article is published by Mqttable.

Start with three different industrial messages

Assume production line line1 has a motor named motor7 and three message classes.

Scroll the table sideways to see all columns.

MessageExample TopicBusiness concernA starting point for evaluation
Periodic telemetryfactory/line1/motor7/telemetryThe dashboard needs a recent temperature; complete historical sampling is a separate requirementQoS 0 for a real-time dashboard that can tolerate occasional gaps
Fault alarmfactory/line1/motor7/alarmsThe alarm should reach the event system; duplicates must not create multiple maintenance ordersQoS 1 with a stable event_id and idempotent storage
Production work orderfactory/line1/work-ordersThe command should be received; duplicate and expired execution need protectionEvaluate QoS 1 or 2 together with command_id, execution results and validity checks

There is no universal rule that telemetry uses 0 and control uses 2. Losing a sample used for traceability, metering or quality decisions may be expensive. Even with QoS 1, the system may need gateway storage, replay and completeness checks. An alarm with a short useful lifetime also needs protection against stale events being treated as new after reconnection.

QoS 2 does not remove the need to deduplicate work orders. Once one MQTT delivery is complete, publishing the same command_id again creates another Application Message. The protocol does not inspect JSON to find your business identifier.

What QoS 0, 1 and 2 acknowledge

QoS describes a protocol delivery between a sender and a receiver. Each diagram below represents one hop, not the entire Publisher-to-Broker-to-Subscriber path. The definitions and flow rules are in MQTT 5.0, section 4.3.

QoS 0: send without an MQTT acknowledgement

text
Sender                         Receiver
       PUBLISH, QoS 0, DUP=0 ->

The receiver does not send PUBACK for this PUBLISH, and the sender does not retransmit it through the MQTT QoS mechanism. It may arrive once or not at all. A QoS 0 PUBLISH has no Packet Identifier.

This does not mean that the transport is unreliable UDP. Ordinary MQTT over TCP still uses TCP ordering and retransmission. QoS 0 omits the MQTT delivery acknowledgement and associated retransmission state. If a connection breaks at a critical point, or an intermediate component fails to forward the complete message, QoS 0 alone cannot establish that the receiver got it.

For a dashboard interested in the latest temperature, a burst of old samples after recovery may increase queueing and staleness. Include a sequence number and sampling timestamp so the receiver can detect gaps and data age. A dashboard that still displays a value is not proof of complete sampling.

QoS 1: transfer delivery responsibility, with possible duplicates

text
Sender                         Receiver
       PUBLISH, QoS 1, id=17 ->
       <- PUBACK, id=17

The sender treats PUBLISH as unacknowledged until the corresponding PUBACK arrives. A successful acknowledgement completes the transfer of delivery responsibility for this hop. The receiver may have processed the message while PUBACK was lost on its way back; session recovery can cause the PUBLISH to be sent again.

MQTT 5 acknowledgements can carry failure Reason Codes. Inspect the code in PUBACK or PUBREC rather than calling every acknowledgement a successful publication. The normal exchanges here use successful acknowledgements; for authorization failures and other rejection cases, see the MQTT error-code guide.

QoS 1 fits events that should not be casually lost and whose duplicates can be recognized. An alarm can carry a stable event_id, with the event system enforcing uniqueness on that identifier. A duplicate returns the existing record or processing result instead of creating another maintenance order.

PUBACK is not proof of a committed business transaction. A client library may acknowledge automatically, while a database write, Webhook request or device action fails later. The consumer still needs its own commit, retry and result-handling design.

QoS 2: distinguish protocol retransmission from a new delivery

text
Sender                         Receiver
       PUBLISH, QoS 2, id=21 ->
       <- PUBREC, id=21
       PUBREL, id=21 ->
       <- PUBCOMP, id=21

QoS 2 uses a two-stage acknowledgement exchange. The receiver retains state associated with the Packet Identifier to distinguish retransmission from a new delivery. After PUBREC, the sender waits for the PUBREL/PUBCOMP stage to complete. A lost packet at any stage does not always mean the entire business message is sent again.

Exactly-once delivery applies to this protocol exchange and depends on both endpoints following the protocol and retaining the required state. It does not make every business action in an industrial system execute once, and it does not compare two payloads for equality. Transferring a message to a database, queue or device introduces another boundary with its own failure and duplication risks.

A normal one-hop exchange has one PUBLISH for QoS 0, two packets for QoS 1, and four for QoS 2. These counts exclude CONNECT, subscriptions, heartbeat and TCP acknowledgements; they are not throughput measurements. Actual latency also depends on RTT, in-flight limits, persistence, load and the client implementation.

Neither Packet Identifier nor DUP is a business key

A Packet Identifier correlates acknowledgements within a protocol flow and can be reused after completion. The identifier used by the Publisher does not need to match the Broker's identifier on the next hop. Do not store it as a globally unique work-order ID.

DUP=1 on PUBLISH means the packet may be a retransmission. It does not prove that the business has already processed it. Conversely, DUP=0 does not prove that its business content is new: an application can publish the same JSON again as a new MQTT message.

Observe the two delivery hops separately

text
Publisher          Broker           Subscriber
       First hop ->       Second hop ->

The Publisher's publish QoS governs the first hop. The Subscriber's subscription options affect the Broker's delivery on the second. SUBACK reports the granted subscription QoS, so inspect that response rather than only the requested value in the form.

For the single, non-overlapping subscription used here, the outgoing QoS is no higher than the smaller of the publish QoS and granted subscription QoS. A Broker can also reduce the granted QoS or reject the subscription. Our publish-2/subscribe-1 message arrives at QoS 1; publish-1/subscribe-2 still arrives at QoS 1. A subscription cannot upgrade the protection of an earlier hop.

A Publisher receiving PUBACK establishes that the Broker acknowledged that publication. It does not establish that every Subscriber received it or committed a database transaction. Broadcasting an alarm to three systems produces three independent receiving and processing outcomes.

Prepare the isolated Mqttable lab

You need Mqttable, a Broker you own or are authorized to test, and a Proxy for the fault experiments. Mqttable does not include an MQTT Broker. This lab uses local Mosquitto with every port bound to 127.0.0.1. Anonymous access is only for this disposable local experiment, not a production configuration.

If you do not have a test Broker, save this configuration in a new lab directory and leave the second command running in its terminal. If the port is occupied, choose a free port and update the Proxy upstream. Do not stop someone else's Broker to free the port.

bash
cat > mosquitto.conf <<'EOF'
listener 18885 127.0.0.1
allow_anonymous true
persistence false
queue_qos0_messages false
max_queued_messages 1000
EOF
mosquitto -c "$PWD/mosquitto.conf" -v

Scroll the table sideways to see all columns.

ObjectValues used in this tutorial
Mosquitto upstream127.0.0.1:18885
ProxyQoS Lab Proxy; listen on 127.0.0.1:38885; upstream 127.0.0.1:18885; TCP
Mqttable Broker definitionQoS Factory Lab; protocol mqtt; Host 127.0.0.1; Port 38885
PublisherName publisher; Client ID qos-lab-publisher
SubscriberName subscriber; Client ID qos-lab-subscriber
Both clientsMQTT 5.0; no authentication; Clean Start off; Session Expiry Interval 3600 seconds

Create a dedicated definition using Brokers and connections. Configure and start the local listener and upstream in Proxy. Do not inject packet loss into a workspace carrying real messages.

Mqttable Edit Client shows MQTT 5.0, Clean Start off and a 3600-second Session Expiry Interval.

This configuration requests a recoverable session. Check Session Present in the reconnect CONNACK to establish whether recovery actually happened.

Add three exact subscriptions to the Subscriber with QoS 0, 1 and 2 respectively:

text
factory/line1/motor7/telemetry   QoS 0
factory/line1/motor7/alarms      QoS 1
factory/line1/work-orders       QoS 2

Leave No Local off, do not create wildcard or overlapping subscriptions, and leave Retain off when publishing. Wait for three successful SUBACK responses before sending. The controls are explained in Publish and subscribe.

Count application deliveries independently

Mqttable Trace records MQTT packets. A retransmitted QoS 2 PUBLISH can appear more than once in Trace while the protocol client delivers the message to its application once. Counting those rows is not a business-execution counter.

We also run mosquitto_sub as the independent consumer qos-lab-observer, through the same Proxy, and record application deliveries as JSON Lines. It and the Mqttable Subscriber have independent sessions. Its count establishes delivery to this consumer, not shared Packet Identifiers or real equipment actions.

bash
mosquitto_sub -h 127.0.0.1 -p 38885 -V mqttv5 \
  -c -x 3600 -i qos-lab-observer -q 2 \
  -t 'factory/line1/motor7/telemetry' \
  -t 'factory/line1/motor7/alarms' \
  -t 'factory/line1/work-orders' \
  -F '%j' > observer.jsonl

-c requests persistent client mode; -x 3600 sets the MQTT 5 session lifetime; -F '%j' records the received QoS, Retain flag and payload. See the Mosquitto subscriber manual. Across machines or with a Remote Runtime, 127.0.0.1 means the machine running that program or Runtime, not necessarily the computer displaying the interface.

Publish QoS 0, 1 and 2 step by step

Step 1: publish a temperature sample at QoS 0

Select qos-lab-publisher in the send composer. Set Topic to factory/line1/motor7/telemetry, expand the payload, select JSON and enter:

json
{ "sensor": "motor7", "seq": 1, "temperature_c": 62.4, "phase": "baseline" }

Select QoS 0, leave Retain off, then send once.

Mqttable composer selects the Publisher, telemetry Topic, JSON payload and QoS 0 with Retain off.

Check the client, Topic and QoS first. The phase field distinguishes the baseline from later fault messages.

Minimize the composer and inspect the Publisher's sent PUBLISH and Subscriber's received PUBLISH in Trace. Their Topic and payload should match, with QoS 0 and no corresponding PUBACK. The independent consumer recorded one seq=1 sample.

Mqttable Trace shows sent and received QoS 0 PUBLISH packets with the same telemetry payload.

These are packet observations from two clients, not two application executions. Observing this successful delivery does not guarantee the next one.

Step 2: publish an alarm at QoS 1

Change Topic to factory/line1/motor7/alarms, select QoS 1 and enter:

json
{
  "event_id": "A-001",
  "asset": "motor7",
  "alarm": "over_temperature",
  "temperature_c": 91.2,
  "phase": "baseline"
}
Mqttable composer configures the alarm Topic, a stable event_id and QoS 1.

The application supplies event_id. An MQTT Packet Identifier does not replace it.

After one send, check the Publisher's PUBLISH/PUBACK and the Subscriber's PUBLISH/PUBACK separately. The matching acknowledgement uses the same Packet Identifier within its hop; the two hops do not need identical identifiers. The independent consumer's baseline alarm count is one.

Mqttable Trace shows the Publisher and Subscriber QoS 1 PUBLISH/PUBACK exchanges separately.

The Publisher's PUBACK and Subscriber's PUBACK belong to different sessions. One is not evidence of the other's result.

Step 3: publish a production work order at QoS 2

Change Topic to factory/line1/work-orders, select QoS 2 and enter:

json
{
  "command_id": "WO-001",
  "operation": "load_recipe",
  "recipe": "R17",
  "target": "line1",
  "phase": "baseline"
}
Mqttable composer configures the work-order Topic, command_id and QoS 2.

This teaching work order is not connected to equipment that loads a recipe.

Send once, then observe PUBLISH, PUBREC, PUBREL and PUBCOMP for each hop. Enable ID in the column menu and correlate Client ID, direction and Packet Identifier rather than mixing the two handshakes together. The independent consumer receives one baseline WO-001.

Mqttable Trace shows complete four-stage QoS 2 handshakes for the two clients.

A completed handshake establishes protocol completion, not recipe loading or a committed business transaction. Read message QoS from PUBLISH; reserved PUBREL header bits do not mean this business message was downgraded to QoS 1.

Controlled packet loss exposes the boundaries

Inject faults only into the dedicated local Proxy. Packet loss here discards selected MQTT packets, rather than directly dropping IP packets. Do not interpret it as evidence that TCP has no retransmission.

Enable one loss rule at a time: Loss 100%, Toxicity 100%, Always On. After removing the rule and restoring connections, send a separate recovery message to confirm the path is working before the next experiment. Do not repeatedly click Send during a fault; that would mix new business publications with protocol retransmission.

QoS 0: recovery does not acknowledge the old sample

Add Packet loss in Proxy, select Downstream and select only PUBLISH. Both Mqttable clients and the independent consumer are connected before enabling it.

Mqttable Proxy configures 100% downstream loss for PUBLISH only.

Dropping Broker-to-client PUBLISH while keeping connection and heartbeat traffic avoids confusing an unestablished connection with message loss.

Publish seq=2, phase=fault once. Remove the rule and publish seq=3, phase=recovery. Inspect the independent consumer's sequence numbers and receipt times, not only the Publisher's send result.

Mqttable shows packet evidence for the QoS 0 fault and recovery messages.

The independent consumer received seq=1 and seq=3, but not seq=2. A new message arriving after recovery does not establish that the dropped old PUBLISH was retransmitted by QoS.

The receiver-version boundary for these fault experiments

The normal delivery demonstrations and QoS 0 loss experiment use MQTT 5.0. In this fixed Proxy version, the selected MQTT 5 ACK-loss rule did not produce the intended fault: the Broker still received acknowledgements. Configuring a rule is not proof that its effect occurred. The following two ACK-loss experiments therefore use MQTT 3.1.1 receivers, while the Publisher remains on 5.0.

Disconnect the Mqttable Subscriber, change its protocol to 3.1.1, turn Clean Session off and reconnect. Do not apply the MQTT 5 Session Expiry parameter to 3.1.1. Stop the old independent consumer and start this process with the same Client ID; do not run both consumers at once:

bash
mosquitto_sub -h 127.0.0.1 -p 38885 -V mqttv311 \
  -c -i qos-lab-observer -q 2 \
  -t 'factory/line1/motor7/telemetry' \
  -t 'factory/line1/motor7/alarms' \
  -t 'factory/line1/work-orders' \
  -F '%j' > observer-v311.jsonl

These results establish behavior for the tested 3.1.1 receiver sessions only. This article does not claim a successfully reproduced MQTT 5 ACK-loss experiment. Recheck bidirectional packets and application counts after changing product versions.

QoS 1: the receiver gets the message, but PUBACK is lost

Remove the previous rule. Add Packet loss with Upstream selected and PUBACK as the only message type. This does not affect the Broker PUBACK sent downstream to the Publisher, but discards acknowledgements returned upstream by the Subscriber and independent consumer.

Mqttable Proxy configures 100% upstream loss for PUBACK only.

The fault affects acknowledgement on the second hop. Completion of the first does not establish completion of the second.

Publish event_id=A-002-V311, phase=fault once. Confirm application receipt, remove the loss rule, then stop and restart this dedicated Proxy for controlled TCP disconnection and recovery. Keep the original Client IDs and Clean Session off. Check Session Present in CONNACK and the retransmitted PUBLISH's original Packet Identifier and DUP flag.

Mqttable shows QoS 1 retransmission after acknowledgement loss and session recovery.

The 3.1.1 independent consumer received A-002-V311 once before disconnection and once after recovery, with Packet Identifier 15. There was no second business publication; the duplicate came from recovery of an unacknowledged delivery.

The tested 3.1.1 sessions recover unacknowledged PUBLISH with CleanSession 0. MQTT 5.0 section 4.4 requires unacknowledged QoS-greater-than-zero PUBLISH and PUBREL packets to be resent with their original Packet Identifiers when reconnecting with Clean Start 0 and an existing session. It is not a universal rule to resend at fixed intervals on a healthy connection. MQTT 3.1.1 wording must not be quoted as though it were identical to 5.0. See MQTT 5.0 and MQTT 3.1.1 section 4.4.

QoS 2: lose PUBREC, then recover an unfinished delivery

Remove the previous fault and add Upstream loss for PUBREC only. Publish command_id=WO-002, phase=fault once. The Broker has sent PUBLISH but has not received the second-hop PUBREC, so that stage remains unacknowledged.

Mqttable Proxy configures 100% upstream loss for PUBREC only.

This is first-stage acknowledgement loss, not PUBCOMP loss. Different interrupted stages require different recovery packets.

Record the independent consumer's count during the fault. Remove the rule and recover the existing session. Inspect a retransmitted PUBLISH with the original Packet Identifier, followed by PUBREC/PUBREL/PUBCOMP. Establish application-delivery timing from consumer records rather than from the presence of PUBLISH alone.

Mqttable shows retransmission and completion after a lost QoS 2 first-stage acknowledgement.

The 3.1.1 independent consumer recorded zero WO-002 deliveries during the fault and one after recovery; retransmission used Packet Identifier 17. This is an observation of this consumer and session, not a statement that every client library delivers to its application at the same internal point.

These experiments leave the Broker running and let it retain unacknowledged downstream state. They do not establish that a restarted Mqttable client restores every local in-flight state, or that a Broker recovers after restart or power loss. A persistent session, disk-persisted message state and an application transaction are different conditions. This lab disables Mosquitto disk persistence while retaining sessions in memory across client reconnections. See the distinction in the Mosquitto configuration manual.

Two comparisons that are easy to misread

After the fault experiments, remove every Toxic and restore the Mqttable Subscriber to 5.0, Clean Start off and Session Expiry 3600 seconds. Verify successful subscriptions again. These two comparisons use two Mqttable MQTT 5 clients; the independent consumer remains a separate subscription session.

Publish at QoS 2, subscribe at QoS 1

Add an exact Subscriber subscription for factory/line1/qos-downgrade at QoS 1 and verify the granted level in SUBACK. Publish at QoS 2 with phase=publish2-subscribe1. Trace should show the Publisher's four-stage exchange and the Subscriber's PUBLISH/PUBACK.

Change that exact subscription to QoS 2, verify SUBACK grants 2, and publish at QoS 1 with phase=publish1-subscribe2. Downstream delivery is still QoS 1. The independent consumer has its own QoS 2 subscription, so its result cannot substitute for the Mqttable Subscriber's downstream observation.

Mqttable shows upstream and downstream packets when publish and granted subscription QoS differ.

The two hops for one Topic can use different QoS levels. This comparison has no overlapping subscriptions and does not establish every Broker routing policy.

Will QoS 2 merge two new publications of the same work order

With the path restored, deliberately publish this identical payload twice at QoS 2:

json
{
  "command_id": "WO-003",
  "operation": "load_recipe",
  "recipe": "R17",
  "target": "line1",
  "phase": "business-repeat"
}
Mqttable shows two new QoS 2 publications and handshakes carrying the same command_id.

The independent consumer received two WO-003 messages. These are two new business publications, not DUP retransmission within a single MQTT delivery.

The receiving application needs command_id to determine whether the work order was already accepted or completed. For database actions, the uniqueness constraint, processing state and business write need an appropriate transaction boundary. If a device or third-party API is called outside that transaction, the system must still handle an action that succeeded while recording its result failed. A simple in-memory set disappears on process restart and is not durable idempotency.

Cross-check with PCAP, not interface row counts alone

Capture bidirectional traffic on the dedicated local ports and import the file into Mqttable PCAP analysis. Select the relevant client session in Connections, Traffic and Packets, then correlate QoS, direction and Packet Identifier. The PCAP image uses a client-side-only excerpt filtered to the Mqttable Subscriber in Packets; the original two-sided capture is retained separately.

Mqttable PCAP analysis shows QoS handshakes and session evidence from the lab capture.

A complete capture helps check both hops and recovery. With incomplete capture, an unseen PUBACK establishes missing visible evidence, not that the Broker never sent it.

There are separate TCP connections on the two sides of the Proxy. A fault can let the Proxy receive a packet without forwarding it. Identify whether you are observing the client side or Broker side; do not add the two observations of one message and call them two application deliveries. For everyday packet correlation, see the Trace guide.

Put QoS inside the industrial reliability design

For real-time telemetry, define acceptable age and gaps. Include a sampling timestamp, sequence number and device identity; reject stale values and display current-value availability separately from historical completeness. When complete history matters, implement bounded capture-side storage, replay and progress checks rather than relying on one MQTT send.

Alarms need stable event identity. Re-reporting an existing event after a device reconnects should preserve event_id, while a genuinely new event needs a new identity. Consumers can update an existing event without creating another work order for every protocol duplicate. If rule changes or device restarts reset counters, the identifier design must account for those boundaries.

Distinguish an accepted work order from an executed one. A business result can include command_id, status, device time and an error reason. The sender needs a deadline and a way to query status. MQTT 5 Response Topic and Correlation Data help correlate requests and responses, but do not implement the work-order state machine or device action. After an uncertain send, query the existing order before deciding to issue it again.

Offline queues also need capacity and lifetime limits. Session existence, a full Broker queue, message expiry and state survival after power loss all affect the outcome. Enabling QoS 1 does not promise unlimited offline replay. Retain provides the latest retained value on a Topic to later subscribers; it is not every disconnected consumer's historical event queue.

Whether QoS 2 is worthwhile depends on the cost of protocol duplicates, endpoint implementations, resource budgets and recovery requirements. QoS 1 remains a reasonable candidate for alarms and work orders with reliable business idempotency. Evaluate QoS 2 where protocol-level duplicate suppression is useful, while keeping business IDs and results. This lab does not benchmark throughput or latency and does not claim a numeric QoS 2 slowdown.

Design work-order results that cannot be mistaken for execution

For example, after receiving WO-003, a consumer validates the target, recipe version and validity window, durably records acceptance, then returns accepted. The business action runs later and reports succeeded, or failed with a reason. This sequence is a business-design example, not an observed simulated-device run.

json
{ "command_id": "WO-003", "status": "accepted", "target": "line1" }

accepted must not make a dashboard display "recipe loaded." If the sender times out waiting for a final result, mark the outcome as unresolved and query using the same command_id. Issuing a new identifier immediately bypasses the original deduplication key and can create two independently valid commands.

On receiving WO-003 again, the consumer returns its durably recorded status. A completed order should not execute again; an executing order should not start another parallel execution; an expired order should not become acceptable merely because the network recovered. Business rules define whether a failed order can be retried or needs a new version, not the DUP bit.

A gateway process, Broker and business database keep different state across their respective restarts. Acceptance testing must separately establish whether the MQTT session exists, whether the order record exists, whether its action happened and whether its result remains queryable. This tutorial tests network-session recovery only, not those production acceptance conditions.

Frequently asked questions

Does QoS 2 guarantee one database write

Not by itself. It governs MQTT delivery, not an atomic transaction spanning a database and device action. Use stable business keys, uniqueness constraints and appropriate transactions, while handling new business publications and side effects outside the transaction.

Does PUBACK mean the device executed the command

No. The Publisher's PUBACK comes from the Broker and does not represent the final device's business outcome. Successful execution needs a correlated result from the device or consumer, plus handling for lost results and status queries.

Why can a QoS 1 business message repeat with DUP set to 0

Repeated business content does not always come from retransmission of the same PUBLISH. The application may publish the event again, or the Broker may establish delivery on another hop. Analyze Client ID, hop direction, Packet Identifier and the business event_id, not only the first-hop DUP flag.

Why does a QoS 2 subscription receive a QoS 0 message

A subscription's QoS is a delivery ceiling; it cannot upgrade a QoS 0 publication to 2. Inspect the actual publish QoS, granted SUBACK level and downstream PUBLISH, rather than only the subscription form.

Can Retain replace an offline queue

No. Retain stores the latest retained message for a Topic for later subscription delivery. A session queue holds pending messages for a particular consumer. Complete history, expiry and queue limits need their own design.

Why not use QoS 2 for every message

QoS 2 adds protocol exchanges and state, without solving every business reliability problem. Decide whether gaps are acceptable, how duplicates are handled, how long data remains useful and whether old messages have value after recovery. Apply this lab to an authorized test Broker, not by injecting faults directly into a production line.