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

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 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 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 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 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 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 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:

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:

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:

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

UI offline state detection:

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
  • MessageRouter enables static addressing of offline peers through persistent noise keys in bitchat/Services/MessageRouter.swift
  • UnifiedPeerService maintains comprehensive peer state across mesh, Nostr, and favorite relationships in 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. 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →