Skip to main content
Understanding the client lifecycle is essential for building XMTP applications. This guide covers client initialization, identity registration, and ongoing management.

Overview

An XMTP client progresses through these stages:
  1. Builder Configuration: Set up API, storage, and identity
  2. Client Construction: Initialize context and workers
  3. Identity Registration: Link installation to inbox (if needed)
  4. Ready State: Client can create/join groups and send messages
  5. Lifecycle Management: Reconnection, cleanup, and shutdown

Client Architecture

The Client<Context> is generic over its context:
Typically, Context is Arc<XmtpMlsLocalContext<ApiClient, Db, S>> containing:
  • Identity and installation keys
  • API client for network communication
  • Database for state and messages
  • MLS storage for cryptographic material
  • Smart contract verifier
  • Locks and mutexes for concurrency
  • Event channels

Building a Client

Identity Strategy

Choose how to handle identity:
CreateIfNotFound (most common):
  • Generates installation keys on first run
  • Derives inbox ID from wallet + nonce
  • Stores identity in database
  • Subsequent runs load from database
CachedOnly:
  • For clients that must already exist
  • Fails if database doesn’t contain identity
  • Useful for re-initializing after disconnect
External:
  • Advanced use case
  • Manually construct Identity and pass in
  • Full control over key generation

Basic Builder Pattern

Configuration Options

API Clients

Two API clients are required:
  • api_client: Main network operations (send messages, add members)
  • sync_api_client: Background sync operations (pull messages, welcomes)
Typically these are the same client, but can be different for advanced scenarios.

Storage

Application Store:
  • Encrypted SQLite database via xmtp_db
  • Stores messages, groups, identity updates, consent
MLS Store:
  • Stores OpenMLS state (epochs, key packages, ratchet trees)
  • Default: SqlKeyStore backed by same database
  • Can provide custom implementation
Use EncryptedMessageStore::new() or EncryptedMessageStore::new_unencrypted() to create the application store. Encryption key should be securely stored by the application.

Smart Contract Verifier

Or use the remote verifier:
Required for validating smart contract wallet signatures. Options:
  • RemoteSignatureVerifier: Uses XMTP validation service
  • ChainRpcVerifier: Validates directly via EVM RPC (requires RPC URLs)
  • Custom implementation
See Identity System for details on signature verification.

Workers

Workers run in background:
  • SyncWorker: Polls for new messages (if device sync enabled)
  • KeyPackagesCleanerWorker: Rotates key packages
  • DisappearingMessagesWorker: Deletes expired messages
  • PendingSelfRemoveWorker: Processes pending member removals
  • CommitLogWorker: Publishes commit logs (optional)
Disabling workers is useful for testing or manual control.

Optional Configuration

Full Example

Identity Registration

After building, the client must register its installation with the network.

Registration States

The client can be in three states:

1. New Inbox (No Previous Registration)

Inbox ID doesn’t exist on network:
What this does:
  1. Creates inbox on network
  2. Links wallet and installation to inbox
  3. Uploads key package
  4. Publishes identity update
After this, client.identity().is_ready() returns true.

2. Existing Inbox, New Installation

Inbox exists but this installation isn’t registered:
Any wallet already linked to the inbox can sign the AddAssociation action. It doesn’t have to be the original wallet.

3. Already Registered

Client installation is already registered:
register_identity() is idempotent and returns immediately if already registered.

Registration Flow Details

Inside register_identity() (from client.rs:875-942):
  1. Check if already registered: Query database for StoredIdentity
    • If exists, mark ready and return
  2. Generate key package: Create MLS key package with installation key
    • Store locally (not uploaded yet)
  3. Validate signatures: Verify all signatures before network calls
    • Prevents polluting network with invalid updates
  4. Upload key package: Send to network first
    • Prevents race where identity update visible but no key package
  5. Publish identity update: Make installation visible on network
  6. Fetch and store: Download identity updates and store in local DB
    • Needed for group operations
  7. Clean up old key packages: Mark previous packages for deletion
  8. Mark identity ready: Set is_ready() flag
Do not attempt to create groups or send messages before register_identity() completes. Operations will fail with IdentityError::UninitializedIdentity.

Key Package Management

Rotation

Key packages should be rotated after use:
When to rotate:
  • After receiving a welcome message (key package consumed)
  • Periodically (recommended but not enforced)
Automatic rotation: The KeyPackagesCleanerWorker handles rotation based on schedule stored in database.

Manual Queueing

Schedules rotation to occur within 5 seconds if none is scheduled.

Fetching Key Packages

Get current key packages for installations:
Used internally when adding members to groups.

Client Operations

Accessing Client Data

Checking Registration Status

All group/message operations check this internally and return IdentityError::UninitializedIdentity if not ready.

Getting Inbox State

Sequence ID

Get current sequence ID for your inbox:
Sequence ID increments with each identity update and is used for group membership tracking.

Lifecycle Management

Disconnecting Database

Release database connection (useful for mobile):
Workers are stopped and database is closed.

Reconnecting

Reconnect after releasing:
Reopens database and restarts workers.

Waiting for Workers

Ensure sync worker is initialized:
Blocks until SyncWorker is running and ready.

Sync Operations

Sync Welcomes

Discover new groups:

Sync All Groups

Update all existing groups:
Optionally filter by consent state.

Error Handling

Common Errors

UninitializedIdentity:
Solution: Call register_identity() before performing operations. Storage Errors:
Solution: Check database connection, disk space, encryption key. API Errors:
Solution: Check network connectivity, retry with backoff.

Retryable Errors

Check if error should be retried:

Testing

Test Utilities

Create test clients:

Mock Implementations

Offline Testing

Skips network calls during initialization.

Best Practices

Secure Key Storage

The database encryption key must be securely stored by the application. LibXMTP does not manage encryption key storage.
Recommendations:
  • Mobile: Use platform keychain (iOS Keychain, Android Keystore)
  • Web: Consider Web Crypto API or secure server-side storage
  • Desktop: OS keychain or encrypted file with user password

Worker Management

For production:
For testing:
Call sync methods manually when workers disabled.

Error Recovery

Implement retry logic with exponential backoff for transient errors:

Database Migration

Diesel handles migrations automatically. On first run:

Platform-Specific Considerations

Mobile (iOS/Android)

  • Release DB connection when app backgrounds
  • Reconnect when app foregrounds
  • Use platform keychain for encryption key
  • Consider battery impact of sync workers

Web (WASM)

  • Use IndexedDB for storage
  • Workers run in Web Workers (if supported)
  • Consider IndexedDB size limits
  • Handle offline scenarios

Node.js

  • Use file-based SQLite
  • Workers run in Node async runtime
  • Consider clustering for multiple clients