Skip to content

Notifications

PreviousNext

How Space tells guests and staff what happened — the in-app inbox, push to the phone, WhatsApp and email — and how to switch each channel on.

Every notice goes through one function, notify() in src/lib/notify. It fans out to the channels below. Each channel is optional: when its keys are missing it does nothing, and the others still go out. Email stays with the callers (site/booking/notify.ts), which own its HTML.

ChannelWho gets itNeeds
In-app inboxSigned-in accounts — guests with an account, and staffNothing — always on
Push to the phone (Web Push)Every browser or phone the person switched on, plus phones a guest switched on from their booking page (no account needed)VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, VAPID_SUBJECT
WhatsAppThe booking's phone; staff with a WhatsApp number on their accountMeta Cloud API or Evolution API keys (below)
EmailThe booking's email; every active ADMIN and CASHIERRESEND_API_KEY

What sends what

EventGuestDesk team (ADMIN + CASHIER)Café team (all staff)
Request sent · price ready · confirmed · transfer received · payment not matched · cancelled (by Space or by the guest) · request expired · time changedinbox · push · WhatsApp · email——
Reminder the day before (hourly cron, never 22:00–07:00, once per booking)inbox · push · WhatsApp · email——
New request, instant booking, transfer to check, guest cancelled—inbox · push · WhatsApp · email—
New QR café order——inbox · push

Staff notices are in Arabic and open the booking in the dashboard. Guest notices use the booking's language and open the guest's booking page.

Voice. Every notice is written the way Airbnb writes its own: one plain sentence that carries the whole update — what, where and when — e.g. "You're all set for Meeting Room on Sun 11 Oct, 10:00–12:00. See you at Space!" That sentence is the inbox row, the push body and the email's lead; the short title ("Booking confirmed") heads the push and the email. Copy lives in booking.json → email and notifications.json → cafe.

The inbox

It mirrors Airbnb's web notifications. There is no bell in the header: while anything is unread, the account button carries a small red dot (--alert, Airbnb's Rausch), and the account menu has Notifications with a red count. The phone menu button carries the same dot. The page — /notifications for guests, /dashboard/notifications for staff (src/components/notifications/inbox.tsx) — is a title, then one row per notice: the round Space mark, the sentence (bold while unread, grey once read), the full date beneath, and an ✕ that dismisses it. Opening the page reads everything; empty, it says "No notifications yet".

One store (feed.ts) reads /api/notifications for every indicator on the page — once a minute while the tab is visible, on navigation, and the moment a push arrives. Signed-out visitors get one 401 per navigation and no polling. The installed app's icon shows the unread count where the phone supports it.

Push to the phone

  • public/sw.js shows the notification and opens its link when tapped. (It also keeps the app shell offline — see the PWA notes in src/components/pwa.)
  • The browser subscribes with the public key from GET /api/push, then POST /api/push stores the subscription against the signed-in account and/or the booking token.
  • src/lib/notify/web-push.ts signs VAPID and encrypts the payload (RFC 8291) with WebCrypto only, so it runs on the Worker. Subscriptions the push service reports gone (404/410) are deleted.
  • iPhone: Safari only allows push once Space is added to the home screen and opened from there. The switch tells the person how.
  • A phone that already has push on follows the booking page it opens last — opening a new booking re-points it.

Generate the keys once per environment with pnpm push:keys. Changing the keys orphans every existing subscription.

WhatsApp

Live: Space's own number, +249 909 199 383, as a linked device (Evolution). The bridge is the Evolution process already running inside the hogwarts container (shared since 2026-10-25, so Space pays for no container of its own), with no public URL: the space Worker reaches it through the WHATSAPP service binding, which points at hogwarts' private WhatsappBridge entrypoint. Space keeps its own instance, space. Its session lives in hogwarts' Neon database evolution, so a restarted container reconnects without a new QR. Link or relink the phone from Admin → WhatsApp (ADMIN): scan the QR from WhatsApp → Linked devices, or type the 8-character code after "Link with phone number instead". The same page sends a test message and unlinks. The site's WhatsApp buttons open a chat with SPACE_WHATSAPP.

Two providers. Meta wins when both are set.

  • Meta WhatsApp Cloud API (official): WHATSAPP_TOKEN, WHATSAPP_PHONE_NUMBER_ID, WHATSAPP_TEMPLATE (default space_notice). Messages Space starts must use an approved Utility template with one body variable, for example سبيس: {{1}}, approved in both ar and en. It needs a verified Meta business and a phone number registered to the WhatsApp Business Platform.
  • Evolution API (self-hosted, links an ordinary WhatsApp number by QR, as hogwarts does): EVOLUTION_API_URL, EVOLUTION_API_KEY, EVOLUTION_INSTANCE. It sends free text, but it isn't official — WhatsApp can ban the number.

Numbers are converted to international digits, with Sudan (+249) as the default: 0912345678 becomes 249912345678. Staff set their number under Admin → Team, or on their own account page.

Data

Notification (one row per recipient, title and body already in their language) and PushSubscription (endpoint + keys, owned by userId or bookingId). Both are deleted with their account or booking.