Secret Key Mint
Minted metal coins carrying a QR code the owner can repoint forever — with the scan metrics to prove it worked.
Secret Key Mint sells a physical object with a software promise attached: a precision-minted coin — a "Key" — whose engraved QR code resolves through infrastructure we run rather than pointing at a URL that will eventually rot. The destination is a database row, so a client can repoint a coin that is already in someone's pocket, at any time, forever. Behind the splash sits the whole business: a QR designer that refuses to export a code it could not itself scan, a production artwork path that measures the code in millimetres before a die is cut, a redirect engine with per-slug scan analytics, customers, products, invoices, and a client portal. One Go binary, one SQLite file.
- Go 1.25
- SQLite (WAL)
- html/template
- Vanilla JS
- qr-code-styling
- rsc.io/qr
- WebAuthn
- Postmark
- ImageRouter
- OpenRouter
- Google Cloud Storage
- Apache
- systemd
- just
Secret Key Mint mints custom physical coins — "Keys" — that carry a QR code pointing at infrastructure we operate. That single layer of indirection is the entire product. A printed QR code is a promise you cannot keep: the link dies, the campaign ends, the company rebrands, and the object becomes litter. A Key's destination is a row in a database, so the coin in someone's hand can be repointed years after it was struck.
Works forever, by design
The hard part of selling a permanent object is what happens when the customer relationship ends. The answer here is a deliberate one: a slug marked minted — meaning a physical coin exists that carries it — can never hard-404. Forwarding is free and permanent. The monthly fee buys powers: scan metrics, self-serve repointing, custom branding on the resting page. Let it lapse and the coin degrades gracefully to a branded resting page that still logs the scan; it never becomes a dead link. The whole decision is a single pure function with unit tests around it, because it is the one behaviour that would betray a customer holding metal.
The QR designer that refuses to lie
- A scan-verify gate. Export is blocked until the
rendered code is decoded back —
BarcodeDetectorwhere the browser has it, ajsQRfallback where it does not. A styled QR that renders beautifully and does not scan is the single most expensive mistake available in this business, because you only discover it after a thousand coins are struck. - Engrave and print modes — different module shapes, gradients, and quiet-zone handling for laser-engraved metal versus ink on paper.
- Forced error correction H whenever a logo is embedded, so the mark cannot eat the payload.
- SVG plus 2048px PNG export, archived to object storage as the artefact of record for that coin run.
Artwork a die sinker can cut
The browser designer shows an operator what a coin will look like; the file a bench actually works from is generated server-side and measured in millimetres. User units in the exported SVG are millimetres, so "how wide is a module" is answered by clicking it in a drawing program rather than by trusting us. Every module is a closed subpath, and horizontal runs are merged into single rectangles — identical geometry, roughly a tenth of the nodes, fewer places for a CAM import to round something off. Below 0.80 mm per module the panel is refused rather than flagged: enamel sitting in a struck recess is not ink on paper, and a code that passes on a proof sheet is a thousand unscannable coins in a box.
The payload comes from one builder — HTTPS:// plus the
domain and slug in upper case, because a bare domain scans as search
text on a real phone — with the domain threaded from config and
hardcoded nowhere. Nothing leaves the toolchain undecoded: every
ordered panel is read back at 3 px per module, and the composite
build refuses to leave a file on disk it could not itself scan. The
admin Key page renders the same payload as a coin that spins and
dwells face-on so a phone can read it off the screen; clicking holds
it still.
A business, not a link shortener
- Scan analytics per slug — asynchronous logging off the redirect hot path, bots and dry-run test scans excluded from the counts, CSV export.
- Funnel to customer to invoice. Quote requests arrive with attachments (sniffed server-side, disk authoritative, mirrored to object storage), thread themselves by email address, and promote into customer records that carry their whole history.
- Products and invoices with cents-based pricing and frozen unit prices on issued lines, so editing a price list never rewrites history.
- A client portal where a customer sees their own Keys, repoints them, and reads their own scan metrics — gated by entitlement, with no self-registration anywhere in the system. Access is granted, never claimed.
- An image studio behind a hard cap. Generation runs through a small registry of providers, and no paid call happens without checking the spend ledger against a budget ceiling — a malformed ceiling falls back to the default, never to unlimited; failed calls are recorded at zero; free models keep working after the cap is reached. Output is pulled off the provider's expiring CDN link immediately into a bucket with public access prevention enforced, addressed by a key derived from its ledger id so no request path ever lists the bucket.
- Email both directions — Postmark outbound, plus a token-gated inbound webhook that files replies into the admin inbox.
How it's built
One Go binary and one SQLite file
in WAL mode. Rendering is html/template; the front end
is hand-written CSS and JavaScript with no framework, no bundler and
no node_modules in the serving path. Migrations are
additive-only — columns are never rewritten or dropped, because a
coin struck last year still has to resolve. Deployment is a systemd
unit behind Apache on a single host; just ship runs the
tests, builds, restarts and health-checks it. Client IP is read from
the hop the proxy wrote — the last X-Forwarded-For
entry — rather than the first, which a caller writes, so the rate
limiters cannot be opted out of from outside.
The codebase enforces its own conventions in CI: files stay under 150 lines and functions under 30, a headless browser harness probes every admin page for console errors and axe-core accessibility failures, and a static gate scans the whole tree for words that must never appear in anything a client could see. A test reads the route table out of the entrypoint and fails the build if any admin route is missing its session check.
Straight from the source
The project's own README.
Rendered in place — every link, image, and code block carried over from the repo. The page below is what a contributor would see opening the project for the first time.
Secret Key Mint
secretkeymint.com — we mint custom physical coins ("Keys") that carry a QR code you control: the code points at a redirect we host, so the destination can change at any time — long after the metal is struck — with scan metrics included.
Single Go binary, SQLite, html/template, vanilla CSS/JS. No frameworks, no build pipeline. Public splash + contact/quote funnel + admin panel.
Live: https://secretkeymint.com
Docs
Start at docs/quick-start.md. Roadmap/status in docs/PROGRESS.md. Per-feature docs in docs/feature/. Founding directives in docs/project/.
Commands
just # list all recipes
just dev # run locally with race detector
just ship # test + build + restart service + health check
just check # tests + lint + scrub gate
Private repository — all rights reserved.
Build something like this
Want a tool like this for your shop?
We've shipped this kind of thing before. Twenty-minute intro call, no slides.