Technical Design
The architecture in detail — the shape of the system, each component, the UPS integration, and the security model. Written so a developer could pick it up cold.
Shape
One Cloudflare Worker serving a small static UI plus a JSON API, behind Cloudflare Access. No installs, one URL.
Browser — single page, two input modes (item lookup | manual carton specs) │ JSON ▼ Cloudflare Access (email OTP, ~14-day session, named addresses) ▼ Cloudflare Worker ├─ product lookup ← D1 table loaded from the SAGE export ├─ carton math (ceil(qty ÷ units/carton), weights) ├─ UPS OAuth (client credentials, token cached until expiry) └─ UPS Rating API — "Shoptimeintransit" → all services, negotiated rates + transit Secrets: UPS client id/secret + account number as server-side Worker secrets — never in source or browser
The design deliberately reuses the in-house prototype's module boundaries (lookup → carton math → rate → display) and removes the parts that had stalled it: web scraping, Windows packaging, and per-machine credentials are structurally absent.
Components
1. Front end. Single page, plain HTML/JS. Two entry modes:
- Item lookup: part #, quantity, ZIP, residential/commercial toggle. Shows the carton specs it found, with the option to override them (which is just manual mode pre-filled).
- Manual: carton weight + dimensions (+ carton count), ZIP, residential/commercial.
Output: one table sorted by cost — service name, negotiated cost, transit days / expected delivery. Items without dimensions in the catalog get a clear nudge into manual mode.
2. API routes.
GET /api/item/{part}→ carton specs (drives the show-what-we-found UI).POST /api/quote→{part | specs, qty, zip, residential}→ rate table.POST /api/catalog(admin-gated) → upload SAGE.xlsx; parsed server-side → database.
3. Product table. From the SAGE export: col B = ItemNum; DH/DI/DJ = carton L/W/H; DK = weight/carton; DL = units/carton. ~465 rows — trivial at this scale. A relational store (D1) was chosen over a key-value store so a future quote log is a table away, not a re-architecture.
Load/refresh mechanics: initial seed on build day via the same POST /api/catalog path the button uses. Each upload is parsed server-side, header-validated against the expected column map (a clear error names the missing/moved column, nothing is ingested on failure), then swapped in atomically — stage into a fresh table, verify row count, rename. Catalog metadata (uploaded-at, by whom, row count, items missing dimensions) is stored alongside and surfaced as "catalog last updated."
4. UPS integration.
- OAuth 2 client-credentials against the UPS production environment; token cached until expiry.
- Rating API with the request option Shoptimeintransit — one call returns all available services with cost and time-in-transit, exactly matching the requirement of "transit time and cost for all available service options."
- Negotiated-rates indicator + the shipper account; ship-from ZIP fixed; residential-address indicator when the toggle is set.
- If negotiated rates come back absent, show published rates with a visible marker rather than failing silently.
- The known gate: negotiated rates require the developer app to have Rating-product access and the account linked — this is the permission the original prototype's rating call hadn't yet cleared. Validated in hour 1 of build day with a bare-bones script before anything else is built.
5. Auth. A Cloudflare Access policy on the hostname: named emails, one-time PIN, ~2-week session. The admin route (catalog upload) sits behind a second, tighter policy (an Access group containing the admin emails).
User load/refresh mechanics: the allow-list lives in the Access policy, not in app code. The initial list is seeded from the ~5 emails on build day. Adds/removes are made via the Cloudflare dashboard or API — same-day turnaround; no self-serve user admin in v1. The app itself never sees passwords or manages identity; it trusts the signed Access assertion header for "who is this" (used for the admin gate and the uploaded-by stamp).
6. No logging in v1 (a Product Requirements decision). If revived: a quotes table + a "download CSV" button — about an hour of work.
Credentials & security
- UPS client id/secret and account number live as server-side Worker secrets, set at deploy time; nothing in source control, nothing client-side.
- No Azure/SharePoint surface in v1 at all.
- Credentials are provisioned fresh at handoff and held server-side only — never in the browser, never in the code.
Repo & ownership
- Private Git repository; deployed via
wrangler. The stack is intentionally standard and boring, so any competent shop — or your own team — can pick it up and keep it running. You get the source, and you're never locked in. - The service is set up to be client-owned and RA-configured: moving it to a client-owned account later is a DNS + secrets change, not a rebuild.
Assumptions
- One product table serves both brands.
- SAGE export dimensions are inches / pounds.
- A partial last carton is rated as a full carton (conservative; matches the original carton-count math). Flag if pro-rating is preferred.
- A single fixed origin; no multi-origin or Saturday-delivery options surfaced as needs at design time.
- UPS Rating API volume (~25–30 calls/day) is negligible for every quota involved.
- ~5 named users; the Cloudflare Access free tier (50) is years of headroom.