updated docs
This commit is contained in:
parent
e2badaa170
commit
1eb4affdb6
5 changed files with 41 additions and 451 deletions
113
CLAUDE.md
113
CLAUDE.md
|
|
@ -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
|
||||
|
|
@ -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
|
||||
83
PROTON.md
83
PROTON.md
|
|
@ -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
|
||||
|
|
@ -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.
|
||||
|
||||
---
|
||||
### Configure tuimail
|
||||
Sorry, there is no binary release for now. This setup requires that you have rust cargo installed.
|
||||
|
||||
## Setup
|
||||
|
||||
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
|
||||
Run the setup wizard:
|
||||
|
||||
```bash
|
||||
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
|
||||
type a new one. Password prompts show `[stored]` when a value already exists
|
||||
in the keychain.
|
||||
When prompted for provider choose `imap/proton`, choose your provider:
|
||||
|
||||
### 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
|
||||
variables:
|
||||
For Proton
|
||||
- Username: your proton account user name
|
||||
- Password:
|
||||
|
||||
| Variable | Credential |
|
||||
|----------|------------|
|
||||
| `TUIMAIL_IMAP_PASSWORD` | IMAP password |
|
||||
| `TUIMAIL_SMTP_PASSWORD` | SMTP password |
|
||||
| `TUIMAIL_PROTON_PASSWORD` | ProtonMail login password |
|
||||
| `TUIMAIL_PROTON_MAILBOX_PASSWORD` | ProtonMail mailbox password (two-password mode) |
|
||||
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.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
TUIMAIL_IMAP_PASSWORD=hunter2 cargo run
|
||||
```
|
||||
Note: every time you recreate the binary file using cargo,
|
||||
Macos will need reapproval (4 times) for access to the keychain.
|
||||
|
||||
**Common provider settings**
|
||||
|
||||
|
|
@ -57,51 +39,33 @@ TUIMAIL_IMAP_PASSWORD=hunter2 cargo run
|
|||
|----------|-----------|-----------|---------|-----------|-----------|----------|
|
||||
| Gmail | imap.gmail.com | 993 | true | smtp.gmail.com | 465 | smtps |
|
||||
| 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
|
||||
|
||||
tuimail can talk to ProtonMail directly — no separate bridge process needed.
|
||||
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
|
||||
```
|
||||
|
||||
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.
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||
```
|
||||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
Loading…
Add table
Reference in a new issue