Skip to content

Automations

PreviousNext

Every automated flow in Space — what starts it, which states it moves through, what runs by itself and what it records.

This page traces every flow that moves on its own or on a single click. Read it before you read a number in Metrics: a metric only means something once you know which step wrote it.

How automation runs here

Space has no scheduler: no cron trigger in wrangler.jsonc, no queue worker, no email. Work that happens "by itself" happens in one of three ways.

MechanismWhen it firesExamples
Lazy on readEvery time a page or query evaluates a conditionBooking and ticket holds expire, program and opportunity deadlines close, packages expire
Daily routinesOn the first /dashboard/desk page load of each Port Sudan dayRent invoices, forgotten check-outs, bookings completed or cancelled
In the transactionInside the action that the guest or staff member triggersWaitlisting at capacity, room clash lock, free tickets issued instantly

Booking and café events also send notifications by themselves — the in-app bell, push to the phone, WhatsApp and email, each when configured; see Notifications. The other outbound links are WhatsApp deep links (wa.me): the guest or the staff member taps one and sends the message themselves, and no tap is recorded.

Legend

States in green are reached by the system. Grey states are set by a person. The second row lists the states a record can leave the path to.

Booking

A guest picks a product and time on /book. createOnlineBooking quotes a price when there's a set rate, takes a Postgres lock on the room, checks for clashes, and sends the guest to their booking page.

PENDINGPENDING + Bankak refCONFIRMEDCOMPLETED
orREJECTEDCANCELLED
StepWhoRecorded
Request created; a priced room is held for ticketHoldMinutes (default 120)GuestcreatedAt, amount, holdUntil
Guest submits their Bankak transaction numberGuestbankakReference, paymentSubmittedAt
Staff verify the transfer → charge and payment written to the ledgerDesk (ADMIN, CASHIER)DeskCharge.createdAt, DeskPayment.createdAt
Staff reject the transferDeskcancelReason (no time)
Day-pass guest checks in → CONFIRMEDDeskDeskVisit.checkedInAt
Auto: confirmed and past endsAt → COMPLETEDDaily routineupdatedAt only
Auto: pending, never paid, past endsAt → CANCELLEDDaily routineupdatedAt only
Lazy: hold passes holdUntil → slot freed, guest page shows "expired"On readno status change

Dedicated desks, private offices and anything without a rate get amount = null: staff quote them by WhatsApp.

Tickets

Paid events sell tickets online or at the door. createOnlineOrder locks the event row, counts seats that are paid, submitted or still held, and caps the order at 10.

PENDINGSUBMITTEDPAIDadmitted at door
orCANCELLED
StepWhoRecorded
Order created and seats heldGuestcreatedAt, holdUntil
Free ticket (price 0) → PAID and tickets issued at onceSystempaidAt
Guest submits Bankak referenceGuestbankakReference (no time of its own)
Staff verify → PAID, tickets issued, ledger writtenDeskpaidAt
Door sale → PAID immediately (cash or Bankak)DeskpaidAt, channel = DOOR
QR scanned at the door → admitted, onceDeskTicket.checkedInAt
Lazy: hold expires → seats releasedOn readorder stays PENDING forever

Free event registration

Events without ticket types take registrations on the event page. Inside one transaction, registerForEvent counts REGISTERED rows: when the event is full, the new row is WAITLISTED.

REGISTEREDATTENDED
orWAITLISTEDCANCELLED

Everything after sign-up is manual in the admin. There is no waitlist promotion when a seat frees up, and the row keeps only createdAt.

Program applications

applyToProgram accepts an application while the program is OPEN and before its deadline. The deadline closes the form lazily — the stored status never changes.

SUBMITTEDREVIEWINGACCEPTED
orREJECTEDWITHDRAWN

Enquiries

The contact, partners, community and membership forms all write an Enquiry with a topic (membership also stores the plan in subject). The guest can continue on WhatsApp straight away.

NEWIN_PROGRESSCLOSED

Front desk

check-incheck-out
orauto check-out at 22:00
  • Visits. One open visit per customer. A day pass writes a DAY_PASS charge; a package visit is checked against the package's minutes and expiry. Check-out rounds the minutes up and adds them to the package.
  • Forgotten visits. The daily routine closes visits left open from an earlier day at that day's closing time and notes "auto check-out at closing".
  • Packages. Sold at the desk. Active, exhausted and expired are worked out on read.
  • Offices. One active contract per office. Monthly rent invoices are issued automatically by the daily routine (or by the admin button), one per contract per month, due on the contract's due day. Overdue rent gets a WhatsApp reminder link.
  • Ledger. Every charge (DAY_PASS, PACKAGE, OFFICE_RENT, ROOM_HIRE, TICKET, OTHER) can take partial cash or Bankak payments. Voids are kept with a reason.

Café

Guests order by scanning a table QR (channel = QR); staff ring up the counter (channel = COUNTER, which starts confirmed).

NEWCONFIRMEDPREPARINGREADYCOMPLETED
orCANCELLED

Every status and payment change stamps its own time (confirmedAt, preparingAt, readyAt, completedAt, cancelledAt, paidAt) and writes a CafeOrderEvent row with the staff member. It is the only full event log in the product. Staff boards poll every 5 seconds and chime every 30 seconds while an order waits.

Report an issue

The dialog in the footer and the admin runs a pipeline: filters → rate limit → duplicate search on GitHub and AI triage (when a key is set) → a score. Reports scoring 30 or more become GitHub issues. Its telemetry goes only to the Worker logs.

Guards on every public form

  • A hidden honeypot field, then a per-IP rate limit (5 submissions per 10 minutes by default; the café allows 60).
  • A caught bot gets a fake success, so it doesn't retry.
  • The limiter lives in memory per Worker, and nothing is counted — see Optimizations.