How BitChat Handles Offline Communication: Mesh Networks, Couriers, and Nostr Fallback

BitChat achieves resilient offline messaging by combining a local Bluetooth mesh network with a store-and-forward courier system and an Internet-fallback Nostr transport.

BitChat, an open-source peer-to-peer messaging protocol developed by permissionlesstech/bitchat, is engineered to function independently of centralized servers or continuous internet connectivity. The application solves BitChat offline communication challenges through a multi-layered transport stack that automatically selects the most reliable path for message delivery. This architecture ensures end-to-end encrypted messages reach recipients even when devices are temporarily out of direct range or disconnected from the internet.

Bluetooth Mesh: The Offline-First Transport Layer

At the foundation of BitChat’s offline capability lies a Bluetooth Low Energy (BLE) mesh network that operates completely without internet access. Implemented in BLEService.swift, the mesh transport discovers nearby devices via BLE advertising and forms a decentralized relay network capable of forwarding messages up to seven hops.

The mesh encrypts all traffic using the Noise protocol, ensuring that relaying peers cannot read message contents. When a user sends a message, the system first attempts delivery through this local mesh, leveraging physically proximal devices as relays. This approach prioritizes privacy and speed by keeping traffic on the local network whenever possible, avoiding internet exposure entirely for nearby peers.

Store-and-Forward Courier System

When direct BLE links cannot reach the final recipient, BitChat employs a sophisticated store-and-forward mechanism centered on MessageRouter.swift. This system treats the local device and nearby peers as physical couriers that carry encrypted envelopes until they encounter the target device.

Message Outbox and Routing Logic

The router maintains a MessageOutboxStore for sealed envelopes awaiting delivery. When a peer is unreachable via direct BLE, the CourierDirectory—an internal component of MessageRouter.swift—resolves the recipient’s static Noise key, which serves as a persistent offline routing identifier. The router then determines whether a nearby mesh peer should carry the message physically or hold it for later transmission.

// Inside MessageRouter – transport selection
private func reachableTransport(for peerID: PeerID) -> Transport? {
    // Picks the first transport that reports the peer as reachable
    transports.first { $0.isPeerReachable(peerID) }
}

Retry Logic and Expiration

Messages that cannot be sent immediately enter a persistent queue within MessageRouter.outbox. The system periodically executes retryBridgeCourierDeposits to attempt redelivery as network conditions change. Stale entries automatically expire after 24 hours, preventing indefinite storage of undeliverable messages. Offline favorites are persisted with their full 64-hex Noise keys, allowing the router to address peers using static identifiers even when they are not currently reachable.

Detecting Offline Peers with UnifiedPeerService

BitChat distinguishes between online and offline peers through UnifiedPeerService.swift, which continuously tracks device reachability across all transport layers. A peer is classified as offline when its static Noise key is known but no active BLE connection exists.

The service updates the UI state to .offline and filters the peer list accordingly, ensuring users understand which contacts are immediately reachable versus those requiring store-and-forward delivery. This detection mechanism enables the message router to make intelligent decisions about which transport method to employ for each recipient.

Nostr Fallback: Bridging Offline to Online

When the BLE mesh cannot locate a recipient and no couriers are available, BitChat falls back to NostrTransport.swift as a bridge between offline and online worlds. This transport treats the recipient’s Noise static key as a persistent identifier and transmits the encrypted private envelope via Nostr relays.

The NostrTransport class maintains a reachablePeers cache populated during initialization with offline favorites loaded from persistent storage. The transport evaluates delivery feasibility through methods like canDeliverPromptly, enabling the router to determine whether immediate delivery is possible or if queuing is necessary.

// NostrTransport – building the offline peers cache
init(keychain _: KeychainManagerProtocol,
     idBridge: NostrIdentityBridge,
     dependencies: Dependencies? = nil) {
    self.dependencies = dependencies ?? .live(idBridge: idBridge)

    // Warm the cache with offline favorites (static Noise keys)
    let favorites = self.dependencies.loadFavorites()
    let reachable = favorites.values
        .filter { $0.peerNostrPublicKey != nil }
        .map { PeerID(publicKey: $0.peerNoisePublicKey) }

    queue.sync(flags: .barrier) { self.reachablePeers = Set(reachable) }
}

End-to-End Message Flow Implementation

The high-level ChatViewModel delegates sending operations to MessageRouter, which orchestrates the multi-transport delivery attempt. The following implementation demonstrates how a direct message automatically traverses the offline communication stack:

// Example: Sending a DM that may be offline
func sendDirectMessage(_ text: String, to peerID: PeerID) async {
    // ChatViewModel delegates to MessageRouter
    await chatViewModel.sendPrivate(
        content: text,
        to: peerID,
        nickname: peerID.displayName,
        messageID: UUID().uuidString
    )
    // MessageRouter will:
    //  1️⃣ try a BLE transport (offline mesh)
    //  2️⃣ if BLE unreachable, use NostrTransport (offline → online)
    //  3️⃣ otherwise queue the message for later delivery
}

This flow ensures that messages always attempt the most private path first (local BLE), escalate to internet fallback only when necessary (Nostr), and guarantee eventual delivery through persistent queuing.

Summary

  • Bluetooth mesh networking in BLEService.swift enables internet-free communication by relaying messages through nearby devices using the Noise protocol.

  • Store-and-forward couriers in MessageRouter.swift physically carry encrypted envelopes via the CourierDirectory until recipients become reachable, using static Noise keys for persistent addressing.

  • Offline peer detection via UnifiedPeerService.swift tracks reachability states and updates UI indicators to reflect which contacts are immediately available.

  • Nostr transport fallback in NostrTransport.swift bridges offline and online worlds by routing encrypted messages through Nostr relays when local mesh delivery fails.

  • Smart queuing with 24-hour expiration ensures messages persist across app restarts and network changes until any transport layer becomes available.

Frequently Asked Questions

How does BitChat deliver messages without an internet connection?

BitChat utilizes a BLE mesh network implemented in BLEService.swift that forwards messages through chains of physically nearby devices. This mesh operates independently of internet connectivity, using the Noise protocol for encryption and supporting up to seven hops between sender and recipient.

What happens to messages when a recipient is offline?

When a recipient is offline, messages enter the MessageOutboxStore managed by MessageRouter.swift. The system queues the encrypted envelope and periodically retries delivery via retryBridgeCourierDeposits. If no courier is available, the message falls back to NostrTransport for delivery through Nostr relays, or remains queued for up to 24 hours awaiting mesh connectivity.

How does the courier system route messages to offline peers?

The CourierDirectory within MessageRouter.swift resolves recipients by their static Noise keys, which serve as persistent offline identifiers. When a direct link is unavailable, the router delegates the message to a nearby peer acting as a courier, who stores the encrypted envelope and attempts to forward it upon encountering the target device within the BLE mesh.

What encryption protects offline messages in BitChat?

All offline communications are encrypted using the Noise protocol, a modern cryptographic framework that ensures end-to-end encryption even when messages traverse multiple relay hops. The static Noise keys used for routing in CourierDirectory are distinct from the encryption keys, ensuring that relaying peers cannot decrypt message contents.

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 →