GitHub - rustmailer/bichon: Bichon – A lightweight, high-performance Rust email archiver with WebUI (original) (raw)

Bichon Logo

GitHub Stars Docker Pulls User Survey

Release Docker License Ask DeepWiki Discord Follow on X

A self-hosted email archiving server built in Rust. Download emails from IMAP accounts, builds a full-text search index, and serves a REST API with an embedded WebUI. Purpose-built for long-term preservation, unified cross-account search, and programmatic access to archived email.

Watch the demo
▶ Click to watch the demo

Note

Bichon is an archiver, not an email client. It does not send, compose, forward, or reply to emails. Its optional SMTP server is for receiving emails only.

Contents

Features

Quick Start

Pull the image

docker pull rustmailer/bichon:latest

Create data directory

mkdir -p ./bichon-data

Run container

docker run -d
--name bichon
-p 15630:15630
-v $(pwd)/bichon-data:/data
--user 1000:1000
-e BICHON_ROOT_DIR=/data
-e BICHON_ENCRYPT_PASSWORD=your-secure-password-here
rustmailer/bichon:latest

Open http://localhost:15630 in your browser.

Important

Default login: username admin, password admin@bichon. Change this immediately via Settings → Profile.

Docker Compose

services: bichon: image: rustmailer/bichon:latest container_name: bichon ports: - "15630:15630" volumes: - ./bichon-data:/data user: "1000:1000" environment: BICHON_ROOT_DIR: /data BICHON_ENCRYPT_PASSWORD: your-secure-password-here BICHON_LOG_LEVEL: info

Binary Installation

Download from the Releases page:

Platform Archive
Linux (GNU) bichon-x.x.x-x86_64-unknown-linux-gnu.tar.gz
Linux (MUSL) bichon-x.x.x-x86_64-unknown-linux-musl.tar.gz
macOS bichon-x.x.x-x86_64-apple-darwin.tar.gz
Windows bichon-x.x.x-x86_64-pc-windows-msvc.zip

Linux / macOS

./bichon --bichon-root-dir /path/to/data --bichon-encrypt-password your-password

Windows

.\bichon.exe --bichon-root-dir E:\bichon-data --bichon-encrypt-password your-password

--bichon-root-dir must be an absolute path. All Bichon data lives under this directory.

Build from Source

Prerequisites: Rust (latest stable), Node.js 20+, pnpm

git clone https://github.com/rustmailer/bichon.git cd bichon

Build the WebUI (required before building the server)

cd web && pnpm install && pnpm run build && cd ..

Build and run

export BICHON_ENCRYPT_PASSWORD=dev-password cargo run -- --bichon-root-dir /tmp/bichon-data

For frontend development:

cd web && pnpm run dev # Vite dev server with API proxy to Rust backend

Tip

The WebUI must be built at least once (pnpm run build) for the server to serve the frontend. In dev mode (pnpm run dev), Vite proxies API calls to the Rust server automatically.

Configuration Reference

All settings accept both CLI flags (--bichon-http-port) and environment variables (BICHON_HTTP_PORT). CLI flags take precedence over environment variables.

Required Settings

Variable CLI Flag Description
BICHON_ROOT_DIR --bichon-root-dir Required. Absolute path for all persistent data
BICHON_ENCRYPT_PASSWORD --bichon-encrypt-password Password used to encrypt stored credentials (IMAP passwords, OAuth tokens)
BICHON_ENCRYPT_PASSWORD_FILE --bichon-encrypt-password-file Alternative: read the encryption password from a file

Note

If both password options are set, the direct value takes precedence over the file.

Server & Networking

Variable Default Description
BICHON_HTTP_PORT 15630 HTTP server port
BICHON_BIND_IP 0.0.0.0 IP address to bind to (IPv4 or IPv6)
BICHON_PUBLIC_URL http://localhost:15630 Public-facing URL used in OAuth redirects and docs
BICHON_BASE_URL / Base path for WebUI when behind a reverse proxy (e.g. /bichon)
BICHON_WEBUI_TOKEN_EXPIRATION_HOURS 168 Access token lifetime in hours (default 7 days)
BICHON_HTTP_COMPRESSION_ENABLED true Enable gzip/brotli/zstd response compression

Logging

Variable Default Description
BICHON_LOG_LEVEL info Log level: trace, debug, info, warn, error
BICHON_ANSI_LOGS true Colorized terminal output
BICHON_JSON_LOGS false JSON-formatted logs for log aggregators
BICHON_LOG_TO_FILE false Persist logs to files under root dir
BICHON_MAX_SERVER_LOG_FILES 5 Max log files to retain

CORS

Variable Default Description
BICHON_CORS_ORIGINS (allow all) Comma-separated list of allowed origins: http://192.168.1.16:15630,http://myserver.local:15630
BICHON_CORS_MAX_AGE 86400 Cache duration for CORS preflight in seconds

Warning

If BICHON_CORS_ORIGINS is not set, all origins are allowed. If you set it, only exact matches pass. Wildcards (*) are not supported. Do not add trailing slashes. When using Docker, avoid wrapping the value in quotes.

TLS & HTTPS

Variable Default Description
BICHON_ENABLE_REST_HTTPS false Serve the API over HTTPS (requires valid certificate)

SMTP Server

Variable Default Description
BICHON_ENABLE_SMTP false Enable the embedded SMTP receiver
BICHON_SMTP_PORT 2525 SMTP listening port
BICHON_SMTP_ENCRYPTION starttls Encryption mode: none, starttls, or tls
BICHON_SMTP_AUTH_REQUIRED true Require authentication for SMTP connections
BICHON_SMTP_TLS_KEY_PATH Absolute path to SMTP TLS private key
BICHON_SMTP_TLS_CERT_PATH Absolute path to SMTP TLS certificate chain

Storage Paths

Variable Default Description
BICHON_INDEX_DIR {root}/bichon-indices Tantivy full-text index directory
BICHON_DATA_DIR {root}/bichon-storage Fjall blob storage directory

Tip

Place BICHON_INDEX_DIR on fast SSD storage for responsive search, and BICHON_DATA_DIR on high-capacity HDD for cost-effective blob storage.

Important

Bichon does NOT support writing data directly to a network file system (NFS, CIFS/SMB, etc.). All directories — BICHON_ROOT_DIR, BICHON_DATA_DIR, and BICHON_INDEX_DIR — must reside on a local file system; otherwise, data corruption may occur.

Performance Tuning

Variable Default Description
BICHON_SYNC_CONCURRENCY num_cpus × 2 Max concurrent account sync tasks
BICHON_METADATA_CACHE_SIZE 134217728 (128 MB) Metadata DB cache in bytes
BICHON_ENVELOPE_CACHE_SIZE 134217728 (128 MB) Envelope index cache in bytes

Authentication & RBAC

Authentication

  1. POST /api/login with username + password returns a JWT access token
  2. All /api/v1/* endpoints require Authorization: Bearer <token>
  3. Tokens expire after the configured duration (BICHON_WEBUI_TOKEN_EXPIRATION_HOURS, default 7 days)
  4. Long-lived API tokens can be created via WebUI or API for programmatic access

Default Admin Account

On first start, Bichon creates a built-in admin user:

Important

Change the password immediately via WebUI: Settings → Profile. If locked out, use the bichon-admin CLI tool to reset it.

Built-in Roles

Role Type Scope Description
Admin Global Unrestricted Full system access — users, roles, tokens, all accounts, all data operations
Manager Global ACL-scoped Create accounts, view users, manage authorized accounts and their data
Member Global Minimal Basic login access; data access granted through account-level role assignments
AccountManager Account Per-account Full control over an assigned account — config, sync, data read/write/delete, import, SMTP ingest
AccountViewer Account Per-account Read-only access to an assigned account's messages and metadata

Permission Reference

Global permissions:

Permission Description
system:access Login and access the dashboard
system:root Manage system configurations (OAuth providers, proxy settings)
user:manage Create, update, and delete users
user:view View user list and basic profiles
token:manage View and revoke all API tokens
account:create Connect new email accounts to the system
account:manage:all Manage configurations for all email accounts
data:read:all Search and read messages across all accounts
data:manage:all Manage tags and metadata for all accounts
data:raw:download:all Download raw EML files from any account
data:delete:all Permanently delete messages from any account
data:export:batch:all Export messages in bulk from all accounts

Account-scoped permissions (require ACL assignment):

Permission Description
account:manage Modify configuration and sync settings for authorized accounts
account:read_details View status and details of authorized accounts
data:read Read messages from authorized accounts
data:manage Manage tags and metadata for authorized accounts
data:raw:download Download raw EML files from authorized accounts
data:delete Delete messages from authorized accounts
data:export:batch Export messages from authorized accounts
data:import:batch Import EML/PST data into authorized accounts
data:smtp:ingest Receive and archive emails via SMTP for authorized accounts

Tip

Built-in role permissions are immutable. Create custom roles via WebUI (/users/roles) or API for any combination of the permissions above.

CLI Tools

bichon-cli — Import & Export

./bichon-cli --config config.toml

Creates a config.toml on first run with your server URL and API token.

Operation Description
EML Directory Recursively scan a directory tree of .eml files; preserves folder structure
MBOX Stream-import from a single .mbox archive (including Gmail's MBOX variant)
Thunderbird Import directly from a local Thunderbird profile directory
PST Import from Outlook Personal Storage .pst files
Export to MBOX Download account data as an .mbox file

All imports are processed server-side — the server handles MIME parsing, indexing, deduplication, and storage.

bichon-admin — Administration

Interactive menu with two operations:

Operation Description
Reset Admin Password Reset the built-in admin password when locked out
Migrate v0.3.7 → v1.0 Non-destructive migration from legacy storage layout to v1.0 architecture

API Reference

Interactive API documentation is available at:

Endpoint UI
/api-docs/swagger Swagger UI
/api-docs/redoc ReDoc
/api-docs/scalar Scalar
/api-docs/spec.json Raw OpenAPI 3.0 JSON
/api-docs/spec.yaml Raw OpenAPI 3.0 YAML

All /api/v1/* endpoints require Authorization: Bearer <token>.

Import & Export

Supported Formats

Format Tool Notes
EML Directory bichon-cli Recursive .eml scan; preserves folder hierarchy
MBOX bichon-cli Single-file streaming import; supports Gmail's MBOX variant
Thunderbird bichon-cli Reads directly from local Thunderbird profile directory
PST bichon-cli Outlook Personal Storage (.pst) file parsing
API Import POST /api/v1/import Base64-encoded EML payloads for programmatic use
MBOX Export bichon-cli Download account data as .mbox file

All imports flow through the Bichon REST API. The server parses MIME, extracts metadata, indexes content into Tantivy, deduplicates by BLAKE3 content hash, and stores raw blobs in Fjall.

Architecture

Workspace Crates

bichon/
├── crates/
│   ├── memdb/         Embedded key-value database layer (WAL, transactions)
│   ├── core/          Library — IMAP sync, search, storage, auth, models
│   ├── server/        Binary — Poem web server + embedded WebUI (rust-embed)
│   ├── cli/           Binary — bichon-cli import/export CLI
│   └── admin/         Binary — bichon-admin password reset & migration
└── web/               React + TypeScript + Vite + ShadCN UI frontend

Three-Layer Storage

Request Layer
    REST API (Poem)  │  WebUI (React)
─────────────────────┼────────────────────
Storage Layer         │
                      │
  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐
  │    memdb     │  │   Tantivy    │  │    Fjall     │
  │  (metadata)  │  │  (full-text) │  │   (blobs)    │
  │              │  │              │  │              │
  │ • accounts   │  │ • envelope   │  │ • raw emails │
  │ • users      │  │ • attachment │  │ • attachments│
  │ • roles      │  │ • tags       │  │   LZ4 compr. │
  │ • config     │  │ • contacts   │  │              │
  │ • proxies    │  │   Zstd compr.│  │ BLAKE3 hash  │
  └──────────────┘  └──────────────┘  └──────────────┘

IMAP Download Pipeline

Schedule tick (every 10s)
        │
        ▼
  reconcile_mailboxes()
  Compare local vs. remote
        │
   ┌────┴────┐
   ▼         ▼
UID OK    UID changed / new
(incremental)  (full rebuild)
   │         │
   ▼         ▼
fetch new   fetch all
(max+1:*)   (1:* batched)
   │         │
   └────┬────┘
        ▼
extract_envelope_and_store_it()
        │
   ┌────┼────┐
   ▼    ▼    ▼
Tantivy Fjall memdb

Content Deduplication & Attachment Storage

                     ┌──────────────────────────────────────────┐
                     │              Raw EML bytes               │
                     └────────────────┬─────────────────────────┘
                                      │
                                      ▼
                     ┌──────────────────────────────────────────┐
                     │         BLAKE3 → email_content_hash      │
                     └────────────────┬─────────────────────────┘
                                      │
                                      ▼
                     ┌──────────────────────────────────────────┐
                     │          MIME parse → Message            │
                     └───────┬──────────────────┬──────────────┘
                             │                  │
                             │     ┌────────────┘
                             │     │  detach attachments
                             │     │
                             ▼     ▼
               ┌─────────────────┐   ┌──────────────────────────────┐
               │  EMAIL BODY     │   │  EACH ATTACHMENT             │
               │                 │   │                              │
               │  Replace raw    │   │  BLAKE3(decoded content)     │
               │  attachment     │   │  → attachment_content_hash   │
               │  bytes with     │   │                              │
               │  placeholder:   │   │  Store raw undecoded bytes   │
               │                 │   │  in Fjall attachments_ks     │
               │  <<BICHON_      │   │  (skip if hash exists)       │
               │   DETACH_HASH:  │   │                              │
               │   xxx>>         │   │  Extract text for indexing   │
               │                 │   │  (PDF, DOCX, etc.)           │
               └───────┬─────────┘   └──────────────┬───────────────┘
                       │                            │
                       ▼                            │
               ┌──────────────────────────────┐     │
               │  Stripped EML stored in      │     │
               │  Fjall email_keyspace        │     │
               │  keyed by email_content_hash │     │
               │  (skip if hash exists)       │     │
               └──────────────┬───────────────┘     │
                              │                     │
                              ▼                     ▼
               ┌─────────────────────────────────────────────────┐
               │           Tantivy full-text index               │
               │  envelope index · attachment index              │
               └─────────────────────────────────────────────────┘

   ═══════════════════════════════════════════════════════════════

   Dedup layers
   ┌─────────────────────────────────────────────────────────────────┐
   │ Fjall (insert-time)                                             │
   │   contains_key(hash)? → skip : store with LZ4 compression       │
   │                                                                 │
   │ Tantivy (periodic, every 12 h)                                  │
   │   Group by (account, mailbox, content_hash)                     │
   │   Keep latest ingest_at → soft-delete older copies              │
   │   Cascade-delete orphaned attachment index entries              │
   └─────────────────────────────────────────────────────────────────┘

   Reconstruction
   ┌─────────────────────────────────────────────────────────────────┐
   │ Fetch stripped EML by content_hash from Fjall                  │
   │ Find <<BICHON_DETACH_HASH:xxx>> placeholders                    │
   │ Replace each with raw attachment blob from Fjall                │
   │ Result → byte-identical original EML                            │
   └─────────────────────────────────────────────────────────────────┘

Every ingested email is hashed with BLAKE3. Attachments are detached from the MIME tree, hashed independently (decoded content), and stored as raw undecoded bytes in Fjall's attachments_keyspace. The email body is patched with hash-based placeholders and stored in email_keyspace. Both keyspaces check for existing hashes before writing — identical content is never stored twice, regardless of which account or folder it arrives in. A periodic index dedup task (every 12 hours) scans Tantivy for duplicate (account, mailbox, content_hash) tuples, keeps the most recently ingested copy, and cascade-deletes orphaned attachment entries so UID-based incremental sync remains accurate. The original EML reconstructs byte-for-byte by swapping placeholders back with their attachment blobs.

Storage & Backup

Data Directory Layout

{root}/
├── bichon-indices/         Tantivy full-text index (envelope + attachment)
├── bichon-storage/         Fjall LZ4-compressed blob store
├── memdb/                  Metadata database (accounts, users, roles, config)
├── logs/                   Server logs (when BICHON_LOG_TO_FILE=true)

Backup

Back up the entire BICHON_ROOT_DIR (and BICHON_INDEX_DIR / BICHON_DATA_DIR if overridden). All three layers must be backed up together for consistency.

Warning

Do not place BICHON_ROOT_DIR or index/data directories directly on network-mounted storage (NFS, SMB, etc.). This can cause index corruption and data loss. Always run Bichon on local storage and use rsync or similar tools to sync to remote destinations.

Example with rsync

rsync -avz /path/to/bichon-data/ backup-server:/backups/bichon/

Encryption

Stored credentials (IMAP passwords, OAuth tokens) are encrypted with AES-256-GCM via ring. The encryption key is derived from BICHON_ENCRYPT_PASSWORD.

Note

Re-encrypting stored secrets after a password change is not yet supported. If this is a required feature for your use case, please open an issue.

Internationalization

The WebUI is available in 18 languages:

Code Language Code Language
ar العربية it Italiano
da Dansk jp 日本語
de Deutsch ko 한국어
en English nl Nederlands
es Español no Norsk
fi Suomi pl Polski
fr Français pt Português
it Italiano ru Русский
zh 中文 sv Svenska
zh-tw 繁體中文

Language preference and UI theme are saved to your user profile and can be changed anytime from the WebUI settings.

Data Migration (v0.3.7 → v1.x)

Bichon v1.x introduced a redesigned storage architecture:

Layer v0.3.7 (Legacy) v1.x
Index Tantivy (shared instance, no full attachments) Tantivy (separate envelope + attachment indices)
Raw data Tantivy (inline, stored in another Tantivy instance) Fjall (LZ4-compressed LSM-tree key-value store)
Metadata Native_DB (shared, disk-based DB powered by redb) memdb (dedicated, in-house in-memory DB)

If you ran Bichon prior to v1.x, migrate your data:

./bichon-admin

Select "Migrate Legacy v0.3.7 Storage to v1.x"

Note

The migration is non-destructive — original v0.3.7 files remain in place and are not modified. You can safely remove them manually after verifying the migration was successful.

FAQ

CORS errors when accessing the WebUI

  1. Enable debug logging: BICHON_LOG_LEVEL=debug
  2. Check the server logs for the incoming Origin header and configured origins
  3. Ensure the browser's exact origin matches an entry in BICHON_CORS_ORIGINS (no trailing slash, no wildcards)
  4. In Docker, do not quote the value: -e BICHON_CORS_ORIGINS=http://192.168.1.16:15630

"Legacy data layout detected" error on startup

Your data was created by Bichon v0.3.7 and must be migrated. Run ./bichon-admin and select the migration option.

How do I run Bichon behind a reverse proxy?

Set BICHON_BASE_URL=/bichon (or your sub-path) and configure your proxy:

nginx example

location /bichon/ { proxy_pass http://127.0.0.1:15630/; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }

Can Bichon send emails?

No. Bichon is an archiver, not an email client. The optional SMTP server receives emails only — it cannot send, forward, or reply.

What hardware does Bichon need?

How do I reset the admin password?

./bichon-admin

Select "Reset Admin Password"

Where can I get help?

Roadmap

Contributing

Contributions of all kinds are welcome — code, bug reports, documentation, or feature suggestions.

git clone https://github.com/rustmailer/bichon.git cd bichon

Build WebUI

cd web && pnpm install && pnpm run build && cd ..

Build backend

cargo build

Run tests

cargo test

Important

Before implementing a new feature or making significant changes, please open an issue first to discuss your idea with the maintainer and ensure it aligns with the project's scope.

Feel free to open an Issue or join the Discord to discuss ideas.

Tech Stack

Layer Technology
Backend Rust, Tokio, Poem + Poem OpenAPI
Full-text search Tantivy (Zstd compression)
Blob storage Fjall (LSM tree, LZ4 compression, KV separation)
Metadata DB memdb (embedded key-value store with WAL)
IMAP async-imap, rustls (ring), SOCKS5 proxy support
SMTP Embedded receiver (AUTH PLAIN/LOGIN, STARTTLS/TLS)
Cryptography AES-256-GCM (ring), BLAKE3 (content hashing)
Frontend React 18, TypeScript, Vite 6, ShadCN UI, TanStack Router/Query/Table
Charts Recharts
i18n i18next (18 languages)
Container Ubuntu 24.04, Docker

License

Bichon is licensed under the GNU Affero General Public License v3.0. Copyright © 2025–2026 rustmailer.com