updated docs

This commit is contained in:
Shautvast 2026-03-02 15:25:52 +01:00
parent e2badaa170
commit 1eb4affdb6
5 changed files with 41 additions and 451 deletions

113
CLAUDE.md
View file

@ -1,113 +0,0 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
**tui_mail** is a TUI email client built with Rust and Ratatui. It supports standard IMAP servers (including Gmail) and ProtonMail (via an in-process bridge). It displays inbox messages with a split-pane interface: email list on top, message preview on the bottom.
**User documentation:** see [`USAGE.md`](USAGE.md) for setup instructions, keyboard shortcuts, and configuration reference.
## Build and Run Commands
```bash
# Build the project (standard IMAP)
cargo build
# Run the application
cargo run
# Build/run with ProtonMail support
cargo build --features proton
cargo run --features proton
# Build optimized release version
cargo build --release
# Check code without building
cargo check
# Format code
cargo fmt
# Run clippy linter
cargo clippy
```
## Test Mail Server
A Docker-based IMAP mail server is available for testing:
```bash
# Start the mail server
docker-compose up -d
# Create a test user
docker exec -it mailserver setup email add test@example.com password123
# Stop the mail server
docker-compose down
```
Connection details: localhost:143 (IMAP) or localhost:993 (IMAPS). See `MAIL_SERVER_SETUP.md` for detailed usage including Gmail configuration.
## Architecture
### Main app (`src/`)
- **`src/main.rs`** — Terminal setup/teardown, delegates to `lib::main`
- **`src/lib.rs`** — Main event loop, UI rendering, worker thread coordination
- **`src/inbox.rs`** — IMAP inbox operations (refresh, fetch older, fetch body, search, delete)
- **`src/connect.rs`** — IMAP connection handling (plain TCP and TLS)
- **`src/config.rs`** — Configuration loading from `config.toml`; provider selection (IMAP vs Proton)
- **`src/credentials.rs`** — OS keychain access via the `keyring` crate
- **`src/smtp.rs`** — Outgoing mail via SMTP using `lettre`
- **`src/setup.rs`** — First-run interactive setup wizard
- **`src/store.rs`** — Local encrypted body cache (AES-256-GCM, key stored in `storage.key` file)
### ProtonMail bridge (`proton-bridge/`)
A separate workspace crate that runs as an in-process local IMAP/SMTP server.
- **`proton-bridge/src/lib.rs`** — Entry point (`start()`); authenticates with ProtonMail API, binds local ports, spawns async tasks
- **`proton-bridge/src/imap_server.rs`** — Local IMAP server (LOGIN, SELECT, FETCH, SEARCH, STORE, EXPUNGE)
- **`proton-bridge/src/smtp_server.rs`** — Local SMTP server for outgoing mail
- **`proton-bridge/src/api.rs`** — ProtonMail REST API client (message list, fetch, delete, send)
- **`proton-bridge/src/auth.rs`** — SRP authentication and session management
- **`proton-bridge/src/crypto.rs`** — PGP key derivation and message decryption/encryption
- **`proton-bridge/src/store.rs`** — In-memory message metadata store for the bridge
- **`proton-bridge/src/srp.rs`** — SRP-6a implementation matching ProtonMail's go-srp
### Key patterns
- IMAP operations run in a **background worker thread** communicating via `mpsc` channels, keeping the UI responsive
- Emails are loaded in **batches of 50**, with lazy loading when scrolling past the end
- A **navigation debounce** (150 ms) avoids firing body fetches on every keypress while scrolling
- The worker checks `wanted_body_seq` (an `AtomicU32`) to drop stale body requests
- **Body cache**: fetched message bodies are AES-256-GCM encrypted and written to `{data_dir}/tuimail/bodies/{hash}.enc`; cache key is the `Message-ID` header value
- Credentials (IMAP/SMTP/Proton passwords) are stored in the **OS keychain** via `keyring`
- The encryption key for the body cache is stored in `{data_dir}/tuimail/storage.key` (mode 0600) to avoid repeated keychain prompts
- **Tab** switches focus between inbox list and preview pane
- Selection is preserved across refreshes by matching IMAP sequence numbers
## Key Dependencies
- **ratatui (0.30)**: TUI framework providing widgets, layouts, and rendering
- **crossterm (0.29)**: Cross-platform terminal manipulation (raw mode, events, alternate screen)
- **imap (2.4)**: IMAP protocol client
- **native-tls (0.2)**: TLS support for secure IMAP connections
- **lettre (0.11)**: SMTP client for sending mail
- **chrono (0.4)**: Date parsing and timezone conversion
- **mailparse (0.15)**: MIME email paursing for body extraction
- **aes-gcm (0.10)**: AES-256-GCM authenticated encryption for the body cache
- **keyring (3)**: OS keychain access (apple-native / linux-native / windows-native)
- **dirs (5)**: Platform-correct data directory paths
## Development Notes
- Uses Rust edition 2024
- Terminal is set to raw mode to capture individual key presses
- The alternate screen prevents terminal history pollution
- `config.toml` contains credentials and is gitignored — see `config.toml.example` for the format
- `proton-bridge/bridge.toml` contains ProtonMail credentials and is gitignored
- The ProtonMail bridge imap_server SELECT response must **not** include `UIDVALIDITY`/`UIDNEXT` lines — imap-proto 0.10 (used by imap 2.4) does not fully consume them, causing tag desync on subsequent commands

View file

@ -1,124 +0,0 @@
# Mail Server Setup
## Gmail Configuration
### 1. Enable 2-Step Verification
App Passwords require 2-Step Verification to be enabled on your Google account.
1. Go to https://myaccount.google.com/security
2. Under "How you sign in to Google", click **2-Step Verification**
3. Follow the prompts to enable it
### 2. Create an App Password
1. Go to https://myaccount.google.com/apppasswords
2. Enter a name (e.g. "Mail TUI") and click **Create**
3. Google will display a 16-character password — copy it
### 3. Configure tuimail
Run the setup wizard:
```bash
cargo run -- --configure
```
When prompted for provider choose `imap`, then enter:
- IMAP host: `imap.gmail.com`, port: `993`, TLS: `true`
- Username: your Gmail address
- Password: the 16-character App Password from step 2 (spaces are optional)
- SMTP host: `smtp.gmail.com`, port: `465`, TLS mode: `smtps`
Passwords are stored securely in the OS keychain — they are never written to `config.toml`.
## Local Test Server (Docker)
### Quick Start
1. **Start the mail server:**
```bash
docker-compose up -d
```
2. **Create a test user:**
```bash
docker exec -it mailserver setup email add test@example.com password123
```
3. **Verify the server is running:**
```bash
docker-compose ps
```
### Configure tuimail for the local server
```bash
cargo run -- --configure
```
Choose provider `imap` and enter:
- IMAP host: `localhost`, port: `143`, TLS: `false`
- Username: `test@example.com`, password: `password123`
- SMTP host: `localhost`, port: `25`, TLS mode: `none`
### IMAP Connection Details
- **Host:** localhost
- **IMAP Port:** 143 (unencrypted) or 993 (SSL/TLS)
- **Username:** test@example.com
- **Password:** password123
### Useful Commands
```bash
# Stop the mail server
docker-compose down
# View logs
docker-compose logs -f mailserver
# List all email accounts
docker exec -it mailserver setup email list
# Add another user
docker exec -it mailserver setup email add user2@example.com pass456
# Delete a user
docker exec -it mailserver setup email del test@example.com
# Access the container shell
docker exec -it mailserver bash
```
### Testing with telnet
You can test IMAP connectivity:
```bash
telnet localhost 143
```
Then try IMAP commands:
```
a1 LOGIN test@example.com password123
a2 LIST "" "*"
a3 SELECT INBOX
a4 LOGOUT
```
### Send Test Email
```bash
# From within the container
docker exec -it mailserver bash
echo "Test email body" | mail -s "Test Subject" test@example.com
```
Or use SMTP (port 25/587) from your application.
## Troubleshooting
- Gmail: if login fails, verify that 2-Step Verification is enabled and you're using an App Password (not your regular password)
- Docker: check logs with `docker-compose logs mailserver`
- Ensure ports aren't already in use
- Data persists in `./docker-data/` directory

View file

@ -1,83 +0,0 @@
# ProtonMail Mini-Bridge
A minimal ProtonMail Bridge implementation in Rust that exposes local IMAP and SMTP servers,
allowing `skim` (and any other standard email client) to connect without code changes.
The full ProtonMail Bridge is ~50,000 lines of Go. This mini-bridge targets only the subset
of functionality that `skim` needs: one account, INBOX only, one concurrent client.
## Components
### 1. Project scaffold
New binary crate with `Cargo.toml` dependencies (`tokio`, `reqwest`, `proton-srp`, `rpgp`,
`serde`, `toml`). Config file format covering ProtonMail credentials and local bind ports.
### 2. ProtonMail authentication
SRP 6a login flow against the Proton API:
- POST `/auth/info` with username → receive modulus, server ephemeral, salt
- Compute SRP proof locally using `proton-srp`
- POST `/auth/login` with client proof → receive access token + encrypted private keys
- Handle optional TOTP 2FA interactively on first run
- Persist session (access token + refresh token) to disk to avoid re-authenticating on restart
### 3. ProtonMail API client
Thin `reqwest`-based HTTP wrapper around the endpoints the bridge needs:
- List messages (with pagination)
- Fetch single message (metadata + encrypted body)
- Delete message
- Fetch recipient public key (for outbound encryption)
- Send message (modelled from the open-source `ProtonMail/proton-bridge` Go implementation)
### 4. Crypto layer
Using `rpgp` or `proton-crypto-rs`:
- Decrypt the user's private key (delivered encrypted by the API, unlocked with the mailbox password)
- Decrypt incoming message bodies with the private key
- Encrypt outbound messages to recipient public keys
### 5. Message store
In-memory (optionally persisted) mapping between IMAP sequence numbers and Proton message IDs.
IMAP uses stable sequential integers; Proton uses opaque string IDs. The store must:
- Assign sequence numbers to messages in order
- Renumber correctly after deletes
- Survive restarts without breaking existing client state
### 6. IMAP server
TCP listener on `localhost:143` (configurable). Implements the nine commands `skim` uses:
| Command | Purpose |
|---------|---------|
| `LOGIN` | Accept local credentials (no real auth needed) |
| `NOOP` | Keepalive / connection check |
| `SELECT INBOX` | Open mailbox, report message count |
| `FETCH range BODY.PEEK[HEADER.FIELDS (SUBJECT FROM DATE)]` | List emails |
| `FETCH seq BODY.PEEK[]` | Fetch full message body |
| `SEARCH OR SUBJECT "..." FROM "..."` | Search by subject or sender |
| `STORE seq +FLAGS (\Deleted)` | Mark for deletion |
| `EXPUNGE` | Delete marked messages |
| `LOGOUT` | Disconnect |
Each command translates to API client + crypto layer calls via the message store.
### 7. SMTP server
TCP listener on `localhost:587` (configurable). Minimal implementation:
- EHLO, AUTH, MAIL FROM, RCPT TO, DATA, QUIT
- On DATA completion: hand the message to the crypto layer to encrypt, then POST via API client
## Build order
Components 2 → 3 → 4 can be built and tested with a simple CLI harness before any
network server exists. Component 5 is pure logic with no I/O. Components 6 and 7 are
the final pieces and can be validated by pointing `tuimail` at localhost.
## References
- [ProtonMail/proton-bridge](https://github.com/ProtonMail/proton-bridge) — official Bridge (Go, open source)
- [ProtonMail/go-proton-api](https://github.com/ProtonMail/go-proton-api) — official Go API client
- [ProtonMail/proton-crypto-rs](https://github.com/ProtonMail/proton-crypto-rs) — official Rust crypto crates
- [ProtonMail/proton-srp](https://github.com/ProtonMail/proton-srp) — official Rust SRP implementation
- [rpgp](https://github.com/rpgp/rpgp) — pure Rust OpenPGP implementation
More work:
- local storge in the proton-bridge. Decide whether to store the message body pgp-encrypted, as it came from the proton server, or
use decrypt it (and serve to the client using imap) and create a AES-256-GCM encrypted cache
- email replies in the tui client have not yet been implemented

View file

@ -4,52 +4,34 @@ tuimail is a terminal email client. It shows your inbox in a split-pane view:
the email list on top, the message preview on the bottom. the email list on top, the message preview on the bottom.
--- ---
### Configure tuimail
Sorry, there is no binary release for now. This setup requires that you have rust cargo installed.
## Setup Run the setup wizard:
tuimail stores passwords securely in the **OS keychain** (macOS Keychain,
GNOME Keyring, KWallet, Windows Credential Manager). No passwords are ever
written to disk in plain text.
### First-time setup
Simply run tuimail — if no config file exists it launches an interactive
wizard automatically:
```bash
cargo run
```
The wizard prompts for your provider, server settings, and passwords, then
saves the config file and stores all passwords in the OS keychain.
### Re-configure / update credentials
```bash ```bash
cargo run -- --configure cargo run -- --configure
``` ```
(the first time --configure is active by default)
All prompts show current values in brackets. Press Enter to keep a value, or When prompted for provider choose `imap/proton`, choose your provider:
type a new one. Password prompts show `[stored]` when a value already exists
in the keychain.
### Headless / CI environments (env-var fallback) For Imap:
- IMAP host: `imap.gmail.com`, port: `993`, TLS: `true`
- Username
- Password
- SMTP host: `smtp.gmail.com`, port: `465`, TLS mode: `smtps`
If the OS keychain is unavailable, export the passwords as environment For Proton
variables: - Username: your proton account user name
- Password:
| Variable | Credential | Tuimail stores passwords securely in the **OS keychain** (macOS Keychain,
|----------|------------| GNOME Keyring, KWallet, Windows Credential Manager). No passwords are ever
| `TUIMAIL_IMAP_PASSWORD` | IMAP password | written to disk in plain text.
| `TUIMAIL_SMTP_PASSWORD` | SMTP password |
| `TUIMAIL_PROTON_PASSWORD` | ProtonMail login password |
| `TUIMAIL_PROTON_MAILBOX_PASSWORD` | ProtonMail mailbox password (two-password mode) |
Example: Note: every time you recreate the binary file using cargo,
Macos will need reapproval (4 times) for access to the keychain.
```bash
TUIMAIL_IMAP_PASSWORD=hunter2 cargo run
```
**Common provider settings** **Common provider settings**
@ -57,51 +39,33 @@ TUIMAIL_IMAP_PASSWORD=hunter2 cargo run
|----------|-----------|-----------|---------|-----------|-----------|----------| |----------|-----------|-----------|---------|-----------|-----------|----------|
| Gmail | imap.gmail.com | 993 | true | smtp.gmail.com | 465 | smtps | | Gmail | imap.gmail.com | 993 | true | smtp.gmail.com | 465 | smtps |
| Outlook/Hotmail | outlook.office365.com | 993 | true | smtp.office365.com | 587 | starttls | | Outlook/Hotmail | outlook.office365.com | 993 | true | smtp.office365.com | 587 | starttls |
| ProtonMail | see ProtonMail section below ||||| |
| Local test server | localhost | 143 | false | localhost | 25 | none |
> **Gmail note:** You must use an [App Password](https://myaccount.google.com/apppasswords),
> not your regular password. Enable 2-Step Verification first, then generate an
> App Password for "Mail".
### ProtonMail ### ProtonMail
Tuimail can talk to ProtonMail directly — no separate bridge process needed!
tuimail can talk to ProtonMail directly — no separate bridge process needed. All transport is secure, except on localhost between client and built-in bridge service. This will be addressed soon.
The bridge starts automatically in-process when `provider = "proton"` is set.
**1. Build with ProtonMail support:**
```bash
cargo build --features proton
```
**2. Run the setup wizard:**
```bash
cargo run --features proton -- --configure
```
The wizard prompts for your ProtonMail username and password (stored in
keychain), two-password mode, and bridge ports. The bridge local password is
auto-generated and stored in the keychain.
**3. Run:**
```bash
cargo run --features proton
```
The bridge authenticates with ProtonMail before the TUI opens. Messages are
decrypted on the fly; sent mail is encrypted end-to-end automatically.
Then run (standard providers):
```bash
cargo run --release
```
--- ---
## Local cache:
Messages are safely stored locally using AES-256-GCM.
## Extra Configuration needed for Gmail
### 1. Enable 2-Step Verification
App Passwords require 2-Step Verification to be enabled on your Google account.
1. Go to https://myaccount.google.com/security
2. Under "How you sign in to Google", click **2-Step Verification**
3. Follow the prompts to enable it
### 2. Create an App Password
1. Go to https://myaccount.google.com/apppasswords
2. Enter a name (e.g. "Mail TUI") and click **Create**
3. Google will display a 16-character password — copy it
## Interface ## Interface
``` ```
@ -223,27 +187,3 @@ progress. Your current selection is preserved across refreshes.
--- ---
## Configuration Reference
### `[imap]`
| Key | Type | Description |
|-----|------|-------------|
| `host` | string | IMAP server hostname |
| `port` | integer | IMAP port (usually 993 with TLS, 143 without) |
| `username` | string | Login username (usually your full email address) |
| `use_tls` | bool | `true` for IMAPS (port 993), `false` for plain/STARTTLS |
> Password is stored in the OS keychain. Use `--configure` to set or update it.
### `[smtp]`
| Key | Type | Description |
|-----|------|-------------|
| `host` | string | SMTP server hostname |
| `port` | integer | SMTP port |
| `username` | string | Login username |
| `tls_mode` | string | `none`, `starttls`, or `smtps` |
| `from` | string | Sender address shown to recipients, e.g. `Name <addr>` |
> Password is stored in the OS keychain. Use `--configure` to set or update it.

View file

@ -1,30 +0,0 @@
version: '3.8'
services:
mailserver:
image: mailserver/docker-mailserver:latest
container_name: mailserver
hostname: mail.example.com
ports:
- "25:25" # SMTP
- "143:143" # IMAP
- "587:587" # SMTP Submission
- "993:993" # IMAPS
volumes:
- ./docker-data/mail-data:/var/mail
- ./docker-data/mail-state:/var/mail-state
- ./docker-data/mail-logs:/var/log/mail
- ./docker-data/config:/tmp/docker-mailserver
- /etc/localtime:/etc/localtime:ro
environment:
- ENABLE_SPAMASSASSIN=0
- ENABLE_CLAMAV=0
- ENABLE_FAIL2BAN=0
- ENABLE_POSTGREY=0
- ONE_DIR=1
- DMS_DEBUG=0
- PERMIT_DOCKER=network
- SSL_TYPE=
cap_add:
- NET_ADMIN
restart: unless-stopped