Core components
Hoot’s architecture is built around four main components that work together to provide a seamless email experience over the Nostr protocol:RelayPool
Manages WebSocket connections to multiple Nostr relays with automatic reconnection and keepalive pings
AccountManager
Handles Nostr keypairs, secure key storage, and NIP-59 gift-wrap encryption/decryption
Database
SQLite database with SQLCipher encryption for storing events, messages, and profile metadata
UI
Immediate-mode GUI built with egui, featuring page-based navigation and floating compose windows
Technology stack
Hoot is built with the following core technologies:- Rust: Systems programming language for performance and memory safety
- egui: Immediate-mode GUI framework for native desktop interfaces
- Nostr SDK: Protocol implementation via the
nostrcrate (v0.37.0) - SQLite + SQLCipher: Encrypted local database for event storage
- ewebsock: WebSocket library for relay connections
Component interactions
Relay system
The relay system manages connections to multiple Nostr relays and handles all network communication.RelayPool
TheRelayPool (src/relay/pool.rs:13) manages multiple relay connections:
- Maintains a
HashMap<String, Relay>of active connections - Automatic reconnection every 5 seconds for disconnected relays
- Keepalive pings every 30 seconds to maintain connections
- Tracks subscriptions and authentication state per relay
Relay
Individual relay connections (src/relay/mod.rs:28) use WebSocket communication:ewebsocklibrary for WebSocket connections with wake-up callbacks- Three connection states:
Connecting,Connected,Disconnected - Authentication state tracking per relay
- Automatic status updates based on WebSocket events
Message types
Client messages (outbound to relays):Req: Subscribe to events matching filtersEvent: Publish a new eventAuth: Authenticate with relay (NIP-42)Close: Close a subscription
Event: Received event matching subscriptionOK: Command acceptance/rejectionEOSE: End of stored eventsNotice: Relay notificationClosed: Subscription closed by relayAuth: Authentication challenge
Account management
TheAccountManager (src/account_manager.rs) handles cryptographic operations and key management.
Key features
- Multiple account support: Load and manage multiple Nostr keypairs
- Secure storage: Platform-specific key storage (Keychain on macOS, Credential Manager on Windows, Secret Service on Linux)
- Gift-wrap encryption: Automatic encryption/decryption of NIP-59 gift-wrapped messages
- Authentication: Create NIP-42 AUTH events for relay authentication
Gift-wrap handling
Gift wraps (NIP-59) provide privacy for direct messages:- Sending:
MailMessage::to_events()creates one gift-wrapped event per recipient - Receiving:
AccountManager::unwrap_gift_wrap()decrypts incoming gift wraps - Storage: Inner “rumor” events are stored; outer gift wraps are tracked for deletion
Database
The database layer (src/db/mod.rs) provides persistent storage with encryption.Schema design
Events table:- Stores raw Nostr events as JSON blobs
- Virtual columns extracted from JSON for efficient querying
- Indexes on
id,pubkey,kind, andcreated_at
- Caches Nostr metadata (kind 0) events
- Includes display name, avatar URL, NIP-05 identifier
- Updated only when newer metadata is received
- Maps outer gift wrap IDs to inner rumor IDs
- Tracks recipients for proper deletion handling
Key features
- Encryption: SQLCipher encryption with user-provided password
- Migrations: Schema versioning via
rusqlite_migration - Thread reconstruction: Recursive CTEs to build email-style threads
- Deletion tracking: Soft deletes with expiration for trash
UI system
The UI is built with egui’s immediate-mode paradigm (src/ui/).Page-based navigation
ThePage enum (src/types.rs:18) defines main views:
- Inbox, Drafts, Starred, Archived, Trash
- Requests (messages from non-contacts)
- Junk (spam folder)
- Contacts, Settings
- Onboarding flow
Component architecture
Main panels:- Left sidebar: Navigation and account selector
- Central panel: Content area (inbox, thread view, settings, etc.)
- Floating windows: Compose, add account
egui::Id.
Rendering pattern
Hoot follows egui’s immediate-mode pattern:update_app()(src/event_processing.rs:33): Process events, update staterender_app()(src/main.rs:274): Render UI based on current state- Every frame: Check relay messages, process image queue, update UI
Mail events
Hoot uses custom kind 2024 events for mail messages (src/mail_event.rs).MailMessage structure
Privacy features
- Gift-wrap encryption: All mail events are wrapped using NIP-59
- One event per recipient: Each recipient gets their own gift-wrapped copy
- BCC privacy: BCC recipients are not visible to other recipients
- Thread references: Parent event IDs enable email-style threading
Event flow
Receiving messages
- RelayPool receives WebSocket event from relay
try_recv_relay_message()(src/event_processing.rs:24) processes raw messageprocess_event()(src/event_processing.rs:283) handles parsed event:- Verify event signature
- Check for duplicates and deletions
- Unwrap gift wraps if recipient key available
- Store in database
- Update UI state (inbox, profile cache, etc.)
Sending messages
- User composes message in
ComposeWindow MailMessage::to_events()creates gift-wrapped events for each recipientRelayPool::send()broadcasts events to all connected relays- Relays return
OKmessage indicating acceptance/rejection
Profile metadata
get_profile_metadata()checks local cache- If not cached, returns
ProfileOption::Waitingand triggers relay request - Relay sends kind 0 (metadata) event
- Update
profile_metadataHashMap and database - Trigger UI repaint to show updated profile
Security considerations
- Database encryption: SQLCipher protects local data at rest
- Secure key storage: Platform keychain integration prevents key exposure
- Event verification: All events verified with Nostr signatures
- Gift-wrap privacy: NIP-59 ensures message content privacy
- Deletion handling: Proper scoping prevents unauthorized deletions
Performance optimizations
- Immediate-mode UI: egui’s immediate-mode design provides responsive rendering
- Wake-up callbacks: WebSocket connections trigger repaints only when needed
- Profile caching: Metadata cached in-memory and database to reduce relay requests
- Image loading: Background threads prevent UI blocking
- Virtual columns: SQLite generated columns enable fast queries without parsing JSON
Next steps
Application structure
Learn about the Hoot struct, event loop, and threading model
Database schema
Explore the database schema and query patterns