Skip to content
Mark Garrigan

Work › Luna Coffee

Luna Coffee

The whole online business for a small-batch roaster.

Live lunacafe.com ↗

Role
Client since the early 2000s. Its web developer through every version.
Built
2000s → now
History
Site version 4, admin version 3, built since the early 2000s.
Stack
Express · MySQL · Stripe · EasyPost · Cloud Run
[x] Interface[x] API[x] Data[x] Infra[x] Ops

Story

Luna Coffee has roasted small-batch coffee since 2000, and I've been its web developer since the early 2000s, long before I used GitHub. Today's platform is the fourth version of the site and the third of the admin.

The current version runs the whole online business. Customers shop for coffee, tea, gift boxes, gift cards, drinkware and apparel, then check out for pickup, local delivery (checked by ZIP code) or shipping. Orders are packed, labelled and tracked in the admin, and the local driver delivers them from a phone app. A monthly job writes a Stripe sales report, and product images come from a separate image host.

Over those versions it opened new sales channels for them: online ordering, local delivery, shipping, gift cards and fundraisers.

Results

  • Built and rebuilt since the early 2000s: the site is on version 4, the admin on version 3.
  • Opened online ordering, pickup, local delivery, shipping, gift cards and fundraisers.
  • Live at lunacafe.com.

System, layer by layer

[x] Interface
Express 5 with a small custom file-based router, plus htmx, server-sent events and idiomorph, and Tailwind 4 through Vite.
[x] API
api.lunacafe.com on Express and Sequelize, about 25 models, with a bearer-token client.
[x] Data
MySQL, with Stripe payments and invoices as the source of orders.
[x] Infra
Google Cloud Run, deployed by GitHub Actions on each release. Cloud Storage for images and reports, EasyPost for shipping and Mailgun for email.
[x] Ops
A monthly Stripe sales report that runs as a Cloud Run job, rate limiting, and a customer email at every fulfillment step.

Screens

Screenshot: lunacafe.com, the storefront.
lunacafe.com, the storefront.[Screenshot]
Illustration: Admin: an order and its fulfillment panel.
Admin: an order and its fulfillment panel.[Illustration]

Private tool: Luna delivery app

Private · Private tool, shown as illustrations · 2025-08 → now

A phone app for the local driver. It isn't public, so the screens here are drawn from its real templates with invented addresses.

  • Shows every order waiting for local delivery as a numbered stop on a map
  • Pulls those orders straight from Stripe payments and invoices tagged for delivery
  • Drag stops to reorder the route
  • Tap a stop to confirm it's delivered
  • Confirming marks the order delivered in Stripe, with a timestamp
Illustration: The map of pinned stops, the stop list, and confirming a delivery.
The map of pinned stops, the stop list, and confirming a delivery.[Illustration]

Decisions

  1. 01

    Stripe is the order book

    Orders come straight from Stripe payments and invoices. The app keeps no separate order ledger.

  2. 02

    Pages are just files

    Pages are HTML files with optional data loaders, routed by a small custom handler. Adding a page means adding a file.

  3. 03

    Every fulfillment gets its own path

    Pickup, local delivery (ZIP allow-list, $6 charge), shipping with EasyPost labels across UPS, FedEx and USPS, gift cards, and manual orders. Each has its own panel and its own customer emails.

In the box

  • Shop by category, product pages and a cart drawer
  • Pickup, local delivery by ZIP, or shipping
  • Stripe checkout
  • Twelve café menu pages, contact and fundraising
  • Admin: orders, subscriptions, packing slips
  • Fulfillment panels: pickup, delivery, shipping, parcel, tracking, gift card
  • Customer emails from confirmation to delivered
  • Products in six categories, café hours, sales report
  • Delivery app for the local driver
  • Monthly Stripe sales report job