Skip to content

Guides

Limits first, then everything that keeps an MQTT device sending reliably.

Usage & rate limits

Per device, keyed by the account's plan, enforced on accepted messages only - a rejection does not reset your timer. On MQTT these are paced on your signed payload timestamp rather than on arrival time, so a backlog delivered after a reconnect is accepted as long as its payload timestamps are properly spaced.

Plan Min. interval between measurements Batch pacing Max distinct signal keys Raw retention
free60 smin. 60 s between batches (60/hour, paced)207 days
premium120 smin. 20 s between batches (180/hour, paced)2015 days
premium210 smin. 10 s between batches (360/hour, paced)2030 days

These are the current defaults for each plan; your account can carry its own overrides, so check your plan page if a send is being dropped and the table says it should not be.

Message limits

50signal keys in one values map
100messages in one batch ({"messages": [...]})
65536bytes for the whole published frame, LM1 signature included

The 64 KB cap is checked before the signature is even verified, so an oversized frame is dropped without any diagnosis at all. Split by BYTES as well as by message count: 100 small readings fit easily, 100 wide multi-signal readings may not.

Keeping delivery reliable

sequence: the rule that decides whether your data arrives

sequence is a per-device integer that must STRICTLY increase. The backend keeps a durable high-water mark and silently drops any message whose sequence is less than or equal to the last one it accepted - even when the signature and the timestamp are both perfectly valid.

Seed it from the wall clock (int(time.time())) and increment from there. Three traps follow from the durable high-water mark:

  • A counter that starts at 0 or 1 works exactly once, then every later message is dropped - including after every reboot.
  • A device that previously used another transport already has a high mark on file, so its very first low-numbered MQTT frame is rejected too.
  • Inside a batch, sequences must increase as well: the backend walks the list in order and rejects the WHOLE batch at the first non-increasing value.

event_id is separate and optional: an idempotency key. Resending a message with a recently-seen event_id for the same device is de-duplicated rather than double-counted. It protects against a retry-after-timeout race, not against resending hours later.

Reconnecting and backoff
  1. There is nothing to re-authenticate. MQTT authenticates inside CONNECT itself, so a successful reconnect is already a fully authenticated session.
  2. Use backoff - start at 1-2 s, double up to a cap of ~30 s. Never a tight reconnect loop.
  3. Re-subscribe to your cmd topic on every connect. Sessions are clean, so subscriptions do not survive a reconnect. Subscribing from your on-connect callback handles this for free.
  4. Most client libraries already do the reconnect for you once their network loop is running (paho: loop_start() plus reconnect_delay_set()). Prefer that over hand-rolling it.

A revoked or regenerated credential is kicked from the broker immediately, and reconnecting with the old one fails at CONNECT - that is a credential problem, not a network one; no amount of backoff fixes it.

Keepalive

The keepalive is the maximum time your client may stay silent before the broker considers the connection dead. Your client proposes it in CONNECT; if it sends nothing else in that window it must send a PINGREQ.

30 seconds is a good default and what every IoT Manager sample uses. Shorter notices a broken TCP connection sooner at the cost of more radio wake-ups; much longer means a device that fell off the network can look connected for minutes.

Keepalive is not presence. Being marked offline in the dashboard is a separate, telemetry-based timeout (default 120 s per device, minimum 30 s) - there is no status topic and no Last Will, so a device that holds a connection open but stops publishing still goes offline.

Offline buffering

While disconnected, keep capturing into a BOUNDED local buffer - a ring buffer on a microcontroller. An unbounded buffer turns a network outage into an out-of-memory crash; dropping the oldest reading is the right trade.

On reconnect, publish the backlog as batches, oldest first: at most 100 messages each, under 64 KB each. Keep every message's own timestamp as the moment it was OBSERVED, and sign the frame fresh at flush time so its LM1 ts is current.

The backlog is accepted even though it all lands at once: rate limits are paced on each message's own signed timestamp, not on arrival time. A QoS 1 backlog drained after a broker or backend restart is ingested rather than discarded.

Error handling: what silence means

There is no error frame on this transport. A rejected message - bad signature, stale or future timestamp, replayed sequence, oversized payload, malformed JSON, a rate limit - is simply dropped. The QoS 1 PUBACK you received confirms broker receipt, never backend acceptance.

So build your diagnosis in this order when nothing arrives:

  1. Did CONNECT succeed? A wrong username/clientid/password fails there, visibly.
  2. Is the device clock right? More than 3600 s old or 300 s ahead is dropped.
  3. Is sequence increasing past everything this device ever sent?
  4. Is the frame under 64 KB and inside the batch limits?
  5. Are you sending faster than your plan allows?

To positively confirm a device is being ingested, use a cmd/ack round trip or the dashboard's live telemetry view. Do not treat PUBACK as proof.

Working code for all of the above: <a href="/docs/samples">Samples</a>. The exact wire format: <a href="/docs/mqtt">Device MQTT Protocol</a>.