diff --git a/README.md b/README.md index 30c2768..6d9223b 100644 --- a/README.md +++ b/README.md @@ -5,138 +5,142 @@

RustMail

- A fast, feature-rich SMTP mail catcher built in Rust.
- Single binary. Persistent storage. Modern UI. CI-ready. + A fast, self-hosted SMTP mail catcher for development and testing.
+ Capture outbound email, inspect it in a modern UI, and assert on it in CI.

CI + Docker Pulls + Latest Release License

- ~7 MB binary · 28 KB JS gzipped · zero runtime dependencies + Documentation · API Reference · Docker Hub

- - - - - - - - - -
Web UITUI
RustMail Web UIRustMail TUI
-

- Inspired by MailHog and MailCrab + RustMail Web UI

-## Install +## Quick Start ```sh -brew install rustmailapp/rustmail/rustmail +docker run -p 1025:1025 -p 8025:8025 smyile/rustmail:latest ``` -Or on Arch Linux (AUR): +Point your app's SMTP at `localhost:1025`, then open [localhost:8025](http://localhost:8025). Emails appear in real time. -```sh -yay -S rustmail-bin +### Docker Compose + +```yaml +services: + rustmail: + image: smyile/rustmail:latest + ports: + - "1025:1025" + - "8025:8025" + volumes: + - rustmail-data:/data + restart: unless-stopped + +volumes: + rustmail-data: ``` -Or with Docker: +## Install + +### Homebrew ```sh -docker run -p 1025:1025 -p 8025:8025 smyile/rustmail:latest +brew install rustmailapp/rustmail/rustmail ``` -Or from source: +### Arch Linux (AUR) ```sh -git clone https://github.com/rustmailapp/rustmail -cd rustmail && make build +yay -S rustmail-bin ``` -## Quick Start +### From Source ```sh -rustmail +git clone https://github.com/rustmailapp/rustmail +cd rustmail && make build ``` -Point your app's SMTP at `localhost:1025`, then open [localhost:8025](http://localhost:8025). Emails show up in real time. +### Pre-built Binaries -## Features +Download from [GitHub Releases](https://github.com/rustmailapp/rustmail/releases/latest) — Linux (x86_64, aarch64, armv7 — glibc + musl), macOS (Intel + Apple Silicon), and multi-arch Docker images. -### Core +## Features -- **Persistent storage** — SQLite-backed, emails survive restarts (or `--ephemeral` for CI) -- **Full-text search** — FTS5 across subject, body, sender, and recipients -- **Real-time updates** — WebSocket pushes new emails to the UI instantly -- **Modern UI** — dark-mode-first, looks and feels like a real email client +| | | +|---|---| +| **Persistent storage** | SQLite-backed, emails survive restarts. `--ephemeral` for CI. | +| **Full-text search** | FTS5 across subject, body, sender, and recipients. | +| **Real-time UI** | WebSocket push — new email appears instantly. Dark/light mode, keyboard shortcuts. | +| **CI-native** | REST assertion endpoints, CLI assert mode, and a first-party GitHub Action. | +| **Single binary** | Frontend embedded at compile time. ~7 MB, zero runtime dependencies. | +| **Auth header display** | Parses DKIM, SPF, DMARC, and ARC headers with color-coded status badges. | +| **Webhooks** | Fire-and-forget POST on every new message. | +| **Email release** | Forward captured emails to a real SMTP server. | +| **Export** | Download any email as EML or JSON. | +| **Retention policies** | Auto-purge by age (`--retention`) or count (`--max-messages`). | +| **TUI** | Optional terminal UI client for Neovim and headless workflows. | -### CI/CD +## GitHub Action -- **REST assertion endpoints** — `GET /api/v1/assert/count?min=1&subject=Welcome` -- **CLI assert mode** — `rustmail assert --min-count=2 --subject="Welcome" --timeout=30s` -- **GitHub Action** — two-step start + assert, works on Linux and macOS runners -- **Ephemeral mode** — in-memory DB for test pipelines, no cleanup needed +```yaml +- uses: rustmailapp/rustmail-action@v1 -### Advanced +- run: npm test # your app sends emails to localhost:1025 -- **DKIM/SPF/DMARC/ARC display** — parses authentication headers, color-coded status badges (no other local catcher does this) -- **Webhook notifications** — fire-and-forget POST on new email via `--webhook-url` -- **Email release** — forward captured emails to a real SMTP server -- **Export** — download as EML or JSON +- uses: rustmailapp/rustmail-action@v1 + with: + mode: assert + assert-count: 1 + assert-subject: "Welcome" +``` -### Platform Support +## CLI Assert Mode -Pre-built binaries and multi-arch Docker images for every major platform: +Run as an ephemeral mail catcher that exits with a status code — for CI without the GitHub Action: -| Platform | Architectures | Format | -|----------|--------------|--------| -| **Linux** | x86_64, aarch64, armv7 | gnu + musl (static) | -| **macOS** | x86_64 (Intel), aarch64 (Apple Silicon) | native | -| **Docker** | amd64, arm64, arm/v7 | multi-arch manifest | +```sh +rustmail assert --min-count=2 --subject="Welcome" --timeout=30s +``` -Static musl builds run on Alpine, distroless, and any Linux — no glibc required. +## Configuration -### Developer Experience +Configure via CLI flags, `RUSTMAIL_*` environment variables, or a TOML config file: -- **Keyboard shortcuts** — `j`/`k` navigate, `d` delete, `D` clear all, `/` search, `Esc` close -- **Dark/light theme** — toggle with localStorage persistence -- **Flexible config** — CLI flags, `RUSTMAIL_*` env vars, or TOML config file -- **Retention policies** — `--retention` (hours) and `--max-messages` with automatic enforcement +```sh +rustmail serve --bind 0.0.0.0 --smtp-port 2525 --http-port 9025 +rustmail serve --retention 24 --max-messages 1000 +rustmail serve --webhook-url https://hooks.example.com/email +``` -## GitHub Action +See the full [configuration reference](https://docs.rustmail.app/configuration/cli-flags). -```yaml -- name: Start RustMail - uses: rustmailapp/rustmail-action@v1 +## TUI -- name: Run tests (your app sends emails to localhost:1025) - run: npm test +

+ RustMail TUI +

-- name: Assert emails were sent - uses: rustmailapp/rustmail-action@v1 - with: - mode: assert - assert-count: 1 - assert-subject: "Welcome" -``` +A terminal UI that connects to a running RustMail instance. Also available as a [Neovim plugin](https://docs.rustmail.app/integrations/neovim). ## Documentation -Full docs at [docs.rustmail.app](https://docs.rustmail.app) ([source](docs/)): +Full docs at [docs.rustmail.app](https://docs.rustmail.app): -- [Getting Started](docs/getting-started/introduction.md) -- [Configuration](docs/configuration/cli-flags.md) -- [CI Integration](docs/ci-integration/rest-assertions.md) -- [Features](docs/features/webhooks.md) — Webhooks, Export, Release, Email Auth, WebSocket -- [Integrations](docs/integrations/neovim.md) — Neovim Plugin -- [API Reference](docs/api/index.md) -- [Docker](docs/getting-started/docker.md) -- [Architecture](docs/getting-started/architecture.md) +- [Getting Started](https://docs.rustmail.app/getting-started/introduction) +- [Docker](https://docs.rustmail.app/getting-started/docker) +- [Configuration](https://docs.rustmail.app/configuration/cli-flags) +- [CI Integration](https://docs.rustmail.app/ci-integration/rest-assertions) +- [API Reference](https://docs.rustmail.app/api/) ## License diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000..e25959f --- /dev/null +++ b/docker/README.md @@ -0,0 +1,102 @@ +# RustMail + +A fast, feature-rich SMTP mail catcher built in Rust. Single binary. Persistent storage. Modern UI. CI-ready. + +**GitHub:** [rustmailapp/rustmail](https://github.com/rustmailapp/rustmail) · **Docs:** [docs.rustmail.app](https://docs.rustmail.app) + +## Quick Start + +```sh +docker run -p 1025:1025 -p 8025:8025 smyile/rustmail:latest +``` + +Point your app's SMTP at `localhost:1025`, then open [localhost:8025](http://localhost:8025). Emails show up in real time. + +## Docker Compose + +```yaml +services: + rustmail: + image: smyile/rustmail:latest + ports: + - "1025:1025" + - "8025:8025" + security_opt: + - no-new-privileges:true + volumes: + - rustmail-data:/data + restart: unless-stopped + +volumes: + rustmail-data: +``` + +## Supported Architectures + +| Architecture | Tag | +|---|---| +| `linux/amd64` | `latest` | +| `linux/arm64` | `latest` | +| `linux/arm/v7` | `latest` | + +Multi-arch manifest — Docker automatically pulls the correct image for your platform. + +## Persistence + +The image stores emails at `/data/rustmail.db` by default. Mount a volume to `/data` to persist emails across container restarts. + +For ephemeral (CI) usage, skip the volume: + +```sh +docker run -p 1025:1025 -p 8025:8025 -e RUSTMAIL_EPHEMERAL=true smyile/rustmail:latest +``` + +## Environment Variables + +All configuration is done via `RUSTMAIL_*` environment variables: + +| Variable | Default | Description | +|---|---|---| +| `RUSTMAIL_BIND` | `0.0.0.0` | IP address to bind listeners to | +| `RUSTMAIL_SMTP_PORT` | `1025` | SMTP listener port | +| `RUSTMAIL_HTTP_PORT` | `8025` | HTTP and WebSocket port | +| `RUSTMAIL_DB_PATH` | `/data/rustmail.db` | Path to SQLite database file | +| `RUSTMAIL_RETENTION` | `0` | Auto-delete messages after N hours (`0` = keep forever) | +| `RUSTMAIL_MAX_MESSAGES` | `0` | Max messages to retain (`0` = unlimited) | +| `RUSTMAIL_MAX_MESSAGE_SIZE` | `10485760` | Max accepted message size in bytes (10 MB) | +| `RUSTMAIL_EPHEMERAL` | `false` | Use in-memory SQLite (no data written to disk) | +| `RUSTMAIL_WEBHOOK_URL` | — | URL to POST on every new message | +| `RUSTMAIL_LOG_LEVEL` | `info` | Log verbosity: `trace`, `debug`, `info`, `warn`, `error` | +| `RUSTMAIL_RELEASE_HOST` | — | Allowed SMTP target for email release (`host:port`) | + +## Ports + +| Port | Protocol | Description | +|---|---|---| +| `1025` | TCP | SMTP server | +| `8025` | TCP | HTTP API, WebSocket, and Web UI | + +## Features + +- **Persistent storage** — SQLite-backed, emails survive restarts +- **Full-text search** — FTS5 across subject, body, sender, and recipients +- **Real-time updates** — WebSocket pushes new emails to the UI instantly +- **Modern UI** — dark-mode-first, looks and feels like a real email client +- **DKIM/SPF/DMARC/ARC display** — parses authentication headers with color-coded badges +- **REST assertion endpoints** — `GET /api/v1/assert/count?min=1&subject=Welcome` +- **Webhook notifications** — fire-and-forget POST on new email +- **Email release** — forward captured emails to a real SMTP server +- **Export** — download as EML or JSON +- **Retention policies** — auto-purge by age or count + +## Image Details + +- **Base image:** `alpine:3.21` +- **Runs as:** non-root user `rustmail` +- **Healthcheck:** built-in (HTTP check every 30s) +- **Volume:** `/data` +- **Security:** `no-new-privileges` recommended + +## License + +Licensed under either of [MIT](https://github.com/rustmailapp/rustmail/blob/master/LICENSE-MIT) or [Apache 2.0](https://github.com/rustmailapp/rustmail/blob/master/LICENSE-APACHE), at your option. diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 05a6ac2..3901fe0 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -60,6 +60,7 @@ const reference: DefaultTheme.SidebarItem[] = [ { text: "WebSocket", link: "/features/websocket" }, { text: "Keyboard Shortcuts", link: "/features/keyboard-shortcuts" }, { text: "Email Authentication", link: "/features/email-auth" }, + { text: "Terminal UI", link: "/features/tui" }, ], }, { @@ -151,7 +152,7 @@ export default defineConfig({ footer: { message: "Released under the MIT / Apache 2.0 License.", - copyright: "Copyright © 2026 Davide Tacchini", + copyright: "Copyright © 2026 Smyile", }, }, }); diff --git a/docs/ci-integration/github-action.md b/docs/ci-integration/github-action.md index f0525cf..40d19af 100644 --- a/docs/ci-integration/github-action.md +++ b/docs/ci-integration/github-action.md @@ -117,9 +117,11 @@ By default the action downloads the latest RustMail release. Pin to a specific v ```yaml - uses: rustmailapp/rustmail-action@v1 with: - version: v0.1.0 + version: v0.2.1 # pin to a release tag ``` +Check [GitHub Releases](https://github.com/rustmailapp/rustmail/releases) for available versions. + ::: tip For non-GitHub CI systems, use the [CLI Assert Mode](/ci-integration/cli-assert) or [REST Assertions](/ci-integration/rest-assertions) directly. ::: diff --git a/docs/features/tui.md b/docs/features/tui.md new file mode 100644 index 0000000..241cb46 --- /dev/null +++ b/docs/features/tui.md @@ -0,0 +1,69 @@ +# Terminal UI + +RustMail includes a built-in terminal UI (TUI) that connects to a running RustMail instance. It provides a full email browser in your terminal with real-time updates via WebSocket. + +## Usage + +```sh +rustmail tui +``` + +Connect to a specific host and port: + +```sh +rustmail tui --host 192.168.1.10 --port 8025 +``` + +::: info +The `tui` subcommand requires the `tui` feature flag. Pre-built release binaries include it by default. Docker images do not (headless use case). +::: + +## Options + +| Flag | Default | Description | +|------|---------|-------------| +| `--host` | `127.0.0.1` | RustMail server host to connect to | +| `--port` | `8025` | RustMail HTTP port | + +## Keymaps + +### Message List + +| Key | Action | +|-----|--------| +| `j` / `Down` | Select next message | +| `k` / `Up` | Select previous message | +| `Enter` / `l` / `Right` | Open selected message | +| `Tab` | Switch focus between list and preview | +| `/` | Search | +| `r` | Toggle read/unread | +| `s` | Toggle starred | +| `d` | Delete selected message | +| `D` | Delete all messages (with confirmation) | +| `R` | View raw message | +| `g` | Jump to first message | +| `G` | Jump to last message | +| `]` | Next page | +| `[` | Previous page | +| `?` | Help | +| `q` | Quit | + +### Message Preview + +| Key | Action | +|-----|--------| +| `j` / `Down` | Scroll down | +| `k` / `Up` | Scroll up | +| `Esc` / `h` / `Left` / `Tab` | Back to list | +| `1` | Text tab | +| `2` | Headers tab | +| `3` | Raw tab | +| `r` | Toggle read/unread | +| `s` | Toggle starred | +| `d` | Delete message | +| `R` | View full raw source | +| `q` | Quit | + +## Neovim Integration + +The TUI is also available as a Neovim plugin with floating windows and custom keymaps. See [rustmail.nvim](/integrations/neovim). diff --git a/docs/getting-started/docker.md b/docs/getting-started/docker.md index bcc45cc..5093401 100644 --- a/docs/getting-started/docker.md +++ b/docs/getting-started/docker.md @@ -17,8 +17,11 @@ services: ports: - "1025:1025" - "8025:8025" + security_opt: + - no-new-privileges:true volumes: - rustmail-data:/data + restart: unless-stopped volumes: rustmail-data: diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 8e0c416..a546383 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -22,6 +22,10 @@ yay -S rustmail-bin Or with any AUR helper (`paru -S rustmail-bin`, `pacman -S rustmail-bin` via chaotic-aur, etc.). +## Pre-built Binaries + +Download from [GitHub Releases](https://github.com/rustmailapp/rustmail/releases/latest) — Linux (x86_64, aarch64, armv7 — glibc + musl), macOS (Intel + Apple Silicon). + ## From Source ```sh diff --git a/docs/getting-started/introduction.md b/docs/getting-started/introduction.md index c1f6e2e..751a74d 100644 --- a/docs/getting-started/introduction.md +++ b/docs/getting-started/introduction.md @@ -12,7 +12,7 @@ Most alternatives are either unmaintained (MailHog), minimal (MailCrab), or clou - **Search that works** — FTS5 across subject, body, sender, and recipients. - **Real-time UI** — WebSocket push, dark mode, keyboard shortcuts. Feels like a proper email client. - **CI-first** — REST assertion endpoints, a CLI assert mode, and a GitHub Action. -- **Zero runtime deps** — one binary, frontend baked in. Also ships as a ~15 MB Docker image. +- **Zero runtime deps** — one binary, frontend baked in. Also ships as a ~8 MB Docker image. ## How It Works diff --git a/docs/index.md b/docs/index.md index 481736f..377a4a0 100644 --- a/docs/index.md +++ b/docs/index.md @@ -34,7 +34,7 @@ features: - icon: '' title: One binary, no deps details: The frontend is embedded at compile time. Drop it on a server and run it. - - icon: '' - title: REST API + OpenAPI - details: Full API with an OpenAPI 3.1 spec. Export emails, release to real SMTP, get webhook notifications. + - icon: '' + title: DKIM / SPF / DMARC + details: Parses authentication headers with color-coded status badges. No other local mail catcher does this. --- diff --git a/ui/src/components/MessageDetail.tsx b/ui/src/components/MessageDetail.tsx index a11e204..be11401 100644 --- a/ui/src/components/MessageDetail.tsx +++ b/ui/src/components/MessageDetail.tsx @@ -113,7 +113,11 @@ export default function MessageDetail() { false; return (