# Postbox Database: Telegram-iOS Custom Storage Layer Explained

> Explore the Postbox database, Telegram-iOS's custom storage layer. Discover its thread-safe API, automatic migrations, and reactive UI updates for efficient data management.

- Repository: [TelegramMessenger/Telegram-iOS](https://github.com/TelegramMessenger/Telegram-iOS)
- Tags: internals
- Published: 2026-04-07

---

**Postbox is a custom, strongly-typed storage engine in Telegram-iOS that abstracts raw SQLite access through a thread-safe queue-based API, handles automatic schema migrations, and drives reactive UI updates via a fine-grained view tracking system.**

The **Postbox database** serves as the core data-access layer for the Telegram-iOS client, providing a robust alternative to Core Data or direct SQLite manipulation. As implemented in the `TelegramMessenger/Telegram-iOS` repository, Postbox manages every persistent object—from messages and peers to media metadata and user preferences—within a single version-controlled SQLite file while exposing a type-safe Swift interface.

## Architecture: SQLite Foundation with Abstraction Layer

Postbox sits atop a low-level SQLite wrapper, providing structured access without exposing raw SQL to the application layer.

### The SqliteValueBox Wrapper

The underlying storage relies on `SqliteValueBox`, defined in [`submodules/ValueBox/Sources/SqliteValueBox.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/submodules/ValueBox/Sources/SqliteValueBox.swift). This thin wrapper handles connection pooling, encryption parameters, and batching optimizations. Postbox initializes this wrapper during the `openPostbox` sequence, ensuring the database file exists at `/db` under the application's base path before exposing higher-level APIs.

### Single File Storage and Version Control

Unlike distributed storage solutions, Postbox consolidates all data into **one SQLite file**. The `openPostbox` function (lines 1449–1499 in [`Postbox.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/Postbox.swift)) coordinates initialization by reading the stored `userVersion` from the `MetadataTable`. If the stored version differs from the current codebase version (`25` at the time of analysis), the system automatically executes registered upgrade steps via `registeredUpgrades()` before making the database available for transactions.

## Thread-Safe Transaction Handling

All database operations in Postbox are serialized through a dedicated dispatch queue to prevent race conditions and ensure ACID compliance.

### The Shared Queue Pattern

Postbox uses `Postbox.sharedQueue` (referenced at lines 4585–4592 in [`Postbox.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/Postbox.swift)) as the single concurrency domain for read and write operations. When you invoke `transaction` or `transactionSignal`, the implementation routes the work to this queue, guaranteeing exclusive access during the transaction lifecycle. The public `Transaction` object passed to your closure provides the actual database interface while the queue manages batching and commit synchronization.

### Transaction API Examples

To perform writes, you execute closures on the shared queue through the `transaction` method (lines 4560–4570 in [`Postbox.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/Postbox.swift)):

```swift
postbox.transaction { transaction in
    // Add a new message to the local store
    let message = StoreMessage(
        id: MessageId(peerId: somePeerId, namespace: .generic, id: 0),
        // ... additional initialization
    )
    transaction.addMessages([message], location: .Local)
    
    // Store a keychain entry within the same transaction
    let secretData = Data("my-secret".utf8)
    transaction.setKeychainEntry(secretData, forKey: "mySecret")
    
    return true
}
.start(next: { success in
    print("Transaction completed, success:", success)
})

```

This approach ensures that even complex multi-table updates—such as inserting messages while updating peer metadata—remain atomic and thread-safe.

## Automatic Schema Migrations

Postbox eliminates manual database versioning through an incremental migration framework.

### Version Tracking in MetadataTable

The `MetadataTable` stores the current schema version as `userVersion`. During initialization, `PostboxImpl` compares this value against the compiled version constant. When discrepancies are detected, the system executes specific upgrade files such as [`PostboxUpgrade_24to25.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/PostboxUpgrade_24to25.swift) (located in `submodules/Postbox/Sources/`) before allowing application code to access the database.

### Upgrade Path Implementation

Migration files follow a consistent naming convention ([`PostboxUpgrade_XtoY.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/PostboxUpgrade_XtoY.swift)) and contain the SQL or API calls necessary to transform old schemas. Because upgrades run synchronously during `openPostbox`, the application never encounters partially migrated databases. The `openPostbox` function returns `.upgrading(progress)` signals during this phase, allowing UI layers to display migration status to users.

## Reactive Data Flow with View Tracking

Postbox implements an observer pattern that pushes changes to UI components without requiring polling or manual refresh logic.

### ViewTracker and Live Collections

The `PostboxImpl` class (lines 1580–1620 in [`Postbox.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/Postbox.swift)) maintains a `ViewTracker` instance that registers observers for derived collections. These include specialized views like "failed message IDs," "chat list holes," and "global tag views." When a transaction commits, the tracker calculates diffs and notifies registered observers automatically.

### Practical Observation Example

To observe real-time changes, such as failed message IDs for a specific peer:

```swift
postbox.failedMessageIdsView(peerId: somePeerId)
    .start(next: { view in
        // This closure triggers automatically when underlying DB changes
        print("Failed IDs:", view.ids)
    })

```

The view creation method (around line 4561 in [`Postbox.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/Postbox.swift)) constructs a reactive pipeline that bridges the gap between raw database transactions and Swift UI updates.

## Integration with Media and Keychain Storage

Postbox extends beyond simple message storage to handle binary assets and cryptographic material.

### MediaBox Integration

The `MediaBox` class (defined in [`submodules/MediaBox/Sources/MediaBox.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/submodules/MediaBox/Sources/MediaBox.swift)) works alongside Postbox to manage file storage for photos, videos, and documents. While Postbox stores metadata and file references in its SQLite tables, MediaBox handles the actual file system operations, thumbnail generation, and caching policies. This separation keeps the primary database lightweight while allowing efficient media streaming.

### Keychain Entry Management

Postbox provides auxiliary storage for small cryptographic secrets through `setKeychainEntry` and `getKeychainEntry` methods on the `Transaction` object. These entries reside within the encrypted database rather than iOS Keychain Services, enabling atomic updates alongside related message data while maintaining the same encryption standards as primary content.

## Summary

- **Postbox** is a custom SQLite abstraction layer in Telegram-iOS that replaces direct SQL manipulation with type-safe Swift APIs.
- All operations execute on `Postbox.sharedQueue`, ensuring thread safety through serial transaction processing.
- **Automatic migrations** via `MetadataTable` versioning and `registeredUpgrades()` keep schemas current without manual intervention.
- The **ViewTracker** system enables reactive UI updates by diffing database changes and pushing notifications to registered views.
- **MediaBox** integration separates large binary assets from the core database while maintaining transactional consistency.
- Source files are located in `submodules/Postbox/Sources/` with key logic in [`Postbox.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/Postbox.swift) (particularly lines 1449–1620 and 4560–4592).

## Frequently Asked Questions

### How does Postbox handle database versioning?

Postbox stores a `userVersion` integer in the `MetadataTable`. During initialization, `openPostbox` compares this value against the current codebase version. If they differ, the system executes sequential upgrade steps from files like [`PostboxUpgrade_24to25.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/PostboxUpgrade_24to25.swift) before allowing database access, ensuring the schema is always current.

### Why does Postbox use a single shared queue instead of concurrent connections?

The `Postbox.sharedQueue` pattern serializes all reads and writes to prevent SQLite locking conflicts and race conditions in Swift. This design guarantees that the `Transaction` object has exclusive access during execution, eliminating the need for complex locking primitives while maintaining ACID properties.

### What is the relationship between Postbox and MediaBox?

**MediaBox** handles file system operations for large binary assets like photos and videos, while **Postbox** stores the metadata, file references, and user data in its SQLite database. They work together to provide a complete storage solution where media caching policies operate independently from the structured data layer.

### Can Postbox be used in read-only mode?

Yes. The `openPostbox` function accepts an `isReadOnly` boolean parameter. When set to true, the database opens without write permissions, suitable for background extensions or widgets that only need to query existing data without modifying state.