A few weeks ago I shipped Out for Never, a web app whose entire premise is: what if the fun part of online shopping — the checkout, the tracking page, the "it's on the way" dopamine — existed without the spending?
You enter the thing you nearly bought, a price, how strong the urge was. You run a theatrical fake checkout. Then a fictional logistics network takes over and ships your imaginary parcel to nowhere, with courier lore, absurd incidents and manual reroutes along the way. You keep the money.
This post is for the dev crowd: the small stories of it being fun, then the parts I actually had to think about.
The fun stories first
The pigeon that wouldn't stop. One courier is Pip, of Pigeon Express — "fast in a straight line, has never chosen one." You pick a courier when you create a parcel, and their pickup line, mood and bio all render on the tracking page. Watching a pigeon with 3,500+ documented courier failures carry your imaginary €89 headphones through Luxembourg is genuinely better than the real tracking page.
The reroutes are deterministic chaos. Every parcel gets a seed. Incidents, "nearby updates" and final delays are all picked from static arrays via a seeded PRNG — so a given parcel always tells the same story, but different parcels diverge. One of mine got "Paused for a tiny parade — seven ducks and an admirably small brass band have priority." You can also manually reroute it to a parade, an extra moon orbit, or duck traffic.
The friends who don't know it's a joke. The paid tier sends a cinematic tracking journey with a real personal message at the end — "For someone lovely", custom reveal chapters, no address, nothing ships. Sending one is the most chaotic thing I've done all year.
The engineering
It's a framework-free SPA (index.html + app.js + styles.css, no build step) served by a Cloudflare Worker (src/worker.js) running as Pages advanced-mode. Around 4,400 lines total. Here are the bits worth stealing.
1. Every parcel is an AES-GCM sealed token
A tracking link like /track/<token> is a self-contained, encrypted parcel. There's no server-side parcel table — the token is the parcel. The Worker seals it like this:
// derive a purpose-scoped key from the master secret
const digest = await crypto.subtle.digest(
"SHA-256",
encoder.encode(`out-for-never:v2:${purpose}:${secret}`),
);
const key = await crypto.subtle.importKey("raw", digest, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
const iv = crypto.getRandomValues(new Uint8Array(12));
// AAD binds the ciphertext to the version + purpose, so a token
// minted for one purpose can't be replayed against another
const additionalData = encoder.encode(`out-for-never:v2:${purpose}`);
const encrypted = await crypto.subtle.encrypt({ name: "AES-GCM", iv, additionalData }, key, plaintext);
// wire format: v2.<base64url(iv || ciphertext)>
The details that matter:
-
Purpose-separated keys —
parcel,stripe-draftandmembershiptokens each derive a different key from the same master secret. A gift token can't be replayed as a membership token. - AAD as a poor-man's type system — the version + purpose string is passed as AES-GCM additional authenticated data, so even a ciphertext minted with the right key but the wrong purpose fails to authenticate.
-
Canonical base64url only — on open, the decoder re-encodes the bytes and rejects the token if the string isn't canonical (
bytesToBase64Url(bytes) !== value). That kills the classic mutability-token bug where two encodings decode to the same payload. - Bounded input — tokens must be 29–14,000 bytes; all user text is normalized and length-limited server-side; locked gift answers are stored only as a hash inside the token.
I wrote this deliberately, because the free tier has no accounts. The link is the identity, so the link has to be unforgeable.
2. The logistics are deterministic, not random
The client has static arrays — COURIERS, INCIDENTS, NEARBY_UPDATES, FINAL_DELAYS, REROUTES — and picks from them with a seeded PRNG:
function seededPick(list, seed, offset = 0) {
const random = seededRandom(Number(seed || 1) + offset);
return list[Math.floor(random() * list.length)];
}
Progress itself is pure time math against two timestamps baked into the parcel:
const progress = clamp((now - startsAt) / (arrivesAt - startsAt), 0, 1);
The SVG route, the courier marker, the timeline stages and the status text all derive from that one 0..1 value. The ETA countdown never hits zero — it renders NEVER at completion. Non-delivery is the only guaranteed delivery.
This design buys you a lot for free: parcels are stateless (shareable, cacheable), the story is stable across reloads and recipients, and there's no progress row to store anywhere.
3. The voucher system lives in KV
Since the whole thing is one Worker, discount codes are just KV records — no Stripe coupon API needed for the product's own promo flow. A voucher is ofn:voucher:<CODE> in the OUT_FOR_NEVER_KV namespace:
{ "percentOff": 50, "maxUses": 25, "uses": 0, "expiresAt": 0 }
The Worker validates (percentOff 0–100, uses < maxUses, not expired), applies it to the computed price, and bumps uses when the order actually confirms. A scripts/voucher.mjs CLI creates, lists and deletes them against the Cloudflare API. Fun side effect: a 100%-off voucher is how you test the paid flow without touching real money — the Worker detects totalCents <= 0 and issues the gift directly.
4. The vault is aggressively local
Every avoided purchase lands in a localStorage vault — money kept per currency, streaks, a 28-day heatmap, category charts, CSV/JSON export. No account, no analytics, nothing phones home. That was a product decision as much as a technical one: a "spend less" tool that tracks you would be a joke at its own expense.
Try it
The core ritual is free, no signup, no card. Build a fictional parcel, pick a courier, and watch it get lost somewhere south of reason.
For dev.to readers: the launch voucher DEVTOFOREVER gives 50% off any premium fictional gift (the shareable tracking story you send to someone). 25 redemptions, drop it in the Voucher code field on the Send page.
And if you send one to someone who also doesn't need a fourth pair of headphones — that's the whole point.
Top comments (0)