# How BitChat Implements Offline-First Communication: Store-and-Forward Architecture Explained

> Discover how BitChat's store-and-forward architecture enables offline-first communication. Encrypted messages are stored and forwarded via BLE mesh or Nostr relays for reliable delivery.

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

---

**BitChat implements offline-first communication through a store-and-forward architecture where encrypted courier envelopes are persisted locally and forwarded opportunistically via BLE mesh or Nostr relays until delivery succeeds.**

BitChat is an open-source messaging application built by permissionlesstech that guarantees message delivery even when recipients are temporarily unreachable or completely offline. The Swift-based implementation centers on a sophisticated store-and-forward system that combines local mesh networking with global relay infrastructure. This article examines the source code to explain exactly how BitChat achieves resilient offline-first communication without centralized servers.

## Core Store-and-Forward Components

### CourierStore (Offline Mailbag)

The `CourierStore` class in [`bitchat/Services/Courier/CourierStore.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/Courier/CourierStore.swift) serves as the central mailbox for encrypted envelopes that devices carry on behalf of other peers. It stores opaque ciphertext envelopes that cryptographically hide the sender, receiver, and content from the storing device itself. The implementation enforces strict quotas—including total count limits, per-depositor tiers, copy budgets, and a 24-hour maximum lifetime—to prevent any single device from becoming a public mailbag.

### CourierDepositTier and Trust-Based Quotas

BitChat uses `CourierDepositTier` to differentiate between trusted and untrusted depositors. **Favorites** receive larger storage quotas and immunity from eviction, while **verified** deposits (signature-verified but not favorited) receive smaller allocations. This ensures strangers cannot crowd out important messages from trusted contacts.

### MessageRouter and Static Addressing

The `MessageRouter` in [`bitchat/Services/MessageRouter.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/MessageRouter.swift) provides static noise keys for each peer, enabling addressing of offline recipients through persistent cryptographic identifiers. When a message cannot be delivered immediately, the router hands the envelope to `CourierStore.deposit(envelope, from:depositorNoiseKey:, tier:)` with the depositor's trust tier for quota management.

### UnifiedPeerService State Management

`UnifiedPeerService` in [`bitchat/Services/UnifiedPeerService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/UnifiedPeerService.swift) merges mesh connectivity, Nostr availability, and favorite status into unified `BitchatPeer` objects. Each peer carries three possible states: **connected**, **reachable** (seen recently on the BLE mesh), or **offline**. The service specifically retains offline mutual favorites so the router can continue addressing them despite temporary unavailability.

## Dual-Transport Offline Resilience

### BLE Mesh Transport for Local-First Delivery

The BLE mesh implementation in [`bitchat/Services/BLE/BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEService.swift) creates a peer-to-peer network that functions without internet connectivity. When the recipient is mesh-reachable, envelopes spray directly to the peer with duplicate prevention via the `sprayedTo` set. The mesh reports peer snapshots that `UnifiedPeerService` interprets as *reachable* within a configurable retention window.

### Nostr Transport as Global Fallback

`NostrTransport` in [`bitchat/Services/NostrTransport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/NostrTransport.swift) provides global delivery when local mesh contacts fail. It maintains a `reachablePeers` cache derived from favorite relationships containing Nostr public keys. When internet connectivity returns, the transport pushes queued envelopes to Nostr relays for delivery to offline recipients who may reconnect through different network paths.

### GossipSyncManager for Eventual Consistency

The `GossipSyncManager` in [`bitchat/Sync/GossipSyncManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Sync/GossipSyncManager.swift) handles post-reconnection backfill. When devices reconnect after offline periods, they pull missed envelopes from the mesh or Nostr relays, ensuring **eventual consistency** across the decentralized network.

## The Offline-First Message Flow

1. **Composition and Encryption**: The sender creates a `CourierEnvelope` containing the recipient's tag (derived from the static noise key) and encrypts the payload.

2. **Deposit**: `MessageRouter` calls `CourierStore.deposit(envelope, from:myNoiseKey, tier:.favorite)` with validation for size, expiry, and copy budget constraints.

3. **Immediate Forward**: If the recipient is reachable on the BLE mesh, the envelope sprays immediately with budget tracking via the `sprayedTo` set.

4. **Offline Persistence**: If the peer is offline, the envelope remains in `CourierStore` with `maxExpirySlack` guaranteeing 24-hour retention and automatic eviction.

5. **Opportunistic Delivery**: Courier devices forward envelopes as they move within mesh range or when Nostr connectivity resumes.

6. **Acknowledgement**: `AckPacer` throttles delivery receipts to avoid Nostr relay rate limits.

## Implementation Examples

Deposit an envelope for offline delivery:

```swift
let envelope = CourierEnvelope(
    recipientTag: recipientTag,
    expiry: UInt64(Date().addingTimeInterval(48*3600).timeIntervalSince1970 * 1000),
    ciphertext: encryptedPayload,
    copies: 3,
    prekeyID: nil
)
let success = CourierStore.shared.deposit(
    envelope,
    from: myNoiseKey,
    tier: .favorite   // or .verified for non-favorites
)

```

Creating offline peer objects in `UnifiedPeerService`:

```swift
let peer = BitchatPeer(
    peerID: peerID,
    noisePublicKey: favoriteKey,
    nickname: favorite.nickname,
    lastSeen: Date(),
    isConnected: false,
    isReachable: false,
    localPetname: localPetname(forFingerprint: nil)
)

```

Sending via Nostr when mesh is unavailable:

```swift
let transport = NostrTransport(
    keychain: keychain,
    idBridge: idBridge
)
transport.send(message: envelope, to: recipientPeerID)

```

UI offline state detection:

```swift
if peer.isReachable {
    label = Strings.mesh_peers.state.reachable
} else if peer.isConnected {
    label = Strings.mesh_peers.state.connected
} else {
    label = Strings.mesh_peers.state.offline   // offline icon shown
}

```

## Summary

- **CourierStore** provides encrypted, quota-managed storage for undeliverable messages in [`bitchat/Services/Courier/CourierStore.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/Courier/CourierStore.swift)
- **MessageRouter** enables static addressing of offline peers through persistent noise keys in [`bitchat/Services/MessageRouter.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/MessageRouter.swift)
- **UnifiedPeerService** maintains comprehensive peer state across mesh, Nostr, and favorite relationships in [`bitchat/Services/UnifiedPeerService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/UnifiedPeerService.swift)
- **Dual-transport architecture** combines BLE mesh for local delivery and Nostr for global fallback without central coordination
- **GossipSyncManager** ensures eventual consistency when peers reconnect after offline periods
- All envelopes carry cryptographically enforced expiration timestamps and copy budgets to prevent indefinite storage growth

## Frequently Asked Questions

### How does BitChat prevent storage exhaustion when storing messages for offline peers?

BitChat implements tiered quotas through `CourierDepositTier` in [`CourierStore.swift`](https://github.com/permissionlesstech/bitchat/blob/main/CourierStore.swift). Favorite contacts receive larger allocations and eviction protection, while verified strangers face strict limits on envelope count, size, and 24-hour maximum lifetimes. The `maxExpirySlack` parameter guarantees automatic cleanup of undelivered messages after this window.

### Can BitChat deliver messages without any internet connectivity?

Yes. The BLE mesh transport in [`bitchat/Services/BLE/BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEService.swift) creates a local peer-to-peer network capable of forwarding encrypted envelopes between devices in physical proximity. Messages hop across the mesh until reaching the recipient or a device with Nostr connectivity that can bridge to the global network.

### How does BitChat handle addressing when recipients are offline?

The `MessageRouter` utilizes static noise keys that persist in `UnifiedPeerService` as "offline favorite rows." These keys allow the router to encrypt and address envelopes specifically for recipients regardless of their current connectivity state. The envelopes remain in `CourierStore` until the recipient appears on either the mesh or Nostr network.

### What prevents duplicate message delivery across multiple transports?

The `CourierEnvelope` includes a `sprayedTo` set that tracks which peers have already received the envelope. When forwarding via BLE mesh, the system checks this set against the copy budget to prevent double-spending. Additionally, `AckPacer` manages delivery acknowledgements to avoid redundant transmissions through Nostr relays.