Sanyu Botanicals
A physical product, sold online
The only build on this floor with a factory — and the factory is a kitchen. Herbal balm and scalp serum, made by hand in small batches, with the software wrapped around the part that does not scale.
- 149 commits
- 2026-05-22 → 2026-07-27
- TypeScript 97.3%
- private repository
Every other build here is software all the way down: the thing sold and the thing built are the same object. Sanyu is not. There is a stove, a whisk, dried herbs, a mixing bowl, jars that get filled one at a time, and a person whose hands are the bottleneck.
That inverts what the software is for. A storefront for a digital product exists to remove friction from an infinite supply. A storefront for a hand-made one exists to protect a finite supply — from over-ordering, from stock that says available when the shelf is empty, from a delivery promise that quietly assumes a warehouse.
4 decisions
What the constraint forced.
Each of these is a choice with a reason attached. The reason is the part worth reading — a stack list tells you what was used, and nothing at all about the judgment that put it there.
- 01
Photograph the making, not just the jar.
The product library leads with the whisk in the bowl, the herbs on the tray, the row of filled jars cooling on a counter — and only then the finished bottle held up in a garden. For a small-batch botanical the provenance is the product; a clean pack shot on white says nothing that a thousand identical brands are not also saying. It is a merchandising decision that happens to be made in a photo folder rather than in code, and it is the one that matters most.
- 02
Trim every credential before you use it.
Three separate commits here exist to strip whitespace off a payment passphrase and off environment variables before signing. An invisible trailing space in a dashboard field produces a signature mismatch that looks exactly like a wrong key, and it costs an afternoon every time somebody meets it fresh. The same bug appears in the K53 and VarsityOS journals — so it is not a gateway quirk, it is what happens whenever a secret is copied by a human.
- 03
Lazy-initialise the mail client so a missing key cannot fail the build.
The transactional-mail client was being constructed at module scope, so a build without that key crashed at compile time instead of failing at send time. Moving construction inside the call turns a deploy-blocking error into a runtime one with a real message. The general rule: anything that reads an environment variable at import time makes that variable a build dependency, whether or not the feature is switched on.
- 04
Send from the domain that is verified today, not the one that will be.
While DNS propagated for the new sending domain, order confirmations went out from an already-verified one. Unglamorous — and the alternative is a launch window in which every confirmation email lands silently in spam, which from the customer side is indistinguishable from never having sent it.
The part that survives the build.
Physical stock needs a different definition of "available" than digital stock.
A template can be sold a thousand times tonight. A balm cannot be sold past the number of jars that exist, and that number is a function of somebody’s Saturday. Software that treats the two identically will eventually accept an order it cannot fill, and that costs more trust than a sold-out label ever does.
A brand with a real product photographs better than a brand with a real budget.
The strongest assets in this build were taken on a phone, in a kitchen and a garden, with no lighting kit. They work because the thing in frame is genuinely being made. Not an argument against production value — an argument that provenance outranks polish when the product is hand-made.
Built with
- Next.js
- TypeScript
- Supabase
- PayFast
- Resend
- Cloudinary
- Vercel
Measured
- commits
- 149
- last commit
- 2026-07-27
Written from sanyubotanicals repository — commit history and the product library. The dossier is composed from the journal rather than quoted out of it — the raw sections stay behind the review gate described in docs/THE_FORGE.md §4, and every figure above is read from the GitHub API at build time.
