Delivery and store-and-forward

What is a message TTL?

A message TTL (time to live) is a limit after which a message stops being forwarded or stored, counted either in hops or in time. A hop limit stops a message circling a network or spreading too far, and a time limit stops a message that can no longer be delivered, or is no longer useful, from waiting in queues forever.

Learning objectives

After reading this article you will be able to:

  • Distinguish hop limits from expiry times as two kinds of message TTL
  • Explain how a hop limit stops loops and controls how far a message spreads
  • Describe how MQTT and the Bundle Protocol handle expiry without trusting device clocks

Two kinds of limit

“Time to live” is used for two different limits, and it helps to keep them apart.

  • A hop limit counts how many times a message can be forwarded. Each relay lowers the count by one, and when it reaches zero the message is not passed on.
  • An expiry time says how long a message stays worth delivering. Once that time has passed, queues and relays delete it instead of holding it.

The internet’s own history shows the overlap. RFC 791 defined IPv4’s Time to Live field in seconds, but because every module that handles a datagram must lower it by at least one, it said the TTL “must be thought of only as an upper bound on the time a datagram may exist.” In practice it worked as a hop count. IPv6 made that official: RFC 8200 explains that IPv6 nodes are not required to enforce a maximum packet lifetime, which is why the field was renamed Hop Limit. Both are 8-bit fields.

Hop limits

A hop limit does two jobs in a network where devices relay for each other.

It stops loops. If routing goes wrong, or a message is flooded to every neighbour, a copy could otherwise circle forever. RFC 8200 says a node discards a packet whose Hop Limit was zero when received or reaches zero when decremented. Deduplication does the rest, by dropping copies a device has already seen.

It also sets how far a message can spread. In a flooded mesh, each extra hop can reach more devices, and every relay that forwards the message spends airtime and battery on it. A higher limit reaches further but costs the whole network more, so it is a trade-off rather than a number to raise until things work.

The delay-tolerant Bundle Protocol (RFC 9171) defines a Hop Count extension block for the same purpose. It carries a hop limit, which must be between 1 and 255, and a running count. The RFC describes it mainly as a safety mechanism, so a bundle caught in a forwarding error is eventually deleted.

Expiry times

A time limit answers a different question: is this message still worth delivering? A stale location update, a request for a ride, or a one-time code may be useless long before they are lost.

Message brokers make this explicit.

  • MQTT 5.0 lets a publisher set a Message Expiry Interval, the message’s lifetime in seconds. If it passes before the server has started delivering the message to a subscriber, the server must delete that copy. When the server does pass the message on, it sends the interval minus the time the message spent waiting, so the remaining lifetime travels with it.
  • RabbitMQ supports a TTL per queue and per message, set in milliseconds. Expired messages are never delivered to consumers, and a queue set up with a dead-letter exchange moves them there instead of dropping them.
  • The Bundle Protocol gives every bundle a lifetime in milliseconds after its creation time. When a bundle’s age exceeds its lifetime, nodes no longer need to keep or forward it, and it should be deleted.

The clock problem

An expiry time depends on knowing what time it is, and offline devices often do not. A phone that has been without a network for days may have a clock that has drifted, and a small sensor may have no real-time clock at all.

Two techniques avoid trusting the receiver’s clock. MQTT’s approach is to carry the remaining lifetime rather than an absolute deadline, reducing it by however long the server held the message. The Bundle Protocol allows a bundle created on a node without an accurate clock to set its creation time to zero and carry a Bundle Age block, so its age is counted as it travels instead of worked out from timestamps.

Choosing values

There is no universal right setting, but the questions are the same in most systems.

  • How far does the message need to go? Set the hop limit from the size of the network you expect to cross, not the largest possible.
  • How long is it useful? Set expiry from the content. A chat message may be worth delivering days later; a live position is not.
  • How long can devices be out of contact? In store-and-forward delivery, a message that expires before the recipient reappears is never delivered. The expiry has to cover the gaps you expect.
  • What happens at expiry? Decide whether the sender is told, the message is logged, or it is moved somewhere for review. Silent loss is hard to notice and hard to debug.

As one example, the Offline Protocol mesh SDK documents defaults for v0.27.0 of an initial TTL of 8 hops for messages crossing the mesh and an outbox that holds up to 500 entries for 7 days.

Frequently asked questions

Is TTL measured in hops or in seconds?

It depends on the system. In IP networks it works as a hop count that each router lowers by one, and meshes that borrow the idea do the same at each relay. In message brokers such as MQTT and RabbitMQ, and in the Bundle Protocol's lifetime field, it is a length of time.

What happens to a message when its TTL runs out?

Relays and queues stop forwarding or storing it. Some systems simply delete it; others report the failure to the sender or move the message to a dead-letter queue so someone can see what was not delivered.

Sources

Build it with Offline Protocol

The limits and capacity page lists the mesh SDK's default initial hop limit together with its outbox, deduplication and retry bounds, which together decide how far and how long a message can travel before the app is told it failed.

Read limits and capacity