Reliable delivery over an unreliable mesh

Reliable delivery is the guarantee that a message arrives despite peers that come and go. On a mesh with no server, the SDK builds acknowledgment, retry, deduplication, and time-to-live into the transport itself, so a message survives a peer that comes and goes.

Why reliability has to live in the transport

On the internet a server is the fixed point that guarantees delivery: it holds the message, retries, deduplicates, and confirms receipt. A peer-to-peer mesh has no such fixed point. Peers appear and disappear, links flap, and a message may travel several hops through devices that do not know each other. Nothing outside the transport is watching whether a message arrived.

So the SDK makes the transport itself responsible for delivery. When you call sendMessage, the message is tracked from the moment it leaves until an acknowledgment comes back, retried on its own schedule if it does not, deduplicated at every relay so it is processed once, and dropped by a hop counter if it fails to reach anyone. These are not features you bolt on; they are how the mesh moves a message at all.

The result is a delivery guarantee that holds through the gaps: a recipient can be out of range when you press send and still receive the message when they return, and you find out either way through the delivery and failure events.

What the reliability layer guarantees

Four mechanisms work together so a message is confirmed, retried, seen once, and never left to circulate.

Confirmed, not fire and forget

A message is not done when it leaves the radio. It is done when an ACK comes back and message_delivered fires. Until then the retry machinery owns it.

Backoff that respects the link

Retries start at 1 second and double to a 30-second cap, so a flapping peer is retried quickly at first and then patiently, without flooding a weak link.

An outbox with a memory

A message lives in the outbox for up to an hour. A peer that walks back into range within that window still gets the message it missed.

Safe to relay

Deduplication and an 8-hop TTL make multi-hop forwarding safe: a message is processed once and cannot loop the mesh indefinitely.

The reliability layer, mechanism by mechanism

Acknowledgments

Every message waits for an ACK from the recipient. The message_delivered event fires when the receipt arrives, carrying the measured latency and hop count. The ACK timeout defaults to around 10 seconds, and up to 1000 pending acks are tracked at once.

Retry queue

Failed messages are retried with exponential backoff: 1 second to start, doubling each attempt, capped at 30 seconds, for up to 10 retries. The outbox holds a message for up to an hour, so it can still deliver when a peer reappears.

Deduplication

The same relayed message is never processed twice. Roughly 1000 recent message ids are tracked with a 1-hour retention, using a space-efficient Bloom filter with about a 1% false-positive rate or an exact hash map.

Message TTL

Every message carries an 8-hop time-to-live. The hop count is decremented as the message travels, and the message is dropped when the TTL reaches 0, which prevents infinite circulation.

Store and forward

Messages can be queued until a session or peer is ready with storePending, so nothing is lost when the destination is temporarily unreachable. The message waits in framing until a path exists, then goes out.

Acknowledgment, in code

A delivery event carries everything you need to close the loop on a sent message: its id, the measured latency, and the hop count it traveled. The reliability defaults are set through ReliabilityConfig and can be tuned or updated at runtime.

TypeScript
// every message waits for an ACK; message_delivered fires on receipt
protocol.on('message_delivered', (event) =>
  console.log(`delivered ${event.message_id} in ${event.latency_ms} ms, ${event.hop_count} hops`));

// if the retry budget is exhausted, message_failed carries the reason
protocol.on('message_failed', (event) =>
  console.log(`failed ${event.message_id}: ${event.reason} (${event.retry_count} retries)`));
TypeScript · ReliabilityConfig
const protocol = new OfflineProtocol({
  appId: 'my-app',
  userId: 'alice',
  reliability: {
    ack: {
      defaultTimeoutMs: 10000,   // wait ~10s for an ACK
      maxPendingAcks: 1000,     // track up to 1000 in flight
    },
    retry: {
      maxRetries: 10,           // up to 10 attempts
      initialDelayMs: 1000,     // start at 1s
      maxDelayMs: 30000,        // cap backoff at 30s
      backoffMultiplier: 2.0,    // double each retry
      outboxMaxLifetimeMs: 3600000, // hold for up to an hour
    },
    dedup: {
      maxTrackedMessages: 1000,  // recent ids tracked
      retentionTimeSecs: 3600,   // 1-hour retention
    },
  },
});

The life of a message that has to wait

  1. Send. sendMessage returns a message id and ACK tracking begins. If the recipient is out of range, storePending keeps the message queued until a session or peer is ready.
  2. Wait for the receipt. The layer waits about 10 seconds for an ACK. On receipt, message_delivered fires with latency and hop count and the message leaves the outbox.
  3. Retry with backoff. No ACK means a retry: 1 second, then 2, then 4, doubling to a 30-second cap, up to 10 times. The outbox holds the message for up to an hour.
  4. Relay once. Every hop deduplicates by message id, so a message that has already been seen is skipped, and its 8-hop TTL is decremented and dropped at 0.
  5. Resolve either way. The peer reappears within the hour and the message delivers, or the retry budget runs out and message_failed reports the reason and retry count.

Where it applies

Humanitarian and events
Humanitarian coordination and event operations stay workable when devices drift in and out of range.
Autonomous fleets
Fleets exchange messages across a moving topology without a fixed point.
What it builds on
Runs on the DORS mesh and pairs with telemetry, which records every delivery, retry, and drop.

Reliable delivery FAQ

How does a message get delivered when the recipient is offline?

The message is held in the outbox and retried with exponential backoff for up to an hour. If the peer reappears in that window, the message is delivered and a message_delivered event fires. Messages can also be queued until a session or peer is ready with storePending, so nothing is lost when the destination is temporarily unreachable.

What confirms that a message actually arrived?

Every message waits for an acknowledgment from the recipient. When the receipt arrives, the message_delivered event fires with the measured latency and hop count. The ACK timeout is configurable and defaults to around 10 seconds.

Why do relayed messages not get processed twice?

Each device deduplicates by message id. Roughly 1000 recent ids are tracked with a 1-hour retention, using either a space-efficient Bloom filter with about a 1% false-positive rate or an exact hash map. A message that has been seen is skipped rather than reprocessed or re-relayed.

What stops a message from circulating the mesh forever?

Every message carries an 8-hop time-to-live. The hop count is decremented as the message is relayed, and the message is dropped when the TTL reaches 0, which prevents infinite circulation across a multi-hop mesh.

How many retries does a failed message get?

Up to 10 retries. The delay starts at 1 second and doubles on each attempt, capped at 30 seconds, so early retries are quick and later ones back off. The message stays in the outbox for up to an hour across those attempts.

Are the reliability settings tunable?

Yes. The ACK timeout and pending-ack ceiling, the retry count, delays, backoff multiplier and outbox lifetime, and the deduplication capacity and retention are all configured through ReliabilityConfig and can be updated at runtime.

What happens when a message finally fails?

When the retry budget is exhausted, a message_failed event fires with the reason and the retry count, so an undelivered message is as diagnosable as a delivered one and your application can decide how to surface it.

Deliver messages through the gaps. Confirmed, retried, deduplicated.

Book a pilot Read the docs