What It Is

The problem it solves: replacing a Saturday wash process that was a bit chaotic, not fair to the staff or the members. Members physically arrive at the garage, scan a QR code, pick a time slot, and receive SMS updates when their car is up next and when it's ready. Staff manage the queue from their phones without any manual data entry.

For Members

Scan QR → pick a slot → enter name, car, phone → receive confirmation text with a link to manage the booking. Get texted when up next and when done.

For Staff

Open queue → watch slots fill in real-time → tap each slot to advance it through Queued → Up Next → Washing → Done. SMS and Slack fire automatically.

The Five Views

URLViewWho uses itAccess
/ (default)Member queueMembers at garageGPS within 500m or QR code
/?qr=ooog-saturday-washMember queue (QR)Members who scanned QRGPS within 200m (tighter)
/?view=staffStaff dashboardStaff running the washStaff PIN
/?view=embedRead-only widgetOOOG Webflow members pageNone
/?manage=TOKENBooking managementMember who bookedUnique token in SMS

Architecture

Everything runs on Cloudflare infrastructure. There is no traditional backend server. The Worker is a serverless function that wakes up only when a request hits it.

Browser (member/staff phone) ↓ HTTPS Cloudflare Pages —— serves saturday-wash.html ↓ fetch() ooog-member-tools Worker —— handles all business logic ├── Cloudflare KV (WASH_KV) live queue — 15 slots, current status ├── Cloudflare D1 (WASH_DB) permanent archive — every completed wash day ├── SimpleTexting SMS to members ├── Slack Webhook staff notifications └── Monday.com end-of-day summary only (not real-time)
Key principle

Monday.com is not used for real-time data. The live queue lives entirely in Cloudflare KV. Monday only receives one write at end-of-day reset — it's a reporting dashboard, not a data store.

Why This Stack

ComponentWhy chosenAlternative that was rejected
Cloudflare KVSub-millisecond edge reads, no rate limits for 15 slots, single JSON blob = single read/writeMonday.com — rate limited at 30 calls, caused 429 errors
Cloudflare D1SQL queries on wash history, same infrastructure, free tierJSONBin — no SQL, no querying
Single HTML fileNo build step, no npm, instant deployment, zero dependenciesReact/Next.js — unnecessary complexity

How It Works — The Core Concept

The entire live queue is one JSON blob stored in Cloudflare KV under the key WASH_QUEUE_STATE. It contains all 15 slots and their current data. Every action in the app reads this blob, updates the relevant slot, and writes it back.

{
  "sessionDate": "2026-06-07",
  "slots": [
    {
      "id": 1, "slotTime": "10:00 AM", "statusIndex": 4,
      "memberName": "Sarah M.", "vehicle": "2022 Porsche 911 GT3",
      "memberPhone": "5865550101", "droppedOffAt": "2026-06-07T14:05:00Z",
      "washStartedAt": "2026-06-07T15:05:00Z",
      "washDoneAt": "2026-06-07T15:38:00Z",
      "bookingToken": "k4xm92bq", "notifiedUpNext": true, "notifiedDone": true
    },
    ... 14 more slots
  ]
}

Slot Status Values

IndexLabelDisplayed asSlot number colour
1Up NextUp NextAmber
2QueuedQueuedLight grey
3In ProgressWashingGreen
4DoneDoneFaded (28% opacity)
5AvailableAvailableBlack
6CancelledCancelled
7UnavailableUnavailable

Member Booking Flow

  1. Scan QR code at the garage — or navigate directly to the URL. GPS check runs immediately on page load (prompts browser permission).
  2. View the queue — available slots show in black with "Tap to book". Claimed slots show "Claimed". Time-expired slots show "Unavailable".
  3. Tap an available slot — geo validation runs (200m if QR, 500m if direct URL). If not within range, an error message appears. No form opens.
  4. Fill in the form — name, vehicle, mobile number, optional notes for staff.
  5. Submit — worker validates slot is still available, generates a booking token, saves to KV. If slot was taken in the meantime, an error message appears.
  6. Receive confirmation SMS — "Your OOOG wash is booked — Slot X · est. TIME. Need to swap, cancel or message staff? [manage link]"
Slot expiry

Slots grey out 30 minutes after their estimated start time — on Saturdays only. A member cannot book an expired slot. Staff can still manually add to any slot regardless of time.

Staff Queue Management

Opening the Day

  1. Go to /?view=staff — enter the staff PIN.
  2. Tap Est. Wait / Car chip (top left) — set the expected wash duration (20, 30, 45, or 60 min). This drives the estimated wait time members see.
  3. Tap Open Queue — members can now see and book slots. Nothing is automated — the queue only opens when staff opens it.

Working the Queue

Tap any slot row to open the detail sheet. Available actions depend on the slot's current status:

StatusAvailable actionsAutomatic triggers
QueuedMark Up Next, Start Wash, Edit Details, Swap Key Tag, Cancel
Up NextStart Wash, Edit Details, Swap, CancelSMS sent to member on Mark Up Next
In Progress (Washing)Mark Done, Edit Details, CancelWash start timestamp recorded
DoneRead-onlySMS sent to member, wash done timestamp + duration recorded
AvailableAdd Member Manually

Admin Tab

Accessible as a tab within the staff view. Provides: summary stats, force status override for any slot, manual SMS resend, and the weekly reset.

Reset blocked if washing

The Reset Queue button is blocked if any slot is marked In Progress (Washing). Mark the car Done or Cancelled first.

Member Booking Management

Every booking generates a unique 8-character token stored in KV. The token is sent to the member in their confirmation SMS as a link: https://ooog-wash-queue.pages.dev/?manage=TOKEN

From the manage page, members can:

The manage link expires (shows "Your wash is complete") when staff marks the slot Done or Cancelled. Time of day is not a factor — expiry is purely status-based.

Saturday Morning Runbook

TimeActionNotes
Before 10 AMOpen ?view=staff, set wait time, tap Open QueueNothing is automated — must be done manually
10 AM–2:30 PMMembers scan QR and self-register as they arriveMonitor slot list in real-time
ThroughoutAdvance each slot: Queued → Up Next → Washing → DoneSMS fires automatically at Up Next and Done
When doneClose Queue (stops new registrations)Existing bookings are unaffected
End of dayAdmin tab → Reset QueueArchives to D1, sends to Monday, creates 15 fresh slots

Data Layer

Live State — Cloudflare KV

KV holds the current Saturday's queue as a single JSON blob. Reads are edge-cached and near-instant. There are no rate limits relevant to this use case.

KV KeyValuePurpose
WASH_QUEUE_STATEJSON blob of 15 slotsThe entire live queue
WASH_QUEUE_OPEN"true" or "false"Whether members can book
WASH_WAIT_MINUTESNumber as stringStaff-set estimate per car

Permanent Archive — Cloudflare D1

D1 is a SQLite database. One record is written per active slot at end-of-day reset. Never modified after writing.

SELECT slot_num, member_name, vehicle, status,
       dropped_off_at, wash_started_at, wash_done_at, wash_duration_mins
FROM wash_sessions
WHERE session_date = '2026-06-07'
ORDER BY slot_num;

The wash_duration_mins column is the number of minutes between wash start and wash done — calculated automatically at reset time.

Data Journey

Member books → KV slot updated (statusIndex: 2, member data written) Staff marks Up Next → KV slot updated (statusIndex: 1, notifiedUpNext: true) Staff starts wash → KV slot updated (statusIndex: 3, washStartedAt stamped server-side) Staff marks Done → KV slot updated (statusIndex: 4, washDoneAt stamped server-side) End of day reset → D1 record written (all fields + duration calculated) → Monday.com item created (ownership visibility) → KV reset to 15 fresh Available slots

Notifications

SMS (SimpleTexting)

Important

The SimpleTexting API endpoint is https://app2.simpletexting.com — note app2, not app. The app URL redirects and strips the Authorization header, causing silent 401 failures.

When sentMessageDedup logic
Member books (self or staff add)"Your OOOG wash is booked — Slot X · est. TIME. Need to swap, cancel or message staff? [link]"None — always sends
Staff marks Up Next"You're up next at OOOG! Slot X. Head over when ready. 🚗"notifiedUpNext flag prevents resend
Staff marks Done"Your car is ready for pickup at OOOG! Keys at the front desk. — The Team 🚗"force=true bypasses dedup
Slot swapped"Your OOOG wash slot has been updated — Slot X · est. TIME. Manage: [link]"None

Slack (#saturday-wash)

Every meaningful action posts to Slack: booking, Up Next, Washing, Done, cancellation, swap, force override, queue open/close, reset. Staff can monitor the full day without opening the app.

Geo-Gate & QR Code

The geo check prevents members from booking from home. It runs twice — once client-side (fails fast before form opens) and once server-side (worker validates on every QUEUED registration).

Access methodRadius checkedWhere
QR code scan (?qr=ooog-saturday-wash)200mClient + Server
Direct URL (no QR)500mClient + Server
Staff manual addBypassed entirelystaffIntake: true in payload

The QR code URL posted in the Cole Street garage:

https://ooog-wash-queue.pages.dev/?qr=ooog-saturday-wash

The token ooog-saturday-wash is hardcoded in both the HTML and the worker. To change it, update both const QR_TOKEN values and regenerate the QR code.

GPS accuracy on mobile

Phones use GPS + WiFi + cell towers for location. Indoor GPS can be inaccurate (50–300m). If a member is physically at the garage but gets a "too far" error, they should step outside briefly or speak to staff for a manual add. Accuracy is checked — GPS readings with accuracy > 150m are rejected with a "weak signal" message.

Worker API Routes

RouteMethodPurpose
/wash-slotsGETRead full queue state from KV
/wash-slots/:idPOSTUpdate one slot (id = 1–15)
/wash-photoPOSTAnthropic Vision OCR for keychain number
/wash-notifyPOSTSend SMS via SimpleTexting
/wash-slackPOSTPost to Slack webhook
/wash-adminPOSTopen / close / reset / init / pin-check
/wash-settingsPOSTUpdate WASH_WAIT_MINUTES in KV
/wash-manageGETLook up booking by token
/wash-managePOSTSwap / cancel request / message
/potluckGET / PUTPotluck signup list (unrelated — do not touch)

Deployment

HTML App (Cloudflare Pages)

  1. Edit saturday-wash.html (the source of truth)
  2. Run: cp saturday-wash.html deploy/index.html
  3. Upload the deploy/ folder to Cloudflare Dashboard → Workers & Pages → ooog-wash-queue → Upload assets

Worker (Cloudflare Worker)

  1. Edit ooog-member-tools-worker.js
  2. Open Cloudflare Dashboard → Workers & Pages → ooog-member-tools → Edit Code
  3. Select all → paste full file contents → Save and Deploy
  4. Ensure format is set to ES Module (not Service Worker)
After first-ever deployment

Run this once to initialise the KV queue state:
POST /wash-admin {"action":"init"}

Secrets Required in Worker

Secret nameWhere to get it
SIMPLETEXTING_API_KEYSimpleTexting → Account → API
SIMPLETEXTING_SENDERSimpleTexting outbound number, digits only
SLACK_WASH_WEBHOOK_URLSlack → Apps → Incoming Webhooks
ANTHROPIC_API_KEYAnthropic Console
MONDAY_API_KEYMonday.com → Admin → API
WASH_STAFF_PINSet by admin — any numeric PIN
JSONBIN_API_KEY / BIN_IDJSONBin (for potluck — do not change)

Common Issues & Fixes

Member says they didn't get a text

  1. Check the phone number was entered correctly during booking.
  2. Check SimpleTexting account → Messages for any send errors.
  3. Use the Admin tab → Manual SMS Resend to force-send the Up Next or Done text.
  4. If booking confirmation is missing, the slot can still be worked normally — texts only require a valid phone number in the slot.

Member can't book — "too far from garage" error

  1. Ask them to step outside the building — indoor GPS is often inaccurate.
  2. Ask them to check Location is enabled for their browser (Chrome: tap padlock → Site Settings → Location → Allow).
  3. If still blocked, use Staff manual add on their behalf.

Staff can't manually add a member — nothing happens

This was a known bug (fixed). Ensure the latest worker and HTML are deployed. The issue was a reference to a removed HTML element (ma-keytag) that silently crashed the submit function.

Queue shows "ask staff" for estimated wait time

Staff haven't set the wait time, or the embed view is showing stale data. Staff should tap the Est. Wait / Car chip and set a value. This was also a fixed bug where the embed view didn't read the wait time correctly — ensure latest HTML is deployed.

Worker returns 429 (rate limited)

This cannot happen with the current architecture — 429s were from the old Monday.com integration. KV has no relevant rate limits for this use case. If a 429 appears, check which API is being called.

Reset fails — "Cannot reset — wash in progress"

One or more slots has status In Progress (Washing). Open the staff view, find the slot, and mark it Done or Cancelled before running reset.

D1 archive shows no data after reset

Check the D1 console — if wash_sessions table exists but has no rows, run the reset again with an active booking. If table doesn't exist, run the CREATE TABLE SQL in D1 console (see CLAUDE.md).

What to Watch

Before each Saturday

Verify SimpleTexting account has credits. Confirm Slack webhook is active. Check Cloudflare Worker is deployed and responding (GET /wash-slots should return slots).

During the day

Monitor the #saturday-wash Slack channel — every action posts there. If Slack goes quiet when you expect activity, something may be wrong.

After reset

Confirm D1 has new rows for the day. Confirm KV shows 15 fresh Available slots (GET /wash-slots). Confirm Monday.com received EOD summary items.

Monthly

Check Cloudflare Worker request count (free plan: 100K/day). Check SimpleTexting message credits. Rotate WASH_STAFF_PIN if staff changes.

Rotating Secrets & Keys

All secrets are stored as encrypted environment variables in the Cloudflare Worker — never in the HTML or code. To rotate a key:

  1. Get the new key from the relevant service (SimpleTexting, Slack, Anthropic, Monday).
  2. Go to Cloudflare Dashboard → Workers & Pages → ooog-member-tools → Settings → Variables.
  3. Edit the encrypted variable, paste the new value, save.
  4. No redeployment needed — the worker picks up the new value immediately on the next request.
If the QR token is ever compromised

Change const QR_TOKEN = 'ooog-saturday-wash' in both saturday-wash.html and ooog-member-tools-worker.js. Redeploy both. Generate a new QR code with the new token. Replace the physical QR in the garage.

Staff PIN

The staff PIN is set as WASH_STAFF_PIN in the worker secrets. Changing it in the dashboard takes effect immediately — no redeploy needed. The PIN gates direct access to /?view=staff. The Webflow page Memberstack gate is the primary access control.



OOOG Saturday Wash Queue · Technical Guide · Last updated June 2026