# How BitChat Routes Read Receipts and Delivery Acknowledgments: A Complete Technical Guide

> Understand how BitChat routes read receipts and delivery acknowledgments. Explore the technical architecture for efficient message tracking and delivery confirmation.

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

---

**BitChat routes read receipts and delivery acknowledgments through a multi-layered architecture where `ReadReceipt` structs are serialized to binary payloads, routed via `MessageRouter` to available transports (direct, mesh, or Nostr), and tracked in the view model using `DeliveryStatus` enums to prevent duplicate processing.**

BitChat is an open-source peer-to-peer messaging framework that prioritizes reliable message delivery confirmation. Understanding how read receipts and delivery acknowledgments are routed through its Swift codebase reveals the architectural patterns that ensure end-to-end reliability across varying network topologies.

## Read Receipt Data Model and Binary Serialization

The foundation of BitChat's acknowledgment system lives in [`bitchat/Models/ReadReceipt.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Models/ReadReceipt.swift). Here, the **`ReadReceipt`** struct captures five critical fields: the original message ID, a unique receipt ID, the reader’s `PeerID`, the reader’s nickname, and a timestamp.

This struct conforms to `Codable` for JSON transport but implements custom **`toBinaryData()`** and **`fromBinaryData(_:)`** methods for efficient network transmission. The binary format packs two 16-byte UUIDs (message ID and receipt ID), an 8-byte `PeerID` representation, a 64-bit timestamp, and the nickname string into a compact `Data` payload. The `fromBinaryData` method validates each field—checking UUID validity, peer ID integrity, timestamp ranges, and nickname constraints—before reconstructing the receipt.

```swift
// Creating a read receipt when a user views a private message
let receipt = ReadReceipt(
    originalMessageID: message.id,
    readerID: transport.myPeerID,
    readerNickname: "Me"
)

// Encoding for network transmission
let binaryPayload = receipt.toBinaryData()

```

## Transport Layer Routing and Abstraction

BitChat abstracts network complexity through the **`MessageRouter`** class. The method `MessageRouter.sendReadReceipt(_:to:)` resolves the appropriate reachable transport—whether direct peer-to-peer, mesh networking, or Nostr relay—and forwards the binary payload.

The transport implementations expose a unified **`sendReadReceipt(_:to:)`** interface. In test environments, [`bitchatTests/Mocks/MockTransport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchatTests/Mocks/MockTransport.swift) demonstrates this contract by storing receipts in a `sentReadReceipts` array for verification, while production transports forward the binary data over active connections. This abstraction ensures that read receipts reach intended peers regardless of underlying connection type.

```swift
// Router selects the best available transport and sends
router.sendReadReceipt(receipt, to: peerID)

// Transport layer serializes and transmits
transport.sendReadReceipt(receipt, to: peerID)

```

## Delivery Status Tracking with Enums

Delivery state management resides in [`localPackages/BitFoundation/Sources/BitFoundation/DeliveryStatus.swift`](https://github.com/permissionlesstech/bitchat/blob/main/localPackages/BitFoundation/Sources/BitFoundation/DeliveryStatus.swift). The **`DeliveryStatus`** enum defines the message lifecycle through cases including `.sending`, `.sent`, `.delivered(to:at:)`, and `.read(by:at:)`.

Each `BitchatMessage` maintains a `deliveryStatus` property that transitions through these states. When a transport confirms successful transmission, the status updates from `.sending` to `.sent`. Upon receiving a read receipt from the recipient, the system updates the corresponding message to `.read`, capturing the reader’s nickname and timestamp for display.

## View-Model Receipt Management and Idempotency

The `ChatViewModel` class prevents duplicate processing through careful bookkeeping. It maintains two `Set` collections: **`sentReadReceipts`** (tracking IDs of receipts already transmitted) and **`sentDeliveryAcks`** (tracking message IDs with confirmed delivery).

Helper methods enforce idempotent operations:
- **`markReadReceiptSent(_:)`** adds receipt IDs to the tracking set
- **`pruneSentReadReceipts(keeping:)`** removes stale IDs to manage memory
- **`didReceiveReadReceipt(_:)`** processes incoming receipts, updates the `messageStore`, and records the receipt ID to prevent reprocessing

```swift
func didReceiveReadReceipt(_ receipt: ReadReceipt) {
    // Update the matching message's status
    if var msg = messageStore[receipt.originalMessageID] {
        msg.deliveryStatus = .read(by: receipt.readerNickname, at: receipt.timestamp)
        messageStore[receipt.originalMessageID] = msg
    }
    
    // Prevent duplicate processing
    sentReadReceipts.insert(receipt.receiptID)
}

```

## End-to-End Routing Flow

The complete routing of read receipts and delivery acknowledgments follows this deterministic sequence:

1. **Sender activation**: After displaying a private message, the sender creates a `ReadReceipt` instance with the original message ID and local peer information.

2. **Router resolution**: The view model invokes `router.sendReadReceipt(receipt, to: peerID)`, which evaluates available network paths (direct, mesh, or relay) and selects the appropriate transport.

3. **Binary transmission**: The chosen transport calls `receipt.toBinaryData()`, producing the compact binary payload containing UUIDs, peer ID, timestamp, and nickname.

4. **Network transmission**: The payload travels across the selected protocol layer to the recipient.

5. **Receiver processing**: The recipient deserializes the payload using `ReadReceipt.fromBinaryData(data)`, updates the corresponding `BitchatMessage.deliveryStatus` to `.read`, and optionally transmits a delivery acknowledgment back to the sender.

6. **Idempotent tracking**: Both ends maintain their respective tracking sets to ensure acknowledgments are processed exactly once, even in high-latency or reconnecting network conditions.

## Summary

- **Binary efficiency**: `ReadReceipt` uses custom `toBinaryData()` and `fromBinaryData(_:)` methods in [`ReadReceipt.swift`](https://github.com/permissionlesstech/bitchat/blob/main/ReadReceipt.swift) to minimize network overhead.
- **Transport abstraction**: `MessageRouter.sendReadReceipt(_:to:)` hides network complexity, routing receipts through direct, mesh, or Nostr transports as implemented in the test suite.
- **State management**: The `DeliveryStatus` enum in `BitFoundation` provides type-safe tracking of message states from sending through read confirmation.
- **Duplicate prevention**: `ChatViewModel` uses `sentReadReceipts` sets and helper methods like `pruneSentReadReceipts(keeping:)` to ensure idempotent receipt handling.
- **End-to-end reliability**: The system guarantees delivery confirmation through structured serialization, abstracted routing, and persistent state tracking across peer connections.

## Frequently Asked Questions

### How does BitChat prevent duplicate read receipts from being processed?

BitChat prevents duplicate processing through the `ChatViewModel` class, which maintains a `Set` called `sentReadReceipts` containing unique receipt IDs. When `didReceiveReadReceipt(_:)` processes an incoming receipt, it inserts the `receiptID` into this set. Subsequent receipts with identical IDs are ignored, ensuring idempotent handling even if network conditions cause duplicate transmissions.

### What transport protocols does BitChat use for routing read receipts?

According to the source code in [`bitchatTests/Services/MessageRouterTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchatTests/Services/MessageRouterTests.swift), the `MessageRouter` abstracts three transport types: direct peer-to-peer connections, mesh networking protocols, and Nostr relays. The router's `sendReadReceipt(_:to:)` method evaluates reachability and selects the appropriate transport, ensuring receipts reach recipients regardless of which underlying network stack is available.

### How is delivery status represented in the BitChat data model?

Delivery status is represented by the **`DeliveryStatus`** enum defined in [`localPackages/BitFoundation/Sources/BitFoundation/DeliveryStatus.swift`](https://github.com/permissionlesstech/bitchat/blob/main/localPackages/BitFoundation/Sources/BitFoundation/DeliveryStatus.swift). This enum tracks message progression through states including `.sending`, `.sent`, `.delivered(to:at:)`, and `.read(by:at:)`. Each `BitchatMessage` maintains this status property, which updates as the transport layer confirms transmission and as read receipts arrive from recipients.

### What validation does BitChat perform when decoding binary read receipts?

When decoding binary read receipts via `ReadReceipt.fromBinaryData(_:)` in [`bitchat/Models/ReadReceipt.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Models/ReadReceipt.swift), the system validates multiple fields: UUID format for both the original message ID and receipt ID, peer ID integrity (8-byte validation), timestamp range checks (64-bit validity), and nickname string constraints. This validation ensures that malformed or corrupted network payloads cannot instantiate invalid receipt objects.