MailMessage structure
From src/mail_event.rs:8:- Email-like addressing: Separate
to,cc, andbccrecipient lists - Threading:
parent_eventsvector references previous messages in conversation - Subject line: Dedicated subject field for email-style organization
- NIP-05 identity: Optional verified identity for sender
- Flexible metadata: Optional
id,created_at, andauthorfor both creating and displaying messages
Event generation
Theto_events() method converts a MailMessage into gift-wrapped Nostr events:
Tag structure
The base kind 2024 event uses standard Nostr tags:Recipient tags
Threading tags
Subject tag
NIP-05 tag
Gift wrap process
Each message creates one gift-wrapped event per recipient:- Base event: Kind 2024 event with all tags and content
- Seal: Base event encrypted for recipient, signed by sender
- Gift wrap: Seal encrypted and wrapped by random ephemeral key
- One per recipient: Each recipient gets individually encrypted copy
HashMap<PublicKey, Event> maps each recipient to their gift-wrapped event.
BCC handling
BCC (blind carbon copy) recipients:- Not in tags: BCC pubkeys excluded from base event’s
ptags - Still wrapped: BCC recipients still get gift-wrapped copies
- Privacy preserved: Other recipients cannot see BCC recipients
Threading implementation
Threading uses event references:- Messages reference parent event IDs in
etags - Database queries walk these references recursively
- UI displays messages in chronological conversation order
- Multiple parent references support branching conversations
Event flow
Sending a message
- User composes message in
ComposeWindow - UI creates
MailMessagestruct with recipients and content to_events()generates one gift-wrapped event per recipient- Each wrapped event sent to all connected relays
- Relays distribute gift wraps to recipient’s relay lists
Receiving a message
- Relay sends gift wrap event (kind 1059) to client
AccountManager::unwrap_gift_wrap()decrypts the gift wrap- Inner rumor (kind 2024 event) extracted
- Database stores rumor with mapping to original gift wrap
- UI displays message in appropriate mailbox
Async handling
Gift wrap operations are async but called from sync context:pollster::block_on() to handle async Nostr crypto operations in synchronous event generation.
Privacy features
Kind 2024 + NIP-59 provides strong privacy:- Encrypted content: Message body encrypted in seal
- Encrypted metadata: Recipients, subject encrypted in seal
- Sender privacy: Gift wrap uses random ephemeral key
- Timing obfuscation: Gift wrap timestamps randomized (TODO)
- Individual encryption: Each recipient gets unique encrypted copy
Kind 2024 vs standard Nostr
Kind 2024 differs from standard Nostr events:- Not kind 1: Regular text notes visible to followers
- Not DMs: Old-style encrypted DMs (deprecated)
- Email-like: To/CC/BCC, subject lines, threading
- Always wrapped: Never sent unwrapped (unlike kind 1)
- Custom kind: Specific to Hoot and compatible clients
Integration with UI
From src/ui/compose_window.rs:223:- Collects message data from user input
- Creates
MailMessagestruct - Generates wrapped events with
to_events() - Sends each event to relay pool
- Relay pool broadcasts to all connected relays