Skip to main content
You want IMAP mail landing in suchi. This recipe stands up a small Docker stack that:
  1. Pulls mail from your provider into a local Maildir (mbsync).
  2. Fans every message into suchi’s ingest dir as <sha>.eml.
  3. Suchi ingests it via fs-watch — parses the .eml, fans out attachments as child docs, populates correspondents from From:, dedups by Message-Id.
Providers wired: Proton Mail (via Bridge, running as a sibling container), Gmail, Fastmail, and a generic IMAPS/IMAP escape hatch (iCloud, Yahoo, Zoho, Dovecot, etc.).

When to use this vs. built-in IMAP polling

Suchi already speaks IMAP directly via INGEST_IMAP_URL — see config#email-ingest-imap-polling. That’s fewer moving parts and enough for most operators. Reach for the mbsync recipe when you want:
  • A local Maildir mirror you can grep, back up, or hand off to another tool independently of suchi.
  • Proton Mail Bridge in the loop (suchi’s direct IMAP works against Bridge too; the recipe just bundles Bridge into the same stack).
  • Multi-folder / weird-provider support that suchi’s direct IMAP doesn’t cover yet.

Boot it

The wizard asks for provider, credentials, folders, and port; it writes config/.env (container-side vars) and .env (compose profiles + port
  • your UID/GID) and points templates/mbsyncrc.tmpl at the matching provider template.
For Proton, do the interactive Bridge login first:
Then bring up the stack:

What each container does

  • bridge (Proton only, proton compose profile) — shenxn/protonmail-bridge with libfido2 layered on for Bridge 3.25+. Listens on protonmail-bridge:143 inside the compose network. State (encrypted vault) lives in ./bridge-data/.
  • mbsync — Debian slim + isync + gettext. Renders the mbsyncrc template from env, polls every SYNC_INTERVAL seconds (default 300), writes Maildir to ./maildir/, then fans new files into ./ingest/ as <sha>.eml. Idempotent via a .fanned-marker timestamp file — each cycle only touches Maildir entries newer than the last fan.
  • suchi — built from the repo root Dockerfile (slim target). fs-watches /ingest, ingests every .eml, runs the full post-ingest pipeline including attachment fanout.

Where things live on the host

Rotating credentials

Re-run ./setup.sh — it’s idempotent. Or edit config/.env directly and docker compose restart mbsync.

Mail wizard (in-browser)

The server-rendered /admin/mail-setup wizard retired with the SPA promotion; the equivalent in-app settings panel is tracked (task #141 — needs GET/PUT /api/admin/settings/mail and a connection- test endpoint). Until it lands, edit config/.env directly and docker compose restart mbsync. The compose recipe still ships the two mounts the future in-app panel will use:
  • ./config → /etc/suchi/mail-config (RW) so suchi can rewrite the file. Directory mount, not a single-file bind — atomic rename requires this.
  • /var/run/docker.sock so suchi can POST /containers/suchi-mail-mbsync/restart to reload mbsync.
Mounting docker.sock grants suchi root-equivalent access to the host Docker daemon. Suchi only uses it to restart the sibling mbsync container, but the socket permits arbitrary daemon calls. Comment out the mount + unset MAIL_SETUP_DOCKER_SOCK if you’d rather docker compose restart mbsync yourself after a wizard save.

Smoke test

./smoke-test.sh drives the whole recipe end-to-end with the generic provider + dummy creds and asserts fs-watch → post-ingest → attachment fanout produces the expected doc counts. Run it before shipping any change to the recipe. Needs Docker; takes ~90s; cleans up after itself.

Teardown

Gotchas

  • First run pulls up to MAIL_MAX_MESSAGES messages per folder. Default is 200. Bump / lower in config/.env; mbsync keeps track of what it’s pulled via .mbsyncstate files inside the Maildir, so subsequent runs are incremental.
  • Attachments over MAIL_MAX_SIZE (default 25 MB) are silently skipped by mbsync. If you’re waiting on a huge PDF that never arrives, that’s the knob.
  • Proton labels ↔ folders. Bridge maps Proton labels to IMAP folders. Patterns in the mbsyncrc is what you tell mbsync to pull; add "Archive" or "Bills" etc. in MAIL_FOLDERS.
  • Gmail’s All Mail. Gmail exposes every message under [Gmail]/All Mail. If you set MAIL_FOLDERS="*" you’ll get every message twice (once from INBOX, once from All Mail). Prefer MAIL_FOLDERS="INBOX \"[Gmail]/Sent Mail\"" — explicit is safer.