> ## Documentation Index
> Fetch the complete documentation index at: https://docs.suchi.page/llms.txt
> Use this file to discover all available pages before exploring further.

# Mail ingest via mbsync

> A one-command Docker stack that polls IMAP with mbsync and hands every message to suchi as .eml.

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](/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

```sh theme={null}
cd deploy/mail-mbsync
./setup.sh
```

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:

```sh theme={null}
docker compose --profile proton run --rm -it bridge init
>>> login       # your Proton email + password + 2FA
>>> info        # copy the "IMAP Password" — that's what setup.sh wants
>>> exit
```

Then bring up the stack:

```sh theme={null}
docker compose up -d
docker compose logs -f mbsync suchi
```

## What each container does

* **bridge** (Proton only, `proton` compose profile) — [shenxn/protonmail-bridge](https://github.com/shenxn/protonmail-bridge-docker)
  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

| Path           | Contents                                                                                   |
| -------------- | ------------------------------------------------------------------------------------------ |
| `config/.env`  | Container-side env (IMAP creds, suchi runtime vars). 600.                                  |
| `.env`         | Compose-level env (profile, port, UID/GID).                                                |
| `maildir/`     | mbsync's Maildir tree.                                                                     |
| `ingest/`      | Handoff between mbsync and suchi. Ephemeral — suchi deletes files after successful ingest. |
| `suchi-data/`  | Suchi's `dms.db`, blob CAS, backups.                                                       |
| `bridge-data/` | Proton Bridge vault (Proton only).                                                         |

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

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

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

```sh theme={null}
docker compose down
# then, if you also want the mail + state gone:
rm -rf maildir ingest suchi-data bridge-data
```

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