# InboxOS **Self-hosted personal ERP / CRM pipeline.** A single Gmail inbox, polled over IMAP, archived to SeaweedFS, classified and extracted by a local LLM (Ollama), and written to NocoDB as structured records. Curated attachments are mirrored to Papra. No third-party SaaS touches PII. ## Where to start - **30-minute walkthrough** — [`docs/operator-tutorial.md`](docs/operator-tutorial.md) (REQ-INB-53). Guided tour for the new operator: setup → first run → day-to-day → incident response → hardening. - **Operations runbook** — [`docs/operations-runbook.md`](docs/operations-runbook.md). System overview, service inventory, workflow operations, observability, failure modes, security, cost, glossary. The first thing to read on day 1 of incident response. - **Extraction contract** — [`docs/extraction-contract.md`](docs/extraction-contract.md). How a raw email becomes a NocoDB record. Source of truth for the `inboxos:classify` + `inboxos:extract-` prompt family. - **NocoDB schema** — [`docs/schemas.md`](docs/schemas.md). The four module tables (`finance`, `crm`, `intel`, `promotions`) and the Message-ID idempotency contract. - **Gitea Actions setup** — [`docs/gitea-actions.md`](docs/gitea-actions.md). Runner provisioning (including the second runner for live + historical parallelism), repo secrets, the Basic Auth model. - **Pipelines** — [`pipelines/README.md`](pipelines/README.md). Flow diagrams, idempotency model, rollback procedure for the live and historical pipelines. - **Papra write policy** — [`docs/papra-write-policy.md`](docs/papra-write-policy.md). When the pipeline writes to Papra (attachments + selected bodies), and why the first ship is dry-run. ## Quick layout ``` inboxos/ ├── README.md # this file ├── .env.example # placeholder env (no real secrets) ├── docker/ │ └── docker-compose.yml # self-hosted stack: gitea, runners, nocodb, seaweedfs, papra, ollama ├── .gitea/workflows/ │ ├── live-pipeline.yml # cron */5 * * * *, polls IMAP │ ├── historical-pipeline.yml # workflow_dispatch, streams .mbox │ └── ci-pr.yml # fast-tests + 100MB e2e + live Ollama smoke ├── pipelines/ # shared Python: common.py, live_pipeline.py, historical_pipeline.py, mbox_stream.py ├── .opencode/commands/ # version-controlled extraction prompts (prompt-as-code) ├── config/ # n8n config (legacy, no longer in use) └── docs/ # this runbook + schemas + contracts + reports ``` ## Status Phase 5 — Full Deployment. Parent goal: [INB-1](/INB/issues/INB-1). Current phase issue: [INB-7](/INB/issues/INB-7). The runbook in `docs/operations-runbook.md` is the canonical reference for ongoing operation. ## Historical archive (Google Takeout .mbox) The full Google Takeout `.mbox` export lives in SeaweedFS at the canonical S3 URI: ``` s3://inboxos/eml/historical/all-mail.mbox ``` That is the value to pass as the `mbox_object_key` workflow_dispatch input to `historical-pipeline.yml`. To verify it exists, run from any host with `mc` configured against the `inboxos` bucket: ```bash mc ls --recursive inboxos/inboxos/ | grep '\.mbox$' ``` The historical pipeline streams this object message-by-message, UPSERTs each into NocoDB on `Message-ID`, and writes the raw `.eml` backup under `s3://inboxos/eml/historical///
/.eml`. See [`docs/operations-runbook.md` §3.2](docs/operations-runbook.md) for the dispatch recipe and §4.4 for the full bucket layout.