Case study
Special Orders
Customer special orders at The Juilliard Store, from request to pickup
When a customer asks for something the store does not stock, that request now runs through software I built. The Special Orders app manages the full lifecycle, capture, ordering, arrival, reservation, notification, invoicing, pickup, across the store's admin, its point-of-sale registers, and its email.
At a glance
- Role
- Designed and built, growing out of a tool I made for my own counter and receiving work
- Scale
- About 475 special orders a year, around 225 open at any moment
- Reach
- The store's admin, its point-of-sale registers, and customer email
- Stack
- Next.js and TypeScript on the Shopify Admin API, with a Shopify POS extension; moving to Postgres
- Size
- Roughly 87,000 lines of TypeScript
01The problem
Special orders lived in Shopify draft orders used as a raw ledger. A ledger records; it does not operate.
Nothing surfaced which orders were ready to invoice, which reservations were about to lapse, which prepaid orders were overdue. Notifying a customer that an item had arrived was a step someone had to remember. The registers had no view into any of it. Orders did not fail loudly; they fell through quietly.
The clearest measure of that opacity is in the store's own records: 2024 shows 47 tracked special orders for the entire year, 2023 shows six. Once consistent tracking arrived, the count settled near 475 a year. The volume did not change tenfold; the visibility did.
02How it grew
I knew the old workflow's pain points because I had lived them, first taking special orders at the counter and then receiving the merchandise that filled them.
The first version solved my own problem: the old system had no easy way to print an order sheet for incoming items, so I built one to save myself the clicks. Staff saw it, told me what they needed next, and the app grew one request at a time into the system that now manages the whole workflow.
The repository is still named draft-order-printer. I keep it that way as a reminder of where it started.
03What I built
Stack
- Next.js
- TypeScript
- Shopify Admin API
- Shopify POS extension
- Postgres (migration in progress)
A Next.js and TypeScript app on the Shopify Admin API, with one deliberate constraint: no database. Shopify remains the single source of truth.
The app layers workflow state onto the existing ledger using tags and metafields (reserved until, notified, reshelved, fulfills order), so a staff member who never opens my app still sees true state in native Shopify, and there is no second system to drift out of sync.
On top of that ledger:
- Action queues. Every open order sorts itself into a queue: ready to invoice, lapsing soon, lapsed, prepay overdue, resolved. The failure modes of the old workflow became the tabs of the new one. Nothing waits silently.
- Reservations with lapse logic. Arrived items are held for a defined window, customers are notified by templated email, and lapsed holds surface for reshelving instead of occupying the shelf indefinitely.
- Automation on cron. Invoice chasing, stale order archiving, and reconciliation run on schedules, so the follow-up that used to depend on memory depends on a clock.
- A register surface. A Shopify POS extension puts special orders on the sales floor, so staff can check and manage an order at the counter without leaving the register.
- Institutional memory. Products that arrive the slow way (special imports, 84 days rather than 21) are tagged once and remembered forever; the next order of that item warns at entry.
- A metrics dashboard for volume and status at a glance, plus printed invoices, pickup notifications, and a guided new order wizard so entry is consistent regardless of who takes the order.
04The constraint had a ceiling
We started noticing inconsistencies across orders, and the data wasn't as reliable as the store needed it to be. Status was being worked out on every page load from some twenty Shopify facts, in five places that could disagree.
Shopify offers no transactions and no audit trail, and it purges inactive draft orders after a year, a clock pointed at our own records. So I am migrating the app to Postgres, carefully and with dry runs, under one rule: never store a fact both systems could claim.
05What changed
The store runs roughly 475 special orders a year through the app, about nine a week, paced by the school's academic calendar. At any given moment it carries around 225 open orders alongside two years of history.
Every open order is in a queue or it is done; arrival to notification to pickup is a tracked path instead of a memory exercise; and the sales floor answers "is my order in" without a trip to the back office.
Built with Claude Code: roughly 87,000 lines of TypeScript, developed in phases while the previous version stayed in service. The architecture and every design decision are mine.
Screens show Special Orders running on demo data.