The key insight from go-proton-api SaltForKey: ProtonMail uses only the
last 31 chars of the bcrypt output as the key passphrase — not the full
60-char string. One line fix, two days of debugging.
Also adds the full crypto layer (crypto.rs): user key unlock, address key
token decryption, and message body decryption via rpgp. Includes SRP auth,
session caching with locked-scope handling, TOTP, and the ProtonMail API
client for inbox listing and message fetch.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements typed async wrappers for the four endpoints tuimail needs:
- list_messages: GET /mail/v4/messages (paged inbox listing)
- get_message: GET /mail/v4/messages/{id} (full message with encrypted body)
- delete_messages: PUT /mail/v4/messages/delete (soft-delete to Trash)
- get_public_keys: GET /core/v4/keys (recipient keys for outbound mail)
All responses decoded through Envelope<T> with Code 1000 check.
main.rs smoke-tests the inbox listing after authentication.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Store expires_at (Unix timestamp) in session.json from ExpiresIn response field
- Add is_expired() with 5-minute refresh margin
- Implement POST /auth/v4/refresh flow: tries refresh before falling back to SRP login
- authenticate() now: use cached → refresh if expired → full login if all else fails
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Convert tuimail repo to Cargo workspace with tuimail and proton-bridge members
- Add proton-bridge binary crate with config, SRP 6a, and auth modules
- Implement ProtonMail SRP 6a exactly matching go-srp:
- Little-endian bigints throughout
- expandHash = SHA512(data||0..3) producing 256 bytes
- k, u, M1, M2 all via expandHash with 256-byte normalised inputs
- Password hashing v3/v4: bcrypt($2y$, salt+proton) + expandHash(output||N)
- Authenticate against Proton API (auth/info → auth/v4), verify server proof
- Persist session (UID, access/refresh tokens) to session.json
- Add bridge.toml and session.json to .gitignore (contain credentials/tokens)
- Add PROTON.md with full build plan for the mini-bridge
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add smtp.rs with send_email using lettre; supports none/starttls/smtps
- Replace use_tls: bool with TlsMode enum in SmtpConfig for explicit port 465 (SMTPS) support
- Add SMTP_IO_TIMEOUT (15s) for socket I/O and SMTP_WALL_TIMEOUT (30s) covering DNS + connect
- Spawn SMTP send on a dedicated thread so the IMAP worker thread is never blocked
- Update config.toml.example with tls_mode documentation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Press / to enter search mode; status bar shows query input
- IMAP SEARCH OR SUBJECT/FROM sent in background worker thread
- Results replace inbox list with match count in title
- Navigation and body preview work the same as in regular inbox
- Esc clears search and returns to normal inbox view
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Compute the max sender width across the loaded emails (capped at 40
chars) and pad each sender field to that width. Long senders are
truncated with an ellipsis. Subject column now starts at a consistent
position regardless of sender name length.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Refresh regression:
- refresh() only loads the latest 50 emails, so if the user scrolled
further via FetchMore their selected email was not in the new list and
selection fell back to index 0. Now preserve emails older than the
refresh batch (previously fetched via FetchMore) by merging them back.
- Also cancel pending debounce on refresh so stale pending fetches can't
overwrite the correct body after selection changes.
- Up-arrow now uses debounce consistently with Down-arrow.
Delete off-by-one:
- IMAP expunge renumbers all messages with seq > deleted seq. The app
was still holding pre-delete sequence numbers, so the next FetchBody
after a delete would retrieve the wrong message. After removing an
email, decrement the seq of every remaining email with seq > deleted.
Cleanup: remove now-unused Inbox.oldest_seq and Inbox.has_older() since
oldest_seq/has_older are now computed from the merged emails list.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three improvements:
- Add 15s connect/read/write timeouts to TcpStream so a hung IMAP
server can no longer block the worker thread indefinitely
- Cache tui_markdown rendering: convert Text<'a> to Text<'static> on
first render and reuse across frames, re-parsing only when the body
actually changes
- Debounce FetchBody requests on keyboard navigation: wait 150ms of
inactivity before sending, so rapid scrolling doesn't flood the
worker with stale requests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Strip pipe characters from HTML-to-markdown table remnants in email body
- Add bold + arrow prefix (▶) to focused pane title for clear focus indication
- Rename Focus::Preview and all preview_* variables to Message/message_*
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Strip images, simplify links to just text, remove very long bare URLs,
and collapse excessive blank lines for a cleaner preview pane.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
HTML emails are now converted to markdown then rendered with rich formatting
(bold, italic, headings, links) in the preview pane via tui-markdown.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Ensure disable_raw_mode and LeaveAlternateScreen run even when the
app returns an error. Also add a panic hook to restore the terminal
on unexpected panics.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Uses Arc<AtomicU32> to track the most recently wanted email sequence
number. The worker skips fetch requests that no longer match, avoiding
wasted network calls when scrolling through emails rapidly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The eager connect() call in the worker thread could hang if the server
was unreachable, preventing the worker from ever processing commands.
Let refresh() handle the initial connection instead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Press 'd' to delete the selected email. Removes it from the list
immediately and performs the server-side delete in the background
for a snappy user experience.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a second-pass QP decode to catch =XX sequences that survive
the initial mailparse decoding.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fall back to html2text when no text/plain part is available,
converting HTML emails to readable terminal output.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fetch full raw email and parse MIME structure to find the
text/plain part, removing MIME headers and boundaries from
the preview pane.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After refresh, find the previously selected email by its IMAP
sequence number and keep the highlight on it. Falls back to the
first email if the selected one was deleted.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tab toggles focus between inbox list and preview. Focused pane
gets a cyan border. When preview is focused, up/down scrolls the
email body instead of navigating the list.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Spawn a worker thread that owns the IMAP session and processes
fetch commands via channels. The UI thread polls for results
non-blockingly, keeping the app responsive during network operations.
Shows loading indicator while fetching.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduce Inbox struct to track the oldest fetched sequence number.
When scrolling past the last email, automatically fetch the next
batch of 50 older messages and append them to the list.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Support both plain and TLS IMAP connections via an ImapSession enum,
enabling use with Gmail and other TLS-only servers. Limit fetch range
to the most recent 50 messages to avoid hanging on large mailboxes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace simple IMAP login with full inbox fetch that displays
Subject, From, and Date for each message. Auto-refreshes every
30 seconds and supports manual refresh with 'r' key.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>