MQTT Fundamentals

MQTT Receive Maximum: Flow Control for Slow Devices in MQTT 5

Receive Maximum is MQTT 5's built-in flow control. It caps how many unacknowledged QoS 1 and 2 messages can be in flight, so slow devices are not overwhelmed after a reconnect.

MQTT receive maximum illustration with a limited in-flight window of messages flowing from a broker to a small microcontroller
Run this on your own MQTT brokerGet a private, managed MQTT broker on the Free plan and start from ready-made code samples.
Try it free Code samples No card required

MQTT receive maximum is the MQTT 5 setting that tells the other side how many QoS 1 and QoS 2 messages you are willing to have "in flight", unacknowledged, at the same time. In this guide you will learn how this flow control window works in both directions, why it matters for microcontrollers and reconnect bursts, and how to set it in mosquitto, Python and Node.js.

What Problem Receive Maximum Solves

MQTT 3.1.1 had no standard flow control. A broker could push QoS 1 messages to a client as fast as the TCP connection allowed, and the client had to buffer every one of them until it sent PUBACK. That works on a server, but a microcontroller with a few kilobytes of free RAM can run out of memory, drop messages or crash. The problem is worst right after a reconnect, when a persistent session releases a backlog of queued messages all at once.

Brokers and libraries invented their own "max in-flight" settings to cope, but these were not negotiated, so neither side knew the other's limit. MQTT 5 standardised the idea as the Receive Maximum property.

How the MQTT Receive Maximum Window Works

Receive Maximum is a two-byte integer, 1 to 65,535, that each side can declare:

  • The client sends it in CONNECT: "Broker, do not send me more than N unacknowledged QoS 1/2 PUBLISH packets."
  • The broker sends it in CONNACK: "Client, do not send me more than N unacknowledged QoS 1/2 PUBLISH packets."

If the property is absent, the value defaults to 65,535, which effectively means "no limit". A value of 0 is not allowed.

What Counts as In Flight

Packet type Counts against the window? Released when
QoS 0 PUBLISH No Not applicable
QoS 1 PUBLISH Yes PUBACK received
QoS 2 PUBLISH Yes PUBCOMP received (or PUBREC with an error reason code)
Retained message sent on subscribe Yes, if delivered at QoS 1 or 2 Normal acknowledgement
Queued offline messages Yes, at QoS 1 or 2 Normal acknowledgement

The sender keeps a counter (the specification calls it the send quota). Each QoS 1 or 2 PUBLISH decrements it; each completing acknowledgement increments it. When it reaches zero, the sender must stop sending QoS 1 and 2 messages and wait. It may still send QoS 0 messages, and the specification lets it continue sending PINGREQ, so keep alive is unaffected.

What Happens If the Limit Is Exceeded

If a sender ignores the window and the receiver gets more unacknowledged QoS 1/2 messages than it advertised, the receiver should disconnect with reason code 0x93 (Receive Maximum exceeded). In practice you rarely see this, because mainstream libraries enforce the window automatically. When you do see it, it almost always points to a custom or very old client implementation.

Why It Matters for Slow Devices

Consider an ESP32 that reconnects after a 10 minute Wi-Fi outage. Its session holds 200 queued QoS 1 configuration updates. Without flow control, the broker can stream all 200 at once. With a receive maximum of 5, the broker sends five, waits for PUBACKs, then sends more as each acknowledgement arrives. Memory use is bounded, processing keeps up, and the device stays responsive.

The window also gives you natural back-pressure for backend consumers: if a worker sends PUBACK only after it has written a message to its database, a small receive maximum stops the broker from piling up more work than the worker can handle. This is a useful complement to MQTT shared subscriptions, where several workers share one stream and each worker's window limits its share of in-flight work.

Setting Receive Maximum in Clients

Test against the public MQTT test broker at broker.connectmq.com with a unique prefix such as connectmq/demo/r2m6/.... The public area is open to everyone, so use dummy payloads.

mosquitto_sub

mosquitto_sub -h broker.connectmq.com -p 1883 -V mqttv5 \
  -i recvmax-demo-r2m6 -q 1 -v -d \
  -D connect receive-maximum 5 \
  -t 'connectmq/demo/r2m6/#'

The -d output shows the CONNECT properties you sent and the CONNACK properties the broker returned, including its own receive maximum if it set one.

Python (paho-mqtt 2.x)

import time
import paho.mqtt.client as mqtt
from paho.mqtt.properties import Properties
from paho.mqtt.packettypes import PacketTypes

def on_connect(client, userdata, flags, reason_code, properties):
    server_rm = getattr(properties, "ReceiveMaximum", 65535)
    print("Connected:", reason_code, "| broker receive maximum:", server_rm)
    client.subscribe("connectmq/demo/r2m6/#", qos=1)

def on_message(client, userdata, msg):
    # Simulate a slow device; paho sends PUBACK after this callback returns
    time.sleep(0.5)
    print("Handled", msg.topic, msg.payload.decode())

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
                     client_id="recvmax-py-r2m6",
                     protocol=mqtt.MQTTv5)
client.on_connect = on_connect
client.on_message = on_message

# Limit what *we* send concurrently (outgoing window)
client.max_inflight_messages_set(10)

# Ask the broker to limit what *it* sends to us (incoming window)
props = Properties(PacketTypes.CONNECT)
props.ReceiveMaximum = 5

client.connect("broker.connectmq.com", 1883, keepalive=60, properties=props)
client.loop_forever()

Publish a burst from another terminal and watch messages arrive at a steady pace:

for i in $(seq 1 30); do
  mosquitto_pub -h broker.connectmq.com -p 1883 -i burst-r2m6 \
    -q 1 -t 'connectmq/demo/r2m6/burst' -m "msg $i"
done

Note that the time.sleep inside the callback also blocks paho's network loop. That is fine for this demonstration, but in production move slow work to a separate thread and keep the loop free, or keep alive pings will be delayed.

Node.js (MQTT.js v5)

const mqtt = require("mqtt");

const client = mqtt.connect("mqtts://broker.connectmq.com:8883", {
  clientId: "recvmax-node-r2m6",
  protocolVersion: 5,
  properties: { receiveMaximum: 5 },
});

client.on("connect", (connack) => {
  console.log("broker properties:", connack.properties);
  client.subscribe("connectmq/demo/r2m6/#", { qos: 1 });
});

client.on("message", (topic, payload) => {
  console.log(topic, payload.toString());
});

Choosing a Receive Maximum Value

Client Suggested value Why
Microcontroller (ESP32, ESP8266) 1 to 10 Bounded RAM, sequential processing
Raspberry Pi gateway 10 to 50 More memory, still modest CPU
Backend worker writing to a database Roughly the number of parallel writes it can sustain Back-pressure matches real throughput
Dashboard showing live values Default Mostly QoS 0 traffic, which is not limited

A value of 1 gives strict one-at-a-time processing, which can also simplify ordering logic, at the cost of throughput on high-latency links. Each in-flight slot requires one round trip, so on a cellular link with high latency a tiny window may cap throughput noticeably. Measure with your real traffic.

Receive Maximum and Message Ordering

A window larger than 1 does not break MQTT ordering guarantees: the broker still sends messages on a topic in the order it received them, and the client still receives them in that order. What changes is how many can be awaiting acknowledgement at once. If your application acknowledges messages out of order, for example by handing them to a thread pool, processing order is your responsibility, not the protocol's. The behaviour is the same whether you connect to a ConnectMQ broker, a local Mosquitto or any other MQTT 5 broker, because the window is negotiated by the protocol itself rather than configured on one side.

Troubleshooting Checklist

  1. Messages arrive slowly after reconnect. That is the window working. Increase it if the device can cope, or reduce the backlog with message expiry.
  2. Device still runs out of memory. Check whether most traffic is QoS 0; Receive Maximum does not limit it. Also check maximum packet size.
  3. Publisher stalls. Your client's send quota hit zero because the broker's receive maximum is small or PUBACKs are slow. Log CONNACK properties.
  4. Disconnect with 0x93. A client sent more in-flight messages than allowed; update or fix the client library.
  5. Setting has no effect. You are connecting with MQTT 3.1.1. Receive Maximum only exists in MQTT 5.

For the bigger picture of how queued messages build up in the first place, see how MQTT queues messages for offline clients. New to the protocol? Start with the MQTT tutorial.

Tune Flow Control on ConnectMQ

ConnectMQ runs MQTT 3.1.1 and 5.0 on TCP, TLS, WebSocket and secure WebSocket, so MQTT 5 properties like Receive Maximum work from any client. Per-plan quotas and flood protection keep a shared MQTT broker in the cloud stable, while Receive Maximum protects your individual devices. Create a free Serverless broker, no credit card needed, connect with YOUR_USERNAME, YOUR_PASSWORD and YOUR_CLIENT_ID, and use the portal's in-browser client to fire test bursts at your devices. For production fleets, a managed MQTT broker removes the broker operations work entirely.

Frequently Asked Questions

What is the default MQTT receive maximum?

If the property is not sent, the receive maximum is 65,535, which effectively means no practical limit. Clients must set a smaller value explicitly to get flow control.

Does receive maximum limit QoS 0 messages?

No. It only counts QoS 1 and QoS 2 PUBLISH packets that have not yet been acknowledged. QoS 0 messages are never in flight in that sense.

Is receive maximum available in MQTT 3.1.1?

No. It is an MQTT 5 property. MQTT 3.1.1 clients rely on library-specific in-flight settings that are not communicated to the broker.

What does reason code 0x93 mean?

It means Receive Maximum exceeded: the sender had more unacknowledged QoS 1 or 2 messages in flight than the receiver allowed, so the receiver closed the connection.

Related articles

Why Your MQTT Client Keeps Disconnecting (and How to Fix It)

Reconnect loops are one of the most common MQTT problems. Learn the five usual causes, from duplicate client IDs to keep alive timeouts and oversized packets, and follow a diagnosis flow that pinpoints yours.

8 min read

Who Invented MQTT? The History from Oil Pipelines to IoT

MQTT was invented in 1999 by Andy Stanford-Clark of IBM and Arlen Nipper of Arcom to monitor oil pipelines over satellite. Follow its path to open source, OASIS, ISO and today's managed cloud brokers.

7 min read

MQTT Maximum Message Size: Limits and Large Payload Strategies

MQTT can technically carry a 256 MB packet, but brokers, client libraries and devices impose far lower limits. Learn where the real limits are, what happens when you exceed them, and how to send large payloads.

7 min read

All articles →

Your private MQTT broker is a few clicks away

Create an account, get your broker credentials and connect your first device — no servers to install, patch or scale.