# How Messages Are Routed and Delivered in the Bitchat Network: A Deep Dive into MessageRouter

> Discover how Bitchat routes and delivers messages using its MessageRouter service. Learn about peer connectivity, offline delivery fallbacks, and internet relays for robust communication.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: deep-dive
- Published: 2026-08-20

---

**Bitchat uses a tiered `MessageRouter` service that selects transports based on peer connectivity, falls back to opportunistic couriers for offline delivery, and bridges to internet relays when mesh paths fail—implementing a robust store-and-forward system with 24-hour TTL outbox persistence.**

The **bitchat** network, developed by [permissionlesstech](https://github.com/permissionlesstech/bitchat), is a permissionless messaging protocol designed for resilience across intermittent connectivity. Unlike centralized messaging apps, bitchat's message routing and delivery mechanism must handle peers that come and go from the network constantly. The core orchestrator for this challenge is the `MessageRouter` service in [`Services/MessageRouter.swift`](https://github.com/permissionlesstech/bitchat/blob/main/Services/MessageRouter.swift).

## Transport Selection Tiers in MessageRouter

When you send a private message, the `MessageRouter` evaluates available transports in three priority tiers. Each tier determines both how the message is sent and whether redundant courier deposits are needed.

### Connected Transport with Secure Session

If the recipient has an active connection **and** an established Noise session, the message takes the fastest path. The router:

- Sends the payload over the secure channel
- Records the transmission in `secureTransmissions` for potential retry after session resets

```swift
// Connected transport with Noise session
router.sendPrivate(
    "Hey, meet me at 5 pm!",
    to: recipientPeerID,
    recipientNickname: "Alice",
    messageID: UUID().uuidString
)

```

### Connected Without Secure Session

When a peer is connected but lacks an authenticated Noise session, the router sends the payload **and** immediately initiates a **courier deposit**. This ensures message availability even if the direct connection drops during authentication.

### Reachable Transport Only

For peers that are merely reachable—such as a Nostr relay showing the recipient's public key online—the message is queued for transmission. If prompt delivery isn't guaranteed, the router attempts a courier deposit as insurance.

### No Reachable Transport

When no path exists, the message enters the outbox and is handed exclusively to couriers for deferred delivery.

## Courier Handling and Opportunistic Routing

The **courier system** is bitchat's answer to intermittent connectivity. When direct delivery fails, the `MessageRouter` recruits other connected peers to physically carry encrypted envelopes across the mesh.

### Courier Selection Process

The router queries the `CourierDirectory`—backed by the local favorites store—to locate the recipient's static Noise key. It then:

- Selects up to **`maxCouriersPerMessage`** eligible couriers
- Prioritizes **mutual favorites** for trust and availability
- Invokes `sendCourierMessage` on each `MeshCourierTransporting` transport

Successful deposits are recorded via `recordCourierDeposit` and exposed through the `onMessageCarried` callback:

```swift
router.onMessageCarried = { msgID, peerID in
    print("📦 Message \(msgID) handed to a courier for \(peerID)")
}

```

## Bridge Deposit to Internet Relays

As a final fallback to the mesh, bitchat can deposit sealed envelopes onto internet relays. The `bridgeCourierDeposit` closure executes after courier attempts, with its completion flag indicating whether at least one relay accepted the envelope. This bridges the local mesh to the broader Nostr relay network when peer-to-peer paths fail entirely.

## Outbox Management and Persistence

Every outgoing private message enters an **in-memory outbox** (`[PeerID: [QueuedMessage]]`) with optional persistence through `MessageOutboxStore`. The outbox enforces:

| Constraint | Value | Purpose |
|------------|-------|---------|
| `messageTTLSeconds` | 24 hours | Maximum retention before cleanup |
| `maxMessagesPerPeer` | (configurable) | Per-peer queue depth limit |

### Outbox Lifecycle Operations

The `MessageRouter` runs three critical maintenance routines:

1. **`flushOutbox(for:)`** – Attempts delivery when a peer becomes reachable
2. **`retrySecurePrivateMessagesAfterAuthentication()`** – Retries messages after Noise session establishment
3. **`cleanupExpiredMessages()`** – Removes items exceeding 24-hour TTL

When delivery or read receipts arrive, `markDelivered` removes the retained copy, releases courier slots, and persists the state change.

```swift
// Flush when connectivity changes
router.flushOutbox(for: recipientPeerID)

// Handle failures
router.onMessageDropped = { msgID, peerID in
    print("❌ Message \(msgID) to \(peerID) failed")
}

```

## Read Receipt Routing

Read receipts follow the same tiered routing logic as messages. The `sendReadReceipt` method attempts delivery through any reachable transport. If none are available, the receipt remains in-flight for later retry—ensuring eventual acknowledgment without blocking the UI.

## Key Source Files for Message Routing

Understanding bitchat's routing implementation requires familiarity with these files:

- **[`Services/MessageRouter.swift`](https://github.com/permissionlesstech/bitchat/blob/main/Services/MessageRouter.swift)** – Core routing logic, outbox management, courier coordination, and bridge fallbacks
- **[`localPackages/BitFoundation/Sources/BitFoundation/BitchatMessage.swift`](https://github.com/permissionlesstech/bitchat/blob/main/localPackages/BitFoundation/Sources/BitFoundation/BitchatMessage.swift)** – Wire format including sender, payload, and encryption flags
- **[`localPackages/BitFoundation/Sources/BitFoundation/BinaryProtocol.swift`](https://github.com/permissionlesstech/bitchat/blob/main/localPackages/BitFoundation/Sources/BitFoundation/BinaryProtocol.swift)** – Low-level packet encoding with optional routing hop metadata
- **[`docs/SOURCE_ROUTING.md`](https://github.com/permissionlesstech/bitchat/blob/main/docs/SOURCE_ROUTING.md)** – Design documentation for routing decisions and fallback strategies
- **[`docs/ARCHITECTURE_V2.md`](https://github.com/permissionlesstech/bitchat/blob/main/docs/ARCHITECTURE_V2.md)** – Overview of the store-and-forward architecture

## Summary

- **Tiered transport selection** in [`MessageRouter.swift`](https://github.com/permissionlesstech/bitchat/blob/main/MessageRouter.swift) prioritizes secure direct connections, falls back to unsecured direct links with courier backup, and finally delegates to purely opportunistic routing
- **Courier deposit** recruits mutual favorites to carry encrypted envelopes when recipients are unreachable, with `maxCouriersPerMessage` limiting redundancy
- **Bridge fallback** deposits messages onto Nostr relays via `bridgeCourierDeposit` when mesh couriers fail
- **24-hour TTL outbox** with `flushOutbox`, `cleanupExpiredMessages`, and `markDelivered` ensures eventual delivery without unbounded growth
- **Deduplication by message ID** on the receiver side prevents duplicate processing across multiple delivery paths

## Frequently Asked Questions

### What happens if the recipient is completely offline when I send a message?

The message is stored in the outbox with a 24-hour TTL and handed to available couriers. If couriers cannot reach the recipient, the `bridgeCourierDeposit` closure attempts to deposit the sealed envelope on Nostr relays. The `flushOutbox` method retries delivery automatically when the recipient becomes reachable.

### How does bitchat prevent message duplication across multiple couriers?

Recipients deduplicate by **message ID** on arrival. While multiple couriers may carry the same envelope, the receiver processes only the first successful delivery and discards subsequent copies with identical IDs.

### What determines which peers become couriers for my messages?

The `CourierDirectory` selects couriers from favorites-boosted candidates, preferring **mutual favorites** for trust and availability. The router respects `maxCouriersPerMessage` to limit network overhead while maintaining reliable redundancy.

### Can I configure how long messages remain in the outbox?

The TTL is fixed at **24 hours** via `messageTTLSeconds` in the current implementation. However, `maxMessagesPerPeer` is configurable to control per-recipient queue depth and storage pressure.