diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 80499f7..0000000 --- a/CLAUDE.md +++ /dev/null @@ -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 diff --git a/MAIL_SERVER_SETUP.md b/MAIL_SERVER_SETUP.md deleted file mode 100644 index a517967..0000000 --- a/MAIL_SERVER_SETUP.md +++ /dev/null @@ -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 diff --git a/PROTON.md b/PROTON.md deleted file mode 100644 index 51a762f..0000000 --- a/PROTON.md +++ /dev/null @@ -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 diff --git a/USAGE.md b/README.md similarity index 64% rename from USAGE.md rename to README.md index fd3c5c3..52eae66 100644 --- a/USAGE.md +++ b/README.md @@ -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 ` | - -> Password is stored in the OS keychain. Use `--configure` to set or update it. diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 4bea7c5..0000000 --- a/docker-compose.yml +++ /dev/null @@ -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