Skip to main content
Task-flavored walkthroughs. Each guide starts from a known state (usually a fresh dev server) and lands somewhere useful. Connecting things: Mail: Developer recipes — bootstrap, upload, dedup, fs-watch, gc — below. Every recipe assumes you’ve built the binary once:
Recipes use disposable data directories under /tmp/suchi-* so runs don’t collide. Kill the server at the end of each recipe (kill $PID) before starting the next one.
The recipes below use small text-like “PDFs” (a %PDF-1.7 ... header + %%EOF). They exercise the ingest pipeline shape but don’t trigger real OCR — for that you need qpdf, pdftotext, and ocrmypdf installed on the box (the Docker full image ships them all).

Recipe 0 — Bootstrap an instance

Every other recipe starts here. Boot with a fresh data dir, grab the setup token from the log, create the admin.

Recipe 1 — Upload API + dedup state machine

Exercises POST /api/documents/, the three dedup branches (fresh / alive-collision / trashed-collision), and soft-delete/restore.

Recipe 2 — Per-user dedup keyspace

Verifies that two users hold the same bytes as two independent docs (see phase-2(dedup) commit).

Recipe 3 — Post-ingest pipeline + /api/tasks/

Uses a real PDF to exercise qpdf → pdf-inspector → ocrmypdf routing and the durable outbox.
Missing binaries (qpdf / ocrmypdf) log skip.no_binary and the pipeline continues. The pdftotext-backed pdf-inspector fallback is available on any box with poppler-utils installed.

Recipe 4 — fs-watch + sidecar

Bootstrap the instance without fs-watch first, then restart with it enabled so the owner-email resolves.

Recipe 5 — bundle import (dry-run first, then full)

Env for the CLI: PUBLIC_URL + DATA_DIR are the only knobs; the subcommand doesn’t start a server, it just reads the same DB the running server would.

Recipe 6 — suchi gc (unreferenced-blob reclamation)

Recipe 7 — Poking at the durable outbox

The jobs table IS the truth of what suchi is doing. Every ingest producer writes a post-ingest job in the same tx as the doc row. Retries, backoff, dead-letters all live there.

Recipe 8 — /metrics scrape

Metrics of interest during development:
  • suchi_http_requests_total{route,status} — is the mux catching what you expect?
  • suchi_http_request_duration_seconds histogram — slow handler regression detector.
  • suchi_jobs_pending / suchi_jobs_running — outbox backlog.
  • suchi_jobs_dead_total{kind} — has anything given up permanently?

Recipe 9 — Clean shutdown + restart

The dispatcher drains gracefully; migrations, JD tree, and every plugin re-initialize on the next boot from the same data dir.
Everything above is deliberately verbose so a new contributor can copy any single recipe and see something happen. Once you’re comfortable, the same recipes compose: bootstrap once, then loop through 1 → 4 → 6 against a single running instance to touch every Phase-2 path in one session.