• Aug 16, 2026
  • 6 min read

Architecture Decision Records for a One-Person Business

Architecture Decision Records sound like something for teams with architecture review boards. I run a one-person business and I write them anyway. Not for anyone else: for the version of me who opens the repository six months from now and wonders why the forms go through a Worker instead of a Pages Function.

This post explains the format I settled on, shows the three records behind my lead-capture work, and covers the part that turned out most valuable: a checklist I’m allowed to mark “not needed”.

Why bother when you’re alone

When you work alone, nobody asks “why did we do it this way?” in a meeting. The question still exists; it just arrives later, from you, with less context.

Three things go missing over time:

  • The alternatives. The code shows what I chose, never what I rejected.
  • The constraints. “Homelab must never receive traffic” isn’t visible in a Python file.
  • The order. Which decision depended on which.

An ADR captures all three in a page.

The format

Each record is one Markdown file in architecture/adr/, numbered: 0001-central-forms-worker.md, 0002-retention-per-kind.md, and so on.

SectionWhat goes in it
StatusAccepted, Superseded by 000X, … plus a date
Builds onLinks to earlier records it depends on
ContextThe situation and the forces at play
DecisionWhat we do, as numbered rules
AlternativesOptions considered, and why not
ConsequencesWhat becomes easier, what becomes our job
RolloutSteps, each marked done with a date and version
ChecklistDefinition of done before something goes live

Two rules keep the collection trustworthy:

  1. Never rewrite a decision. If it changes, a new record supersedes the old one. ADR 0002 amends 0001 instead of editing it.
  2. Rollout and checklist are the only living parts. I tick items and add “done 2026-10-04 (version 0.4.0)” so the record doubles as a status page.

Three real examples

0001 — One central forms Worker

Context: three sites, three ways of capturing leads (a Pages Function, a third-party form service, nothing). Decision: one Cloudflare Worker, attached as a zone route on each site (site.com/api/forms/*), so forms stay same-origin. Leads go to Workers KV, alerts go to a self-hosted ntfy. Alternative rejected: a Pages Function per site, because copies drift and only work on Pages. The build is described in one forms Worker with same-origin routes.

0002 — Retention per kind

Context: 0001 set a 24-month expiry on every lead. Problem: that would silently delete newsletter subscribers who never unsubscribed. Decision: retention depends on the form kind: questions expire after 24 months, subscriptions don’t. A short record, but exactly the kind of thing I would otherwise “fix” back later. Details: retention as a KV TTL.

0003 — Do-it-yourself email

Context: capture worked, but everything after it was missing: double opt-in, unsubscribe, a suppression list, drips. Decision: confirm and unsubscribe live in the Worker (they must work when the homelab is down); drips live in the homelab behind a swappable sender. Alternatives rejected: listmonk (weak at drips, another service to run) and Mailpit as a sender (it captures mail and delivers nothing). Background: Mailpit vs Mailtrap vs listmonk.

The checklist that saved a week of work

ADR 0003 has a “must-have before any production send” checklist. One item read:

Existing single-opt-in subscribers receive one re-confirmation email; unconfirmed after 30 days are dropped.

I built it: re-confirmation sequences, a 30-day drop job, a check that a late click still wins, tests. Then, while summarising, I looked at who those “existing subscribers” actually were. Every lead in the store was one of my own test submissions. There was nobody to re-confirm.

The honest fix was not to delete the code or pretend the work mattered. It was to mark the item in the record:

- [x] ~~Existing single-opt-in subscribers receive one re-confirmation …~~
      — not needed: every lead before double opt-in was the owner's own test.
      Test leads are deleted before go-live. Tooling stays, dormant, tested.

The lesson I added to my notes: check who the legacy users really are before building for them. A checklist written at decision time describes an imagined situation. Ticking items honestly, including “not needed”, keeps it connected to the real one.

ADRs plus learning notes

ADRs record what and why. They’re the wrong place for “wrangler keeps its live login in a different folder”. For that I keep a z-learnings.md next to each project (the z- sorts it last in the file listing):

ADRz-learnings
AnswersWhy is it built this way?What surprised us while building it?
ChangesNever (superseded instead)Appended continuously
GranularityOne decisionOne lesson per bullet
Example”Confirm links open a page; only POST confirms""Mail scanners prefetch every link in an email”

The learning notes also hold a timeline table: step, commit hashes, result. That table points into the git history, which is the third leg.

Commit messages as the timeline

I write commit messages for the same future reader: a subject that names the component and version, a body that lists what changed and how it was verified.

leads-forms 0.4.0: double opt-in, confirm/unsubscribe links, suppression

- Signed stateless tokens: confirm links expire after 7 days …
- 11 new tests (32 total)
DOI is off for visitors until DOUBLE_OPT_IN_SITES is set.

Combined with the ADR’s rollout section (“done, version 0.4.0”) and the learning timeline (commit bd100e3), any decision can be traced from the reasoning to the exact code that implemented it.

What it costs

Honestly, little. A record takes 20–30 minutes while the decision is fresh. Learning bullets take a minute each. The payoff comes the first time I’m about to undo a decision and the record tells me why I made it.

Takeaways

  • Write ADRs for your future self; that’s a big enough audience.
  • Keep decisions immutable and supersede them; keep rollout and checklists alive.
  • Mark checklist items “not needed” when reality says so, and say why.
  • Pair ADRs with a running learnings file and descriptive commit messages: why, what surprised you, and exactly what changed.