CloudSignal Docs
GuidesMQTT Fundamentals

QoS levels

Understanding MQTT Quality of Service levels and when to use each.

QoS determines the delivery guarantee for MQTT messages. Use this guide to pick the right level for each topic, balancing reliability against speed and overhead.

Overview

MQTT defines three QoS levels:

LevelNameGuarantee
0At most onceFire and forget, may be lost
1At least onceGuaranteed delivery, may be duplicated
2Exactly onceGuaranteed single delivery

QoS 0: at most once

The simplest level. The message is sent once with no acknowledgment.

QoS 0 flow
Publisher
PUBLISH
Broker
No ack, no retry, no storage
deliver
Subscriber

Characteristics

PropertyValue
SpeedFastest, lowest overhead
RetryNone
LossPossible if network issues occur
DuplicatesNone

When to use QoS 0

ScenarioWhy it fits
Frequent state samplesMissing one is acceptable
High-frequency dataNext reading replaces the last
Non-critical status updatesLoss is recoverable
Severely limited bandwidthSmallest packet count
// Heartbeat every second - missing one is fine
client.publish('agents/agent-001/heartbeat', 'alive', { qos: 0 });

QoS 1: at least once

The message is retried until acknowledged. Most common choice.

Publisher -> Broker: PUBLISH
Publisher <- Broker: PUBACK

Broker -> Subscriber: PUBLISH
Broker <- Subscriber: PUBACK

Characteristics

PropertyValue
StorageStored and retried until acknowledged
DeliveryGuaranteed
DuplicatesPossible if acknowledgment is lost
OverheadModerate

When to use QoS 1

ScenarioWhy it fits
Important data that must arriveRetry until acknowledged
Commands safely repeatableDuplicate is fine
Events where duplicates are acceptableIdempotent handlers cover it
Most agent state and chat messagesReliability without QoS 2 cost
// Agent status update - must arrive, duplicate is OK
client.publish('agents/agent-001/state', 'online', { qos: 1 });

QoS 1 is the recommended default for most applications. It provides reliability without the complexity of QoS 2.

QoS 2: exactly once

The highest guarantee. A four-step handshake ensures single delivery.

Publisher -> Broker: PUBLISH
Publisher <- Broker: PUBREC (received)
Publisher -> Broker: PUBREL (release)
Publisher <- Broker: PUBCOMP (complete)

Same 4-step process: Broker -> Subscriber

Characteristics

PropertyValue
DeliveryGuaranteed exactly once
DuplicatesNone
LossNone
OverheadHighest (4 packets vs 1-2)
SpeedSlowest

When to use QoS 2

ScenarioWhy it fits
Financial transactionsDuplicates are unacceptable
Billing eventsSingle accounting entry
Critical commandsMust not duplicate
Any non-idempotent actionReplay would corrupt state
// Payment notification - must happen exactly once
client.publish('billing/charges', chargeData, { qos: 2 });

QoS 2 has significant overhead. Only use it when duplicates would cause real problems.

Comparison

AspectQoS 0QoS 1QoS 2
Packets per message124
Storage requiredNoneUntil PUBACKUntil PUBCOMP
RetriesNoYesYes
Duplicates possibleNoYesNo
Message loss possibleYesNoNo
Relative speedFastestMediumSlowest

QoS downgrade

QoS is negotiated between publisher and subscriber. The broker delivers at the minimum of:

  • Publisher's QoS
  • Subscriber's QoS
Publisher sends QoS 2
Subscriber subscribed with QoS 1

-> Subscriber receives at QoS 1

This means:

Publish QoS 2 + Subscribe QoS 0 = Delivered QoS 0
Publish QoS 1 + Subscribe QoS 2 = Delivered QoS 1
Publish QoS 0 + Subscribe QoS 2 = Delivered QoS 0

Code examples

JavaScript (mqtt.js)

const mqtt = require('mqtt');

const client = mqtt.connect('mqtts://mqtt.cloudsignal.app:8883', {
  username: 'user@org-id',
  password: 'password'
});

// QoS 0 - frequent, non-critical
client.publish('agents/agent-001/heartbeat', 'alive', { qos: 0 });

// QoS 1 - important, duplicates OK
client.publish('agents/agent-001/state', 'online', { qos: 1 });

// QoS 2 - critical, no duplicates
client.publish('billing/event', JSON.stringify(event), { qos: 2 });

// Subscribe with QoS
client.subscribe('commands/#', { qos: 1 });

Python (paho-mqtt)

import paho.mqtt.client as mqtt

client = mqtt.Client()
client.username_pw_set('user@org-id', 'password')
client.tls_set()
client.connect('mqtt.cloudsignal.app', 8883)

# QoS 0
client.publish('agents/agent-001/heartbeat', 'alive', qos=0)

# QoS 1
client.publish('agents/agent-001/state', 'online', qos=1)

# QoS 2
client.publish('billing/event', event_json, qos=2)

# Subscribe with QoS
client.subscribe('commands/#', qos=1)

Handling duplicates (QoS 1)

When using QoS 1, design your system to handle duplicates.

Idempotent operations

Make operations safe to repeat:

// Good: setting state is idempotent
client.publish('lights/kitchen/state', 'on', { qos: 1 });
// Receiving "on" twice still results in light being on

// Risky: increment operation
client.publish('counter/increment', '1', { qos: 1 });
// Receiving twice would increment twice

Deduplication with message IDs

Include a unique ID and deduplicate on receive:

// Publisher
const messageId = uuid();
client.publish('events/user-action', JSON.stringify({
  id: messageId,
  action: 'purchase',
  amount: 100
}), { qos: 1 });

// Subscriber
const processedIds = new Set();

client.on('message', (topic, payload) => {
  const message = JSON.parse(payload);

  if (processedIds.has(message.id)) {
    return; // Already processed, skip
  }

  processedIds.add(message.id);
  processEvent(message);
});

CloudSignal recommendations

Use caseRecommended QoS
Agent state (frequent)0 or 1
Agent status updates1
Commands to agents1
User notifications1
Chat messages1
Financial/billing events2
Critical alerts1 or 2

Next steps

On this page