From c2841712763e8b68c0b54a360f6464d372be312e Mon Sep 17 00:00:00 2001
From: Smyile <84925446+davidetacchini@users.noreply.github.com>
Date: Tue, 31 Mar 2026 01:14:35 +0200
Subject: [PATCH 1/3] docs: rewrite README for launch
Restructure around quick start with Docker, add feature table,
GitHub Action and CLI assert examples, TUI section, and link
to docs.rustmail.app. Add Docker pulls and release badges.
---
README.md | 162 ++++++++++++++++++++++++++++--------------------------
1 file changed, 83 insertions(+), 79 deletions(-)
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.
+
+
- ~7 MB binary · 28 KB JS gzipped · zero runtime dependencies
+ Documentation · API Reference · Docker Hub
-
-
- Web UI
- TUI
-
-
-
-
-
-
-
- Inspired by MailHog and MailCrab
+
-## 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
+
+
+
-- 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
From dae6e7123bd7a2520cd85c1bb61642aa1e75c357 Mon Sep 17 00:00:00 2001
From: Smyile <84925446+davidetacchini@users.noreply.github.com>
Date: Tue, 31 Mar 2026 01:14:40 +0200
Subject: [PATCH 2/3] docs: update documentation site and add TUI page
Add Terminal UI feature page with usage, options, and keymaps.
Update VitePress sidebar and copyright. Add pre-built binaries
section to installation. Harden docker-compose with security_opt
and restart policy. Fix Docker image size (~8 MB). Update GitHub
Action version pin example. Replace REST API feature card with
DKIM/SPF/DMARC. Add Docker Hub README.
---
docker/README.md | 102 +++++++++++++++++++++++++++
docs/.vitepress/config.ts | 3 +-
docs/ci-integration/github-action.md | 4 +-
docs/features/tui.md | 69 ++++++++++++++++++
docs/getting-started/docker.md | 3 +
docs/getting-started/installation.md | 4 ++
docs/getting-started/introduction.md | 2 +-
docs/index.md | 6 +-
8 files changed, 187 insertions(+), 6 deletions(-)
create mode 100644 docker/README.md
create mode 100644 docs/features/tui.md
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.
---
From 89ee33b21b880dcd1236eb1c80c7e4f3d18c155b Mon Sep 17 00:00:00 2001
From: Smyile <84925446+davidetacchini@users.noreply.github.com>
Date: Tue, 31 Mar 2026 01:14:44 +0200
Subject: [PATCH 3/3] style: format MessageDetail component
---
ui/src/components/MessageDetail.tsx | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
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 (
api.markStarred(msg().id, !starred()).catch(() => {})}
+ onClick={() =>
+ api
+ .markStarred(msg().id, !starred())
+ .catch(() => {})
+ }
class="rounded-md border border-zinc-300 dark:border-zinc-700 bg-zinc-100 dark:bg-zinc-800 p-1.5 transition cursor-pointer"
classList={{
"text-amber-400 hover:text-amber-500": starred(),