- This recipe — mail-intake. A small Go daemon that polls IMAP,
extracts attachments matching a MIME allowlist, and drops them
into a consume directory with a per-attachment JSON sidecar.
suchi’s
fs-watchpicks up both. Clean cred separation: the mail service never sees suchi; suchi never sees the mailbox. - Direct IMAP polling via
INGEST_IMAP_URL— no external tools; see config.
Prerequisites
- Proton Mail Bridge (or any IMAP endpoint) reachable from the mail-intake container.
- A shared volume both mail-intake and suchi can write / read.
Recommended: a bind mount off
/mnt/homelab/mail-intake/consume.
docker-compose
docker-compose.yml
mail-intake config.yaml
Sidecar compatibility
mail-intake emits flat-shape JSON:"suchi_sidecar": 1, but
core/ingest/sidecar transparently accepts the flat-shape JSON —
no producer patch needed. "Name <address>" correspondent strings
are peeled to just the display name; created accepts RFC-3339 or
YYYY-MM-DD.
If you’re writing your own producer, you can EITHER emit the
flat-shape JSON (portable) OR the versioned suchi-native form
(access to jd_category, custom_fields, multi-role correspondents,
etc. — see sidecar spec).
What you’ll see
For each attachment mail-intake extracts, suchi’s fs-watch:- Sees the file drop into
/ingest/. - Reads the sidecar (
filename.json). - Creates a document row owned by
INGEST_FS_OWNER_EMAIL. - Enqueues
post-ingest— qpdf → OCR → ZUGFeRD → rules → render. - Applies sidecar-derived title, created, correspondent (as sender), tags.
.eml as its own suchi
doc. If you want the email body indexed alongside the attachments
(so a full-text search hits “hi, please see attached” in the body),
either:
- Configure mail-intake to also drop the raw message alongside its attachments (upstream feature request, or write a small wrapper), OR
- Use suchi’s direct IMAP polling (
INGEST_IMAP_URL). That path ingests the.emlfirst, then fans out its attachments as child docs viaemail_parent_id.
Testing locally
The fastest way to kick the tires without touching your real mailbox:/consume/.