In one line: Royal Glow reaches people through email (the durable
record — Resend for transactional, Brevo for marketing) and Web Push (the
instant nudge). All three are guarded extension points: with a key absent the
send is a no-op that logs and returns cleanly.
Email and push are guarded extension points. Each helper reads its keys
directly behind a truthy guard — Resend behind
RESEND_API_KEY, Brevo behind
BREVO_API_KEY, and Web Push behind the VAPID keys. With a key absent, the
send is a no-op that logs and returns cleanly, so the app builds and runs with
no email or push configured.Why two email services
Because authentication is Google OAuth only, the app sends zero auth emails — no password resets, no magic links. Every email is a business event. Those split naturally into two very different infrastructure problems, so they use two services.Email providers
Resend handles transactional mail; Brevo handles marketing. The split is strict — invoices and confirmations never go through the marketing pipeline, and promotional blasts never go through the transactional one.- Resend — transactional
- Brevo — marketing
Resend is built for instant, reliable 1-to-1 delivery, has first-class support
for PDF attachments (invoices), and integrates natively with React Email. Its
free tier — 3,000 emails/month, 100/day — covers the entire launch phase.
All transactional mail is sent
from hello@theroyalglow.in.React Email templates
Transactional templates are built with React Email as typed.tsx
components in packages/emails/, styled in Royal Glow’s brand. They receive
strongly typed props (money as integer paise, dates as DD/MM/YYYY) and
render to HTML at send time.
packages/emails
welcome.tsx — Onboarding complete
booking-confirmation.tsx — Booking pending
booking-approved.tsx — Confirmed: staff, pricing, calendar
booking-rejected.tsx — Rejected: reason + rebook CTA
appointment-reminder.tsx — 24h / 1h reminder
invoice-service.tsx — Service invoice (Salon + SPA non-member)
invoice-membership-purchase.tsx — Membership enrolment invoice
invoice-membership-session.tsx — ₹0 session confirmation
membership-expiry-reminder.tsx — 30d / 7d / 1d expiry warning
membership-expired.tsx — Expired: renewal CTA
membership-hours-update.tsx — Periodic hours-remaining nudge
Brevo marketing emails do not use React Email. They are designed in
Brevo’s drag-and-drop editor and managed entirely in the Brevo dashboard, so
marketing content can change without a deploy.
Web Push notifications
Web Push delivers a short, tappable alert to a customer’s device — booking confirmed, reminder due, gems earned — even when the tab is closed. It uses the browser’s native Push API and the VAPID protocol; no third-party push vendor is involved.Subscription flow
1
Permission
The browser’s native prompt is shown after the customer’s first successful
booking, not on first load.
2
Subscribe
Once granted, the service worker calls
pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: VAPID_PUBLIC_KEY }),
returning a PushSubscription.3
Store
The client posts it to
POST /api/push/subscribe, which upserts a row in the
push_subscription table keyed by endpoint and bound to the signed-in user.4
Send
When an event fires, the server fetches the user’s subscriptions and the
web-push library signs and delivers the payload; the service worker shows the
notification.Storage
POST /api/push/subscribe persists the subscription serialised by the browser’s
PushManager:
DELETE /api/push/subscribe soft-unsubscribes by deactivating the row for a
given endpoint. Both routes are session-scoped, so one user can never touch
another’s subscriptions. See the
Notifications & Realtime API for the full
request and response contracts.
Web Push is gated on the VAPID keys (
VAPID_PUBLIC_KEY /
VAPID_PRIVATE_KEY). When they are absent, subscription and delivery no-op and
log — the booking still succeeds, it simply sends no push. Reminder jobs always
check the customer’s preferences before sending, and skip a booking that is no
longer confirmed.Related links
Notifications & Realtime API
POST /api/push/subscribe and the in-app notification feed.
Realtime (Ably)
Live in-page updates — the instant counterpart to push.
Background Jobs
The scheduled jobs that trigger reminders and marketing email.
Jobs API
The job endpoints that send reminders and follow-ups.
Environment Variables
Resend, Brevo, and VAPID keys.
