MQTT Receive Maximum: Connect Protocol Flow Control to a Bounded Device Queue
Receive Maximum limits outstanding MQTT QoS 1 and QoS 2 protocol work on one connection. It does not directly cap an application’s pending records, and it does not throttle QoS 0. To use it as part of a memory bound, identify exactly when the client library sends acknowledgements and which buffers remain owned after that point.
This guide pins the OASIS MQTT 5.0 Standard, 7 March 2019, especially sections 3.1.2.11.3, 3.2.2.3.3 and 4.9. An original credit-accounting model was executed locally. No broker, client library, network throughput or MCU memory measurement was tested.
Record the receiving direction
A client’s CONNECT Receive Maximum tells the server how many QoS 1/2 publications the client is willing to process concurrently. The CONNACK counterpart limits publications sent to the server. The valid nonzero two-byte range and the default of 65,535 do not imply that a small device has provisioned that many application buffers.
Keep both directions in the connection record. A device may accept only a small inbound command window while its server accepts a larger outbound telemetry window. One application setting named “inflight limit” might control only the library’s sender, not the value advertised to its peer. Inspect the actual CONNECT and CONNACK properties rather than trusting a configuration label.

Follow the complete credit lifecycle
Section 4.9 defines a nonzero initial send quota no greater than the peer’s Receive Maximum. Each QoS-greater-than-zero PUBLISH consumes credit. At zero credit, further such PUBLISH packets must wait, while other control packets must still be processed. The sender may continue QoS 0, or choose to pause it as an additional policy.
| Protocol event | Credit effect |
|---|---|
| Send QoS 1 or QoS 2 PUBLISH | Consume one credit |
| Receive PUBACK | Restore one, capped at initial quota |
| Receive successful PUBREC | No credit restored yet |
| Receive PUBCOMP | Restore one, capped at initial quota |
| Receive PUBREC with reason at least 0x80 | Restore one for the rejected exchange |
The QoS 2 distinction matters: treating every PUBREC as available send credit can exceed the intended window. A negative PUBREC terminates that publication path differently from a successful PUBREC followed by PUBREL/PUBCOMP. Keep protocol state and acknowledgement reason together.
Map acknowledgements to real buffer ownership
Draw the receive path through packet parsing, library-owned storage, callback delivery, application queue admission, worker processing and final release. At each transition, mark whether a payload is copied, borrowed or retained by reference. Then mark the actual acknowledgement event on the same path.
If the library acknowledges before the application removes a record from its queue, more protocol credit becomes available while that queue still contains older work. Receive Maximum alone cannot bound that backlog. Include records already being processed, library buffers and any separate QoS state in the memory ledger; an empty queue slot is not necessarily a freed payload.
Where supported by the selected implementation, acknowledgement timing can be coordinated with a defined admission boundary. Verify that feature and its failure semantics; the standard does not promise a particular manual-ack callback API. If acknowledgements are automatic, enforce application admission and memory limits through mechanisms the library actually supports rather than pretending the protocol window is the queue capacity.
A hypothetical small-command receiver
Suppose a hypothetical device advertises Receive Maximum one and has space for two pending command records. Its library acknowledges each received command immediately after invoking the callback. A worker is temporarily delayed. Three successive commands can complete their protocol acknowledgements while three application records are offered to a two-record queue.
The correct response depends on the application contract: reject before ownership transfer, stop admitting through a supported mechanism, or use a separately qualified bounded storage path. Silently dropping the third record after reporting application acceptance is not repaired by the Receive Maximum setting. Also test a QoS 0 publisher, because this property supplies no corresponding receiver-side QoS 0 throttle.
Do not block the control path to create backpressure
A sender with no credit still needs to receive and process acknowledgements that restore it. A receiver may also need to send acknowledgements or handle other control packets while its application is congested. Blocking the network-processing task behind an application dependency can therefore prevent the very protocol progress needed to recover.
Review callback restrictions and keep application work bounded. If a worker waits for an outbound publish result while the network callback waits for that worker to free a queue slot, the integration has a dependency cycle. Capture those waits separately from an ordinary zero-credit condition.
Executed model and proposed implementation tests
The original model started with two credits, exhausted them with QoS 1 and QoS 2 publications, and rejected another QoS-positive send. It allowed a QoS 0 send without changing credit. A successful PUBREC did not replenish quota; PUBACK, PUBCOMP and failed PUBREC paths did. A second model with Receive Maximum one accumulated three application-pending records through early acknowledgements.
These are accounting assertions, not measurements of a client library. For the real stack, hold acknowledgements at controlled test points and inspect transmitted packets, quota state, callback order and total live-buffer count. Repeat with a slow worker, full queue, QoS 0 traffic and rejected QoS 2 publication.
On reconnect, reinitialize the protocol quota from the new connection’s values; it is not preserved session state. Reconcile any retained application work separately. Acceptance requires no over-quota QoS-positive sends, continued control-packet progress and a bounded storage policy that remains valid even after protocol credit is released.