Bichon – A lightweight, high-performance Rust email archiver with WebUI
Rust
1,976
157 commits
updated Oct 3, 2026
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.
[!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.
/api-docs (Swagger UI, ReDoc, Scalar). All endpoints documented with request/response schemas.bichon-cli.# 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, passwordadmin@bichon. Change this immediately via Settings → Profile.
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
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.
Prerequisites: Rust (latest stable), Node.js 20+, pnpm
git clone https://github.com/rustmailer/bichon.git
cd bichon
# Build and run — frontend dependencies are installed and built automatically via build.rs
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
All settings accept both CLI flags (--bichon-http-port) and environment variables (BICHON_HTTP_PORT). CLI flags take precedence over environment variables.
| 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.
| 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 |
| 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 |
| 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_ORIGINSis 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.
| Variable | Default | Description |
|---|---|---|
BICHON_ENABLE_REST_HTTPS | false | Serve the API over HTTPS (requires valid certificate) |
| 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 |
| Variable | Default | Description |
|---|---|---|
BICHON_INDEX_DIR | {root}/bichon-indices | Tantivy full-text index directory |
BICHON_DATA_DIR | {root}/bichon-storage | bichon-blob storage directory |
[!TIP] Place
BICHON_INDEX_DIRon fast SSD storage for responsive search, andBICHON_DATA_DIRon 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, andBICHON_INDEX_DIR— must reside on a local file system; otherwise, data corruption may occur.
| 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 |
POST /api/login with username + password returns a JWT access token/api/v1/* endpoints require Authorization: Bearer <token>BICHON_WEBUI_TOKEN_EXPIRATION_HOURS, default 7 days)On first start, Bichon creates a built-in admin user:
adminadmin@bichon[!IMPORTANT] Change the password immediately via WebUI: Settings → Profile. If locked out, use the
bichon-adminCLI tool to reset it.
| 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 |
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.
./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
Interactive menu with three operations:
| Operation | Description |
|---|---|
| Reset Admin Password | Reset the built-in admin password when locked out |
| Migrate v0.3.7 → v2.x | Non-destructive migration from legacy Tantivy-based storage to v2.x |
| Migrate v1.x → v2.x | Blob-only migration from Fjall to bichon-blob (indexes and metadata untouched) |
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>.
| 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 |
| WebUI Import | WebUI | Upload .eml files directly from the browser |
| 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 bichon-blob.
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
Request Layer
REST API (Poem) │ WebUI (React)
─────────────────────┼────────────────────
Storage Layer │
│
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ memdb │ │ Tantivy │ │ bichon-blob │
│ (metadata) │ │ (full-text) │ │ (blobs) │
│ │ │ │ │ │
│ • accounts │ │ • envelope │ │ • raw emails │
│ • users │ │ • attachment │ │ • attachments│
│ • roles │ │ • tags │ │ Zstd compr.│
│ • config │ │ • contacts │ │ │
│ • proxies │ │ Zstd compr.│ │ BLAKE3 hash │
└──────────────┘ └──────────────┘ └──────────────┘
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 bichon-blob memdb
num_cpus × 2)POST /api/v1/accounts/:id/start-download; cancel with cancel-download ┌──────────────────────────────────────────┐
│ 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 bichon-blob │
│ <<BICHON_ │ │ (skip if hash exists) │
│ DETACH_HASH: │ │ │
│ xxx>> │ │ Extract text for indexing │
│ │ │ (PDF, DOCX, etc.) │
└───────┬─────────┘ └──────────────┬───────────────┘
│ │
▼ │
┌──────────────────────────────┐ │
│ Stripped EML stored in │ │
│ bichon-blob │ │
│ keyed by email_content_hash │ │
│ (skip if hash exists) │ │
└──────────────┬───────────────┘ │
│ │
▼ ▼
┌─────────────────────────────────────────────────┐
│ Tantivy full-text index │
│ envelope index · attachment index │
└─────────────────────────────────────────────────┘
═══════════════════════════════════════════════════════════════
Dedup layers
┌─────────────────────────────────────────────────────────────────┐
│ bichon-blob (insert-time) │
│ contains_key(hash)? → skip : store with Zstd 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 bichon-blob │
│ Find <<BICHON_DETACH_HASH:xxx>> placeholders │
│ Replace each with raw attachment blob from bichon-blob │
│ 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 bichon-blob. The email body is patched with hash-based placeholders and stored separately. Both email and attachment blobs are deduplicated by content hash — 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.
{root}/
├── bichon-indices/ Tantivy full-text index (envelope + attachment)
├── bichon-storage/ bichon-blob Zstd-compressed blob store
├── memdb/ Metadata database (accounts, users, roles, config)
├── logs/ Server logs (when BICHON_LOG_TO_FILE=true)
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_DIRor 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/
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.
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.
Bichon v2.x replaces the Fjall blob engine with bichon-blob. Two migration paths are available:
| Layer | v0.3.7 (Legacy) | v1.x | v2.x |
|---|---|---|---|
| Index | Tantivy (inline) | Tantivy (separate envelope + attachment) | Tantivy (unchanged from v1.x) |
| Blobs | Tantivy (inline) | Fjall (LZ4-compressed LSM tree) | bichon-blob (Zstd-compressed log-structured) |
| Metadata | native_db (redb-backed) | memdb | memdb (unchanged from v1.x) |
v0.3.7 → v2.x (full migration):
./bichon-admin
# Select "Migrate Legacy v0.3.7 Storage to v2.x"
Rebuilds Tantivy indexes, migrates metadata to memdb, and converts blobs to bichon-blob.
v1.x → v2.x (blob-only):
./bichon-admin
# Select "Migrate v1.x Storage to v2.x"
Copies blobs from Fjall to bichon-blob. Tantivy indexes and memdb are left untouched.
[!NOTE] Both migrations are non-destructive — legacy files are never modified. After verifying the migration was successful, see the Migration Guide for cleanup instructions.
BICHON_LOG_LEVEL=debugOrigin header and configured originsBICHON_CORS_ORIGINS (no trailing slash, no wildcards)-e BICHON_CORS_ORIGINS=http://192.168.1.16:15630Your data was created by an older version of Bichon and must be migrated. Run ./bichon-admin and select the appropriate migration option.
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;
}
No. Bichon is an archiver, not an email client. The optional SMTP server receives emails only — it cannot send, forward, or reply.
./bichon-admin
# Select "Reset Admin Password"
Contributions of all kinds are welcome — code, bug reports, documentation, or feature suggestions.
[!IMPORTANT] By submitting a Pull Request, you agree to the terms of the Contributor License Agreement.
git clone https://github.com/rustmailer/bichon.git
cd bichon
# Build backend — frontend dependencies and build are handled automatically via build.rs
cargo build
# Run tests
cargo test
[!IMPORTANT] For new features: Please open a feature request issue first before starting implementation. PRs that introduce new functionality without a prior issue may be rejected to avoid unnecessary wasted effort.
For major bug fixes with wide-ranging impact, please open an issue and discuss with the maintainer before acting and submitting. This ensures the fix approach is aligned and avoids duplicate or conflicting work.
Large, hard-to-review PRs that touch many modules or contain substantial changes may be rejected outright. Break your work into smaller, focused PRs — one logical change per PR.
Feel free to open an Issue or join the Discord to discuss ideas.
Format: <type>(<scope>): <subject>
fix, feat, refactor, ci, test, docs, chorerustmailer#286, dedup_cache)Rules:
fix(#286): ...).update, fix bug, wip.| Layer | Technology |
|---|---|
| Backend | Rust, Tokio, Poem + Poem OpenAPI |
| Full-text search | Tantivy (Zstd compression) |
| Blob storage | bichon-blob (log-structured, Zstd compression, BLAKE3 dedup) |
| 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 |
Bichon is licensed under the GNU Affero General Public License v3.0. Copyright © 2025–2026 rustmailer.com
(top 24 of 28)
729 followers · starred May 2026
397 followers · starred Jan 2026
159 followers · starred Jan 2026
355 followers · starred Jan 2026
Rust
51.7%
TypeScript
47.8%
Bichon – A lightweight, high-performance Rust email archiver with WebUI
Rust
1,976
157 commits
updated Oct 3, 2026
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.
[!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.
/api-docs (Swagger UI, ReDoc, Scalar). All endpoints documented with request/response schemas.bichon-cli.# 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, passwordadmin@bichon. Change this immediately via Settings → Profile.
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
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.
Prerequisites: Rust (latest stable), Node.js 20+, pnpm
git clone https://github.com/rustmailer/bichon.git
cd bichon
# Build and run — frontend dependencies are installed and built automatically via build.rs
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
All settings accept both CLI flags (--bichon-http-port) and environment variables (BICHON_HTTP_PORT). CLI flags take precedence over environment variables.
| 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.
| 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 |
| 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 |
| 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_ORIGINSis 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.
| Variable | Default | Description |
|---|---|---|
BICHON_ENABLE_REST_HTTPS | false | Serve the API over HTTPS (requires valid certificate) |
| 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 |
| Variable | Default | Description |
|---|---|---|
BICHON_INDEX_DIR | {root}/bichon-indices | Tantivy full-text index directory |
BICHON_DATA_DIR | {root}/bichon-storage | bichon-blob storage directory |
[!TIP] Place
BICHON_INDEX_DIRon fast SSD storage for responsive search, andBICHON_DATA_DIRon 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, andBICHON_INDEX_DIR— must reside on a local file system; otherwise, data corruption may occur.
| 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 |
POST /api/login with username + password returns a JWT access token/api/v1/* endpoints require Authorization: Bearer <token>BICHON_WEBUI_TOKEN_EXPIRATION_HOURS, default 7 days)On first start, Bichon creates a built-in admin user:
adminadmin@bichon[!IMPORTANT] Change the password immediately via WebUI: Settings → Profile. If locked out, use the
bichon-adminCLI tool to reset it.
| 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 |
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.
./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
Interactive menu with three operations:
| Operation | Description |
|---|---|
| Reset Admin Password | Reset the built-in admin password when locked out |
| Migrate v0.3.7 → v2.x | Non-destructive migration from legacy Tantivy-based storage to v2.x |
| Migrate v1.x → v2.x | Blob-only migration from Fjall to bichon-blob (indexes and metadata untouched) |
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>.
| 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 |
| WebUI Import | WebUI | Upload .eml files directly from the browser |
| 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 bichon-blob.
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
Request Layer
REST API (Poem) │ WebUI (React)
─────────────────────┼────────────────────
Storage Layer │
│
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ memdb │ │ Tantivy │ │ bichon-blob │
│ (metadata) │ │ (full-text) │ │ (blobs) │
│ │ │ │ │ │
│ • accounts │ │ • envelope │ │ • raw emails │
│ • users │ │ • attachment │ │ • attachments│
│ • roles │ │ • tags │ │ Zstd compr.│
│ • config │ │ • contacts │ │ │
│ • proxies │ │ Zstd compr.│ │ BLAKE3 hash │
└──────────────┘ └──────────────┘ └──────────────┘
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 bichon-blob memdb
num_cpus × 2)POST /api/v1/accounts/:id/start-download; cancel with cancel-download ┌──────────────────────────────────────────┐
│ 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 bichon-blob │
│ <<BICHON_ │ │ (skip if hash exists) │
│ DETACH_HASH: │ │ │
│ xxx>> │ │ Extract text for indexing │
│ │ │ (PDF, DOCX, etc.) │
└───────┬─────────┘ └──────────────┬───────────────┘
│ │
▼ │
┌──────────────────────────────┐ │
│ Stripped EML stored in │ │
│ bichon-blob │ │
│ keyed by email_content_hash │ │
│ (skip if hash exists) │ │
└──────────────┬───────────────┘ │
│ │
▼ ▼
┌─────────────────────────────────────────────────┐
│ Tantivy full-text index │
│ envelope index · attachment index │
└─────────────────────────────────────────────────┘
═══════════════════════════════════════════════════════════════
Dedup layers
┌─────────────────────────────────────────────────────────────────┐
│ bichon-blob (insert-time) │
│ contains_key(hash)? → skip : store with Zstd 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 bichon-blob │
│ Find <<BICHON_DETACH_HASH:xxx>> placeholders │
│ Replace each with raw attachment blob from bichon-blob │
│ 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 bichon-blob. The email body is patched with hash-based placeholders and stored separately. Both email and attachment blobs are deduplicated by content hash — 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.
{root}/
├── bichon-indices/ Tantivy full-text index (envelope + attachment)
├── bichon-storage/ bichon-blob Zstd-compressed blob store
├── memdb/ Metadata database (accounts, users, roles, config)
├── logs/ Server logs (when BICHON_LOG_TO_FILE=true)
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_DIRor 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/
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.
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.
Bichon v2.x replaces the Fjall blob engine with bichon-blob. Two migration paths are available:
| Layer | v0.3.7 (Legacy) | v1.x | v2.x |
|---|---|---|---|
| Index | Tantivy (inline) | Tantivy (separate envelope + attachment) | Tantivy (unchanged from v1.x) |
| Blobs | Tantivy (inline) | Fjall (LZ4-compressed LSM tree) | bichon-blob (Zstd-compressed log-structured) |
| Metadata | native_db (redb-backed) | memdb | memdb (unchanged from v1.x) |
v0.3.7 → v2.x (full migration):
./bichon-admin
# Select "Migrate Legacy v0.3.7 Storage to v2.x"
Rebuilds Tantivy indexes, migrates metadata to memdb, and converts blobs to bichon-blob.
v1.x → v2.x (blob-only):
./bichon-admin
# Select "Migrate v1.x Storage to v2.x"
Copies blobs from Fjall to bichon-blob. Tantivy indexes and memdb are left untouched.
[!NOTE] Both migrations are non-destructive — legacy files are never modified. After verifying the migration was successful, see the Migration Guide for cleanup instructions.
BICHON_LOG_LEVEL=debugOrigin header and configured originsBICHON_CORS_ORIGINS (no trailing slash, no wildcards)-e BICHON_CORS_ORIGINS=http://192.168.1.16:15630Your data was created by an older version of Bichon and must be migrated. Run ./bichon-admin and select the appropriate migration option.
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;
}
No. Bichon is an archiver, not an email client. The optional SMTP server receives emails only — it cannot send, forward, or reply.
./bichon-admin
# Select "Reset Admin Password"
Contributions of all kinds are welcome — code, bug reports, documentation, or feature suggestions.
[!IMPORTANT] By submitting a Pull Request, you agree to the terms of the Contributor License Agreement.
git clone https://github.com/rustmailer/bichon.git
cd bichon
# Build backend — frontend dependencies and build are handled automatically via build.rs
cargo build
# Run tests
cargo test
[!IMPORTANT] For new features: Please open a feature request issue first before starting implementation. PRs that introduce new functionality without a prior issue may be rejected to avoid unnecessary wasted effort.
For major bug fixes with wide-ranging impact, please open an issue and discuss with the maintainer before acting and submitting. This ensures the fix approach is aligned and avoids duplicate or conflicting work.
Large, hard-to-review PRs that touch many modules or contain substantial changes may be rejected outright. Break your work into smaller, focused PRs — one logical change per PR.
Feel free to open an Issue or join the Discord to discuss ideas.
Format: <type>(<scope>): <subject>
fix, feat, refactor, ci, test, docs, chorerustmailer#286, dedup_cache)Rules:
fix(#286): ...).update, fix bug, wip.| Layer | Technology |
|---|---|
| Backend | Rust, Tokio, Poem + Poem OpenAPI |
| Full-text search | Tantivy (Zstd compression) |
| Blob storage | bichon-blob (log-structured, Zstd compression, BLAKE3 dedup) |
| 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 |
Bichon is licensed under the GNU Affero General Public License v3.0. Copyright © 2025–2026 rustmailer.com
(top 24 of 28)
729 followers · starred May 2026
397 followers · starred Jan 2026
159 followers · starred Jan 2026
355 followers · starred Jan 2026
Rust
51.7%
TypeScript
47.8%