Skip to content

Latest commit

 

History

History

README.md

Spool documentation

A small, self-hosted email helpdesk. Support mail arrives, becomes a ticket, an agent replies, the thread continues. Built for a team of 1–5.

Named for /var/spool/mail, and for the thing you wind thread onto.

These documents are the design record. They explain why the code is shaped the way it is — the code itself explains what it does. If you change a decision recorded here, change the document in the same commit.

Index

Document Covers
architecture.md Stack, storage model, database configuration, what's deliberately absent
ingest.md Inbound mail: rejection, threading, MIME splitting, idempotency
outbound.md Outbound mail: Mailgun, the delivery queue, delivered_at, backfill
compression.md zstd layer, dictionaries, when and how to train them
queue.md Tuber, consumers, the scheduler, the Active Job adapter
auth.md OIDC, the allowlist, the three configuration states
tags.md Ticket tags, the spam tag, blocked senders, what the inbox hides
ui-contract.md The model API the views are built against
ui.md The screens: design tokens, screen anatomy, Stimulus and Turbo conventions
deploy.md Docker image, versioning, release, what CI runs
versioning-rollout.md Brief for porting this versioning scheme to the sibling repos
todo.md What's built, what's next, open decisions

Orientation for someone (or something) new to the codebase

Read architecture.md first — it explains the one constraint everything else follows from: the primary SQLite file is the entire application state. There is no Active Storage, no blob directory, no second datastore. That property is why attachments live in a table, why there is a compression layer at all, and why Action Mailbox was rejected.

Then read ingest.md. The email plumbing is roughly 80% of the difficulty and 20% of the code.

Running it

bin/setup           # bundle, prepare databases
bin/dev             # web + worker + scheduler + tailwind + tuber (Procfile.dev)
bin/rails test      # the suite
bin/rails test:system
bin/standardrb      # style; --fix to autocorrect
bin/ci              # everything CI runs
bin/build           # the production image, locally, with GIT_SHA baked in

The README's screenshots are generated from the seeds rather than curated: SCREENSHOTS=1 bin/rails test test/system/screenshots_test.rb rewrites docs/images/. That test skips without the variable, so CI never takes a picture and the images only change when someone means them to.

Style is Standard, matching splat. There is no house style to learn and no .rubocop.yml to argue with — run bin/standardrb --fix and move on. The two per-file exemptions in .standard.yml carry their reasoning inline.

Tuber is expected on localhost:11300; override with TUBER_URL. Without a provider configured, Spool runs open in development and every request acts as a stand-in agent — see auth.md.