# How BitChat's Store and Forward Mechanism Ensures Message Delivery

> Discover how BitChat's store and forward mechanism ensures message delivery using a robust stack including local storage, direct sessions, Bluetooth mesh, and Nostr relays for reliable communication.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: internals
- Published: 2026-08-22

---

**TLDR:** BitChat guarantees message delivery through a layered store-and-forward stack that retains messages in a local outbox until acknowledged, then routes them via direct Noise-encrypted sessions, opportunistic Bluetooth mesh couriers, or Nostr relay mail-drops.

BitChat is an open-source, privacy-focused messenger from the **permissionlesstech/bitchat** repository that uses a sophisticated store and forward mechanism to ensure messages reach their recipients even when devices are offline or out of radio range. Unlike centralized messengers that rely on always-on servers, BitChat's architecture combines Bluetooth mesh networking with internet relays to create a resilient delivery fabric. The core of this system lives in [`MessageRouter.swift`](https://github.com/permissionlesstech/bitchat/blob/main/MessageRouter.swift), and understanding how it works reveals a carefully engineered approach to asynchronous, encrypted communication.

## The Core Store and Forward Stack

At the heart of BitChat's delivery guarantee is the **MessageRouter** class, which implements a layered store-and-forward (SF) stack on top of its routing logic. Every outbound private message is held in a local **outbox** until the router receives an explicit delivery acknowledgment. This design makes the outbox the single source of truth for unsent messages, as documented in [[`CONVERSATION-STORE-DESIGN.md`](https://github.com/permissionlesstech/bitchat/blob/main/CONVERSATION-STORE-DESIGN.md)](https://github.com/permissionlesstech/bitchat/blob/main/docs/CONVERSATION-STORE-DESIGN.md).

The router persists this outbox to disk via the **MessageOutboxStore** class, so messages survive app restarts and network changes. If the app crashes mid-delivery, the stored messages are restored and retry logic resumes automatically. This persistent queue is the foundation that makes eventual delivery possible in a mesh-connected environment.

## How Message Delivery Priority Works

When a message is sent, the router evaluates the current network state and chooses the best available path. The `sendPrivate` method initiates this process, and the router follows a priority cascade:

1. **Direct secure delivery** — If a Noise session is active and the recipient is reachable (`connectedTransport` plus `canDeliverSecurely`), the router sends the private message immediately using end-to-end encryption.
2. **Courier delivery** — If no direct path exists, the message is sealed into a `CourierEnvelope` and passed to nearby peers that act as physical couriers through the Bluetooth mesh.
3. **Bridge deposit** — If Nostr relays are reachable, the router optionally deposits a copy via `bridgeCourierDeposit`, creating a mail-drop the recipient can retrieve later.

Each strategy ensures a message is never dropped simply because the recipient was temporarily unreachable.

## The CourierEnvelope Sealed Transport System

The **courier enqueuing** mechanism is what makes BitChat unique among mesh messengers. Instead of routing messages hop-by-hop with full knowledge, the `CourierEnvelope` struct wraps the payload in an opaque, Noise-X encrypted blob that acts like a physical letter in a sealed envelope.

### Recipient Tag Authentication

Each envelope contains a rotating **recipient tag** derived from the recipient's static Noise key. This tag lets the intended recipient and the courier verify the target only to see an opaque package. Couriers can forward the envelope without ever knowing the contents or the final destination, which preserves metadata during transit.

The envelope also carries a mutable counter (`maxCopies`) that limits how many couriers can hold a copy of the same message, preventing unnecessary network flooding.

## Outbox Management and Delivery Limits

The BitChat router enforces several hard limits in the store to ensure reliable delivery without memory exhaustion. These are configured in [`MessageRouter.swift`](https://github.com/permissionlesstech/bitchat/blob/main/MessageRouter.swift) as constants:

| Limit | Default Value | Purpose |
|-------|--------------|---------|
| `messageTTLSeconds` | 24 h (86,400 s) | Prevents indefinite storage of undelivered messages |
| `maxMessagesPerPeer` | 100 | Bounds memory per recipient |
| `maxSendAttempts` | 8 | Stops endless retransmission of unacknowledged packets |
| `maxCouriersPerMessage` | 3 | Caps distinct couriers per message |

The router runs `cleanupExpiredMessages()` as a background task to prune expired envelopes once their TTL elapses.

## Courier Discovery and Envelope Handoff

When a new peer becomes available via Bluetooth mesh, the router's `courierBecameAvailable` method scans the outbox for messages that the new peer hasn't yet received a copy of. The router tracks which courier keys already hold a deposit when the message is enqueued.

Upon finding a suitable message at delivering, it calls `sendCourierMessage` to hand over the sealed payload. Before handing off, the router verifies the message hasn't already been copied to `maxCouriersPerMessage` distinct couriers, discarding further attempts once the budget is exhausted.

## Retry Logic and Delivery Acknowledgment

The router continuously attempts redelivery whenever a transport becomes available.

- `flushOutbox` triggers a full retry pass across all pending outbox entries.
- `retrySecurePrivateMessagesAfterAuthentication` is called when the recipient reconnects via Nostr or a new Noise session is established.

When the final ACK arrives, the `markDelivered` method removes the message from the outbox and updates the persistent store, ensuring the UI reflects the sent state. This acknowledgment-driven cleanup is what transforms the system from "best effort" to "guaranteed eventual delivery."

## Persistence and Recovery Between Sessions

The outbox is not held in memory — the **MessageOutboxStore** class writes pending entries to disk. This provides two key capabilities:

- **Crash recovery**: If the app terminates mid-send, the outbox is restored and delivery resumes on the next launch.
- **Multi-network resilience**: messages can be sent over Bluetooth in one location and the ACK received later over the Internet, with the handler functioning seamlessly across connection states.

## Privacy Properties of the Delivery Mechanism

The store and forward mechanism achieves both delivery and anonymity for the courier nodes:

- Couriers see only the `CourierEnvelope`, never the plaintext payload.
- The recipient tag rotates, preventing couriers from tracking the exposed static Noise key over time.
- The network cannot distinguish a store-and-forward message from a direct one, since both use Noise encryption.

The white-paper ([`WHITEPAPER.md`](https://github.com/permissionlesstech/bitchat/blob/main/WHITEPAPER.md)) further documents these layered design decisions and their privacy guarantees.

## Code Example: Typical Send-and-Acknowledge Flow

```swift
// 1. Send a private message — the router decides the best path.
router.sendPrivate(
    "Hey, meet me at the park!",
    to: peerID,
    recipientNickname: "Alice",
    messageID: UUID().uuidString
)

// 2. The router retains the message until an ACK arrives.
router.markDelivered(messageID)


// 3. A new peer connects — trigger a courier retry.
router.courierBecameAvailable(newCourierPeerID)


// 4. Periodic cleanup of expired outbox entries.
router.cleanupExpiredMessages()

```

## Implementation Files to Review

| File | Role in Delivery |
|------|------------------|
| [`bitchat/Services/MessageRouter.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/MessageRouter.swift) | Core routing, outbox management, courier allocation, bridge deposits |
| [`localPackages/BitFoundation/Sources/BitFoundation/CourierEnvelope.swift`](https://github.com/permissionlesstech/bitchat/blob/main/localPackages/BitFoundation/Sources/BitFoundation/CourierEnvelope.swift) | Sealed envelope structure, copy budget, expiration logic |
| [`bitchat/Services/Courier/MessageOutboxStore.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/Courier/MessageOutboxStore.swift) | Persistent disk storage for recovery after restarts |
| [`WHITEPAPER.md`](https://github.com/permissionlesstech/bitchat/blob/main/WHITEPAPER.md) | High-level description of the layered SF stack |
| [`docs/CONVERSATION-STORE-DESIGN.md`](https://github.com/permissionlesstech/bitchat/blob/main/docs/CONVERSATION-STORE-DESIGN.md) | Outbox-as-truth-of-record design rationale |

## Summary

- BitChat's store and forward mechanism uses a persistent outbox that retains every private message until an explicit acknowledgment (`markDelivered`) is received.
- The router pursues three delivery paths in sequence: **direct Noise delivery**, **opportunistic mesh couriers** carrying `CourierEnvelope` objects, and **Nostr relay mail-drops**.
- Safety limits (24-hour TTL, 100 messages per peer, 8 send attempts, 3 courier copies) keep memory bounded while maximizing delivery odds.
- The `MessageOutboxStore` persists the outbox across restarts, making the system resilient to app crashes and network interruptions.

## Frequently Asked Questions

### Q: What happens if a BitChat message is never acknowledged?

If a message hits its TTL (24 hours) without acknowledgment, the router removes it from the outbox via `cleanupExpiredMessages()` and stops further delivery attempts. The user interface no longer displays the message as "sending," and the message is permanently dropped — this is the deliberate trade-off to prevent indefinite storage.

### Q: How does BitChat avoid flooding the Bluetooth mesh with message copies?

The router enforces a `maxCouriersPerMessage` limit of three distinct couriers per message. The `courierCounterEnvelope` tracks how many couriers already carry a copy, and the router stops handing out new copies once the budget is exhausted.

### Q: Can the recipient's device receive the message even if it never connects to the same Bluetooth mesh?

Yes. If the router has bridge deposit enabled, it writes a copy to Nostr relays via `bridgeCourierDeposit`. The recipient can later retrieve this mail-drop from the Nostr side regardless of mesh connectivity.

### Q: What prevents couriers from reading the messages they transport?

Working recommends goes to work in the transport. The payload is sealed inside a Noise-X encrypted `CourierEnvelope`. Couriers only see the recipient tag (derived from the recipient's static Noise key) and cannot decrypt the content, ensuring end-to-end privacy for every store-and-forward hop.