bichon

rustmailerAGPL-3.0活跃维护

用 Rust 写的高性能邮件归档工具,通过 IMAP 拉取邮件并压缩存储,自带 WebUI 和全文搜索,适合自建邮箱备份。

Bichon – A lightweight, high-performance Rust email archiver with WebUI

2.3.2发布于 2026-11-07 13:09 UTC2,077star +4 / 7 天92fork同步于 2026-11-10 11:45 UTC

怎么上手

由 README 提炼,执行前请确认
docker pull rustmailer/bichon:latest
硬件要求按 README 说明

README 未说明显存或 GPU 要求;需本地文件系统存放数据,明确不支持 NFS、CIFS/SMB 等网络文件系统,建议索引目录放 SSD、数据目录放 HDD。

有 Docker 镜像
下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

Rust 编写的自托管邮件归档服务器,从 IMAP 拉取邮件并建全文索引,提供 REST API 和 WebUI

用 Rust 写的高性能邮件归档工具,通过 IMAP 拉取邮件并压缩存储,自带 WebUI 和全文搜索,适合自建邮箱备份。

核心能力

  • 多账号并发 IMAP 下载,支持 OAuth 2.0 与自动刷新令牌
  • 基于 UID 的增量下载,检测 UIDVALIDITY 变化自动重建缓存
  • Tantivy 全文索引加 Zstd 压缩存储,内置零外部依赖
  • 按 BLAKE3 内容哈希去重邮件正文与附件
  • 内置 RBAC 多用户权限与账号级隔离,附 OpenAPI 文档

容易踩的坑

BICHON_ROOT_DIR 为必填项,且必须使用绝对路径;BICHON_ENCRYPT_PASSWORD 用于加密存储的 IMAP 密码和 OAuth 令牌,必须设置;数据目录不能放在 NFS/SMB 网络文件系统上,否则可能损坏数据;默认 HTTP 端口为 15630,SMTP 默认关闭且端口为 2525。

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

5 个版本2.3.2
  1. 2.3.2Release 2.3.2最新正式版2026-11-07 · 2026-11-07 13:09 UTC

    Release Notes

    Fixed

    • Empty IMAP folders no longer stay "Pending" forever after an incremental sync (#398). Both a newly discovered empty mailbox and an existing one are now marked Success (0/0) once the round observes them. Previously the session could report Success overall while its empty folders kept showing Pending.
    • A round that completes one folder and fails another is no longer reported as a total failure when the completed folder was newly discovered and empty — empty-folder completions now count toward the synced total, so such rounds report Partial (x/y) like any other partial round.
    • Messages whose inline image precedes their text part no longer render an empty body; body extraction now picks the first part that actually renders as text/HTML, converting HTML fallbacks.
    • bichon-admin: migrating from a v0.3.7 store no longer panics (the offline migration touched a runtime-dependent global) and no longer loses internal_date, which was being read from the message_id field of the old envelope layout.

    Changed

    • mail-parser upgraded 0.11 → 1.0. Explicitly attached parts inside multipart/alternative are now archived (RFC 8621 classification; 0.11 silently ignored them). Attachment content hashes and sizes are kept stable across the upgrade, so existing blob/dedup records stay valid.

    What's Changed

    New Contributors

    Full Changelog: https://github.com/rustmailer/bichon/compare/2.3.1...2.3.2

  2. 2.3.1Release 2.3.12026-11-06 · 2026-11-06 12:40 UTC
    查看更新说明

    Release Notes

    Fixed

    • Large-account IMAP syncs no longer fail when Gmail/Exchange throttles (#396). Rate limits are now recognized as throttling instead of bad credentials, the round backs off per account and defers the remaining folders, and a round with failures reports Partial (e.g. 393/394 folders) instead of Failed — so you can tell how complete the archive actually is. Folders with nothing new are skipped without opening a connection, and a proven-expunged UID tail no longer re-enumerates on every sync.
    • Blob store: interrupted garbage collection no longer risks data loss (#393). The GC index cursor confused payload bytes with file offsets (an interrupted run could truncate a segment's tail and poison a backup object), and a stale index after a crash could make reads return another key's data. Both are fixed; recovery now rescans and heals affected segments.
    • Auto-adding a newly discovered mailbox no longer silently drops INBOX/Sent for accounts that never made an explicit folder selection.
    • Emails without a subject can be opened from the attachment list (#394); removed a non-functional bulk-selection control there (#395).

    Added

    • /healthz liveness endpoint — returns 200 ok even in maintenance mode, so orchestrators stop restarting the process an operator needs alive for a migration. /api/status remains the readiness probe.
    • reindex --yes — explicit non-interactive confirmation for scripts and Kubernetes Jobs; a non-TTY run without it now fails loudly (exit code 2) instead of cancelling silently with exit code 0.

    Changed

    • DELETE /api/v1/account/:id on a nonexistent account now returns 404 instead of a silent success, consistent with GET.
    • async-imap now tracks the official crate (0.12.0, with imap-proto 0.17) instead of our fork; brings additional IMAP parser fixes.

    Full Changelog: https://github.com/rustmailer/bichon/compare/2.3.0...2.3.1

  3. 2.3.0Release 2.3.02026-10-31 · 2026-10-31 22:57 UTC
    查看更新说明

    Release Notes

    Features

    Source-mailbox cleanup for IMAP accounts (#24) Archived mail can now be moved out of the source mailbox into bichon_auto_created_trash, manually or on a schedule.

    • Move only, never delete: MOVE (RFC 6851) is the only path. Bichon never issues EXPUNGE, and a server without the MOVE capability is reported as unsupported instead of falling back.
    • Every move is preceded by metadata-only verification of both the archived copy and the remote message; the MOVE capability is checked per connection.
    • Scheduled runs drain batches under a 10-minute budget; jobs are listed, polled and cancelled over the API and reattach after a page reload.
    • Reports keep the last 30 runs with per-folder outcomes and the messages that failed, and the new WebUI dialogs ship in 18 locales.

    Mailbox browser with reading pane A per-folder mail browser: browse a mailbox folder by folder and read messages in a pane without leaving the list.

    Prometheus metrics (#162) New /api/v1/metrics endpoint exposing 34 metrics for scraping.

    Dashboard storage breakdown Email data (blob store) and search index size are counted separately,honoured, and storage_stats_available tells API clients when thefigures would be partial.

    Sender resolved from configured headers (#386)

    • Accounts can list ordered headers whose first valid mailbox replaces the stored From address at ingest. The real From header remains the fallback, and the original address is preserved in wire_from.
    • The display name is stored in from_name; both fields are indexed into from_text / from_text_tri, so sender search, contacts and the dashboard use the resolved address.
    • Exposed in account advanced settings (collapsed by default); the web UI renders Name and a via hint.
    • Archive rules still match the real From address; forged headers are

    Contact suggestions include display names /all-contacts now returns { email, name? }, so pickers can show and milter values stay addresses. Mailbox avatars take their initials fromfrom_name.

    Gap-fill applies archive rules before downloading Candidates rejected by header-only filtering are never fetched, and filtered counts surface in state, API and UI. Cancellation finalization, watermark advancement when every candidate is filtered, and partial-batch accounting are fixed.

    Fixes

    Message content

    • Attachment parts with Content-Disposition: attachment but no ContenGET /api/v1/message-content with HTTP 500; they fall back toapplication/octet-stream and keep their filename (#369).

    Gap-fill and sync

    • Message-ID headers that are folded, lower/upper-case, carry comments, or are not valid UTF-8 are now parsed with the same parser as the archive side, so archived mail is no longer treated as missing and re-downloaded as duplicates. Thpath uses the same fix (#368).
    • Archive-rule filtering runs before bodies are fetched; failed header batches no longer shrink a run silently.

    Blob store (#393)

    • A short read at a segment tail is treated as a torn write instead of failing the scan.
    • The GC temp segment is removed when prepare or rename fails, stoppi
    • Writes are refused below a 1 GiB free-space floor and trigger a graceful shutdown instead of exiting; draining writes stay allowed.
    • Fixed pread_exact short reads on Windows.

    Search

    • Multi-word queries are AND by default; legacy saved searches replayep their OR behaviour.
    • Tag edits rebuild documents losslessly — non-stored text, trigram siblings and re-extracted attachment content from one shared implementation.
    • The analyzer is context-free (no per-string language detection), soion agree.
    • bichon-admin reindex builds the new index root aside, validates it, then swaps by rename: nothing is deleted, unreadable documents go to a 0600 NDJSON report, the v1.x migration can run it inline, and index markers self-heal. A cuured.

    Docker (#387)

    • tini runs as PID 1, so healthcheck orphans are reaped and SIGTERM ibounded wit
  4. 2.2.0Release 2.2.02026-10-20 · 2026-10-20 10:16 UTC
    查看更新说明

    Release Notes

    Features

    • Local directory backup target — backup/restore can now write to a local directory alongside S3, not just to S3-compatible storage
    • Search by envelope ID — look up one exact message by its envelope id, with a copy-id button in the message header; the Advanced filter matches the stored value verbatim
    • "No tags" search filter — the tag popover now offers a pinned untagged entry, so messages carrying no tag at all can be found (#379)
    • OAuth2 public clients — configs can be created without a client secret, with a client type selector in the OAuth2 form; access and refresh tokens can also be imported from the frontend (#383)
    • "Use system theme" toggle — the chosen color family is kept while its light/dark variant follows the OS/browser setting, live (#381, #382)
    • Blob repair — a message whose archived email or attachment blobs are missing can be re-fetched from IMAP and restored in place, keeping the envelope and its id; the repair verifies the fetched content against the archived hash and keeps a history of past runs (#371)
    • Admin-only reminder while the default admin password is still in use

    Bug Fixes

    • Attachment reconstruction lost the encoded MIME bodies, so reattached files could differ from what was archived; raw and decoded hashes are now kept apart, and mixed-format detached EMLs are recovered on reattach (#375)
    • Empty MIME records and legacy blobs are now isolated from raw MIME keys, so one attachment can no longer shadow another
    • Cp1253 encoded message text was not decoded (#378)
    • IMAP sync advanced the watermark on partially completed folders and stalled on unparseable emails; unparsable messages are skipped and the watermark only moves on a completed folder (#373, #374, #377)
    • The import button never became enabled for .pst files (#372)
    • --point on restore now accepts LATEST and pasted m-<id> names
    • Paused repair reads are preserved instead of being resumed by the nex

    What's Changed

    New Contributors

    Full Changelog: https://github.com/rustmailer/bichon/compare/2.1.1...2.2.0

  5. 2.1.1Release 2.1.12026-10-07 · 2026-10-07 08:59 UTC
    查看更新说明

    Release Note

    Fixed

    • Dashboard "Top accounts" showed the wrong name and searched the wrong account when two accounts share an email address #366
    • Account settings form failed validation when date_since/date_before came back as null #364

    Changed

    • Startup preflight failures (legacy data layout, layout check errors) no longer exit the process: the server stays alive and serves a 503 maintenance page with fix steps. Under Docker the container keeps running (healthcheck: unhealthy) and docker exec -it bichon-admin works for running the migration #331 #352 #365

    What's Changed

    New Contributors

    Full Changelog: https://github.com/rustmailer/bichon/compare/2.1.0...2.1.1

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    [!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

    • Multi-Account IMAP Download: Download multi-account concurrently. Supports password (PLAIN/LOGIN) and OAuth 2.0 (SASL XOAUTH2) with automatic token refresh and PKCE. SSL/TLS, STARTTLS, or plain connections with optional self-signed certificate acceptance.
    • Incremental Download: UID-based delta fetching downloads only new messages after the initial download. UIDVALIDITY changes are detected and trigger automatic cache rebuilds.
    • Fetch Scoping: Filter download by date range, mailbox folder limit, or specific folder names. Configurable per-account SOCKS5 proxy routing.
    • Auto-Configuration: Discover IMAP server settings automatically from an email domain.
    • Full-Text Search: Search across subject, body, sender, recipients, attachment properties, and more. Optimized for European languages.
    • Advanced Filters: Date range, size range, attachment presence, file type, content category, and facet-based tag combinations.
    • Thread Grouping: Reconstruct and view complete conversation threads across folders.
    • Attachment Search: Browse and filter attachments by sender, file type, size, and other attachment properties.
    • Faceted Tags: Add, remove, or overwrite tags on messages and attachments. Filter by tag combinations with real-time count updates.
    • Contacts View: Extracted and deduplicated sender/recipient address book across all authorized accounts.
    • Three-Layer Storage: Tantivy for full-text indexing (Zstd compression), bichon-blob with Zstd for compressed blob storage, and memdb for relational metadata. All embedded — zero external dependencies.
    • Content Deduplication: Identical email bodies and attachments stored once via BLAKE3 content hashing. Folder moves update metadata only.
    • Dashboard Analytics: Email volume trends, top senders, storage usage breakdown, attachment statistics, and per-account activity. Scoped by user permissions.
    • OpenAPI 3.0: Interactive API documentation at /api-docs (Swagger UI, ReDoc, Scalar). All endpoints documented with request/response schemas.
    • Multi-User RBAC: 5 built-in roles (Admin, Manager, Member, AccountManager, AccountViewer) plus custom roles with 22 granular permissions.
    • Account-Level Isolation: Grant users access to specific accounts with scoped roles. Permissions enforced at the API layer.
    • CLI & WebUI Import Tools: Import from EML directories, MBOX files (including Gmail variants), Thunderbird profiles, and Outlook PST files via CLI. Import EML files directly from the WebUI.
    • CLI Export: Download account data as MBOX via bichon-cli.
    • Bulk Restore: Restore emails in bulk back to their original IMAP accounts.
    • Embedded SMTP Server: Receive emails directly at the gateway level. STARTTLS or TLS encryption. AUTH PLAIN/LOGIN with API token authentication.
    • Admin Tooling: Password reset for locked-out admins. Non-destructive migration from v0.3.7 and v1.x to v2.x.
    • API Token Management: Create, list, and revoke long-lived API tokens for programmatic access.
    • SOCKS5 Proxy Management: Configure and manage proxy profiles for routing IMAP traffic per account.
    • Scheduled Download: Configure per-account download schedules using cron expressions. Run syncs at specific times or intervals — for example, nightly-only or business-hours-only archiving.
    • Remote Content Blocking: External images and tracking pixels embedded in emails are blocked by default. Users can selectively allow remote content to load on a per-message basis from the WebUI.
    • Async Index Deduplication: Duplicate detection in the search index is performed asynchronously, reducing write latency during high-throughput ingestion.

    Quick Start

    Docker (Recommended)

    # 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:

    PlatformArchive
    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
    macOSbichon-x.x.x-x86_64-apple-darwin.tar.gz
    Windowsbichon-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 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
    

    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

    VariableCLI FlagDescription
    BICHON_ROOT_DIR--bichon-root-dirRequired. Absolute path for all persistent data
    BICHON_ENCRYPT_PASSWORD--bichon-encrypt-passwordPassword used to encrypt stored credentials (IMAP passwords, OAuth tokens)
    BICHON_ENCRYPT_PASSWORD_FILE--bichon-encrypt-password-fileAlternative: read the encryption password from a file

    [!NOTE] If both password options are set, the direct value takes precedence over the file.

    Server & Networking

    VariableDefaultDescription
    BICHON_HTTP_PORT15630HTTP server port
    BICHON_BIND_IP0.0.0.0IP address to bind to (IPv4 or IPv6)
    BICHON_PUBLIC_URLhttp://localhost:15630Public-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_HOURS168Access token lifetime in hours (default 7 days)
    BICHON_HTTP_COMPRESSION_ENABLEDtrueEnable gzip/brotli/zstd response compression

    Logging

    VariableDefaultDescription
    BICHON_LOG_LEVELinfoLog level: trace, debug, info, warn, error
    BICHON_ANSI_LOGStrueColorized terminal output
    BICHON_JSON_LOGSfalseJSON-formatted logs for log aggregators
    BICHON_LOG_TO_FILEfalsePersist logs to files under root dir
    BICHON_MAX_SERVER_LOG_FILES5Max log files to retain

    CORS

    VariableDefaultDescription
    BICHON_CORS_ORIGINS(allow all)Comma-separated list of allowed origins: http://192.168.1.16:15630,http://myserver.local:15630
    BICHON_CORS_MAX_AGE86400Cache 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

    VariableDefaultDescription
    BICHON_ENABLE_REST_HTTPSfalseServe the API over HTTPS (requires valid certificate)

    SMTP Server

    VariableDefaultDescription
    BICHON_ENABLE_SMTPfalseEnable the embedded SMTP receiver
    BICHON_SMTP_PORT2525SMTP listening port
    BICHON_SMTP_ENCRYPTIONstarttlsEncryption mode: none, starttls, or tls
    BICHON_SMTP_AUTH_REQUIREDtrueRequire 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

    VariableDefaultDescription
    BICHON_INDEX_DIR{root}/bichon-indicesTantivy full-text index directory
    BICHON_DATA_DIR{root}/bichon-storagebichon-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

    VariableDefaultDescription
    BICHON_SYNC_CONCURRENCYnum_cpus × 2Max concurrent account sync tasks
    BICHON_METADATA_CACHE_SIZE134217728 (128 MB)Metadata DB cache in bytes
    BICHON_ENVELOPE_CACHE_SIZE134217728 (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:

    • Username: admin
    • Password: admin@bichon

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

    Built-in Roles

    RoleTypeScopeDescription
    AdminGlobalUnrestrictedFull system access — users, roles, tokens, all accounts, all data operations
    ManagerGlobalACL-scopedCreate accounts, view users, manage authorized accounts and their data
    MemberGlobalMinimalBasic login access; data access granted through account-level role assignments
    AccountManagerAccountPer-accountFull control over an assigned account — config, sync, data read/write/delete, import, SMTP ingest
    AccountViewerAccountPer-accountRead-only access to an assigned account's messages and metadata

    Permission Reference

    Global permissions:

    PermissionDescription
    system:accessLogin and access the dashboard
    system:rootManage system configurations (OAuth providers, proxy settings)
    system:metricsRead server-wide operational metrics (Prometheus endpoint)
    user:manageCreate, update, and delete users
    user:viewView user list and basic profiles
    token:manageView and revoke all API tokens
    account:createConnect new email accounts to the system
    account:manage:allManage configurations for all email accounts
    data:read:allSearch and read messages across all accounts
    data:manage:allManage tags and metadata for all accounts
    data:raw:download:allDownload raw EML files from any account
    data:delete:allPermanently delete messages from any account
    data:export:batch:allExport messages in bulk from all accounts

    Account-scoped permissions (require ACL assignment):

    PermissionDescription
    account:manageModify configuration and sync settings for authorized accounts
    account:read_detailsView status and details of authorized accounts
    data:readRead messages from authorized accounts
    data:manageManage tags and metadata for authorized accounts
    data:raw:downloadDownload raw EML files from authorized accounts
    data:deleteDelete messages from authorized accounts
    data:export:batchExport messages from authorized accounts
    data:import:batchImport EML/PST data into authorized accounts
    data:smtp:ingestReceive 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.

    OperationDescription
    EML DirectoryRecursively scan a directory tree of .eml files; preserves folder structure
    MBOXStream-import from a single .mbox archive (including Gmail's MBOX variant)
    ThunderbirdImport directly from a local Thunderbird profile directory
    PSTImport from Outlook Personal Storage .pst files
    Export to MBOXDownload account data as an .mbox file

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

    bichon-admin — Administration

    ./bichon-admin
    

    Interactive menu with three operations:

    OperationDescription
    Reset Admin PasswordReset the built-in admin password when locked out
    Migrate v0.3.7 → v2.xNon-destructive migration from legacy Tantivy-based storage to v2.x
    Migrate v1.x → v2.xBlob-only migration from Fjall to bichon-blob (indexes and metadata untouched)

    API Reference

    Interactive API documentation is available at:

    EndpointUI
    /api-docs/swaggerSwagger UI
    /api-docs/redocReDoc
    /api-docs/scalarScalar
    /api-docs/spec.jsonRaw OpenAPI 3.0 JSON
    /api-docs/spec.yamlRaw OpenAPI 3.0 YAML

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

    Monitoring

    GET /api/v1/metrics serves the Prometheus text exposition format (text/plain; version=0.0.4) for the whole installation. It requires a token whose user has the global system:metrics permission — use a dedicated monitoring user with system:access and system:metrics only. Prefer Authorization: Bearer over ?access_token= so the token does not end up in proxy/access logs, and do not attach a rate limit to the monitoring token's ACL (scrapes would be throttled with HTTP 429).

    Create a metrics-only token so the monitoring credential cannot be used anywhere else in the API:

    curl -X POST https://bichon.example.com/api/v1/access-token \
      -H "Authorization: Bearer <admin-token>" \
      -H "Content-Type: application/json" \
      -d '{"user_id": 42, "name": "prometheus", "metrics_only": true}'
    

    The token keeps the normal API-token lifecycle (expiry, revocation and last-access tracking), is accepted only on /api/v1/metrics, and is rejected by every other endpoint. Creation fails unless the token's owner holds system:metrics; the permission is checked again on every scrape.

    scrape_configs:
      - job_name: bichon
        scrape_interval: 30s
        scheme: https          # or http, matching the deployment
        authorization:
          type: Bearer
          credentials: <api-token-of-a-user-with-system:metrics>
        metrics_path: /api/v1/metrics
        static_configs:
          - targets: ["bichon.example.com:443"]
    

    Import & Export

    Supported Formats

    FormatToolNotes
    EML Directorybichon-cliRecursive .eml scan; preserves folder hierarchy
    MBOXbichon-cliSingle-file streaming import; supports Gmail's MBOX variant
    Thunderbirdbichon-cliReads directly from local Thunderbird profile directory
    PSTbichon-cliOutlook Personal Storage (.pst) file parsing
    WebUI ImportWebUIUpload .eml files directly from the browser
    API ImportPOST /api/v1/importBase64-encoded EML payloads for programmatic use
    MBOX Exportbichon-cliDownload 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.

    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    │  │ bichon-blob  │
      │  (metadata)  │  │  (full-text) │  │   (blobs)    │
      │              │  │              │  │              │
      │ • accounts   │  │ • envelope   │  │ • raw emails │
      │ • users      │  │ • attachment │  │ • attachments│
      │ • roles      │  │ • tags       │  │   Zstd compr.│
      │ • config     │  │ • contacts   │  │              │
      │ • proxies    │  │   Zstd compr.│  │ BLAKE3 hash  │
      └──────────────┘  └──────────────┘  └──────────────┘
    
    • memdb: Key-value metadata store. Houses accounts, users, roles, OAuth2 configs, proxy settings, and system configuration.
    • Tantivy: Full-text search indices with Zstd compression support. Two separate indices: envelope (email metadata + body text) and attachment (file metadata + extracted text). Batch-committed every 1,000 documents or 60 seconds.
    • bichon-blob: Zstd-compressed log-structured storage engine. Append-only segment files (1 GB each) with a redb-backed index for O(1) key lookup. Content-hash addressed (BLAKE3) with insert-time deduplication. Supports global dedup, online GC, and crash-safe recovery.

    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 bichon-blob memdb
    
    • Per-account background tasks managed by a global download-task singleton
    • Concurrency controlled by semaphore (default: num_cpus × 2)
    • Manual sync via POST /api/v1/accounts/:id/start-download; cancel with cancel-download
    • Busy-check prevents overlapping manual and automatic syncs on the same account

    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 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.

    Storage & Backup

    Data Directory Layout

    {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)
    

    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 19 languages:

    CodeLanguageCodeLanguage
    arالعربيةko한국어
    csČeštinanlNederlands
    daDansknoNorsk
    deDeutschplPolski
    enEnglishptPortuguês
    esEspañolruРусский
    fiSuomisvSvenska
    frFrançaiszh中文
    itItalianozh-tw繁體中文
    jp日本語

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

    Data Migration

    Bichon v2.x replaces the Fjall blob engine with bichon-blob. Two migration paths are available:

    Layerv0.3.7 (Legacy)v1.xv2.x
    IndexTantivy (inline)Tantivy (separate envelope + attachment)Tantivy (unchanged from v1.x)
    BlobsTantivy (inline)Fjall (LZ4-compressed LSM tree)bichon-blob (Zstd-compressed log-structured)
    Metadatanative_db (redb-backed)memdbmemdb (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.

    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 an older version of Bichon and must be migrated. Run ./bichon-admin and select the appropriate 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 I use an OAuth app that has no client secret?

    Yes. Native/desktop apps (public clients) have no secret. Microsoft Entra apps on the "Mobile and desktop applications" platform are the common case. Set Client type to Public/native client in the OAuth2 configuration and leave the secret out. Bichon then relies on PKCE for the code exchange. The redirect URI must match the app registration exactly, e.g. http://127.0.0.1:15630/oauth2/callback.

    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?

    • Recommended: 4+ CPU cores, 2+ GB RAM (sufficient for 10+ accounts and 200+ GB of archived data)
    • Filesystem: use a mainstream Linux filesystem such as ext4 or XFS; avoid network / virtual filesystems (NFS, VirtIO-FS) for all data directories
    • Indices benefit from SSD storage; blob storage can use HDD

    How do I reset the admin password?

    ./bichon-admin
    # Select "Reset Admin Password"
    

    Where can I get help?

    Roadmap

    • Multi-account IMAP Download (Password + OAuth2)
    • Full-text search with faceted tags
    • Multi-user support with RBAC and custom roles
    • WebUI in 18 languages with dark/light themes
    • Dashboard with analytics
    • CLI import: EML, MBOX, Thunderbird, PST
    • CLI export: MBOX
    • Embedded SMTP server
    • Data migration tooling (v0.3.7 / v1.x → v2.x)
    • On-demand manual download controls
    • Post-download server cleanup (free remote mailbox space)
    • Account-to-account email merge / migration
    • MCP Server for LLM-powered email search and analysis
    • S3-compatible storage backend
    • Enterprise SSO (OIDC / SAML)

    Contributing

    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.

    Guidelines
    1. AI-assisted, not AI-authored. Use AI to help analyze, debug, or draft code when unsure — but understand and review every change yourself before submitting. Don't submit unreviewed AI-generated content.
    2. Keep PRs scoped to one issue. Don't bundle unrelated changes (CI config, dependency bumps, fixes to other modules) into the same PR. Split them into separate PRs.
    3. Frontend/backend changes go together. If a change affects an API, data structure, or behavior with a frontend consumer, update the frontend in the same PR (or a clearly linked companion PR).
    4. Unit tests are required. New or fixed logic must include tests that reproduce the original issue and verify the fix. PRs without tests won't be merged.
    5. Maintain backward compatibility. Changes to data formats, protocols, configs, or APIs must state whether they're backward compatible. If not, include a migration plan.
    6. State the blast radius. PR descriptions must specify which modules/APIs/data are affected and any downstream impact.

    Commit Messages

    Format: <type>(<scope>): <subject>

    • type: fix, feat, refactor, ci, test, docs, chore
    • scope: affected module/component (e.g. rustmailer#286, dedup_cache)
    • subject: imperative, present tense, no period

    Rules:

    • One logical change per commit — don't mix a fix with CI tweaks or unrelated module changes.
    • Reference the issue number when applicable (e.g. fix(#286): ...).
    • Body explains why, not just what — include root cause and how it was verified for non-trivial fixes.
    • Rebase before submitting — squash WIP/fixup commits into a clean, logical sequence.
    • No vague messages like update, fix bug, wip.

    Tech Stack

    LayerTechnology
    BackendRust, Tokio, Poem + Poem OpenAPI
    Full-text searchTantivy (Zstd compression)
    Blob storagebichon-blob (log-structured, Zstd compression, BLAKE3 dedup)
    Metadata DBmemdb (embedded key-value store with WAL)
    IMAPasync-imap, rustls (ring), SOCKS5 proxy support
    SMTPEmbedded receiver (AUTH PLAIN/LOGIN, STARTTLS/TLS)
    CryptographyAES-256-GCM (ring), BLAKE3 (content hashing)
    FrontendReact 18, TypeScript, Vite 6, ShadCN UI, TanStack Router/Query/Table
    ChartsRecharts
    i18ni18next (18 languages)
    ContainerUbuntu 24.04, Docker

    License

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