Royal Glow internal docs · now fully interactive — Steps, API tables, file trees & live status
Royal Glow Docs

Notifications & Email

How Royal Glow reaches customers and staff — transactional email via Resend, marketing email via Brevo, React Email templates, and browser Web Push notifications.

Notifications & Email

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.

Royal Glow keeps people informed through two channels: email (the durable record — confirmations, invoices, reminders, offers) and Web Push (the instant nudge that lands on a phone or laptop even when the site is closed). This page covers how each is wired up and which service handles what.

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.

ConcernTransactionalMarketing
VolumeLow, 1-to-1High, 1-to-many
SpeedInstant delivery criticalScheduled / batched
AttachmentsYes (PDF invoices)No
Unsubscribe managementNot requiredLegally required
Deliverability focusIndividual inboxBulk sender reputation

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 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 [email protected].

EmailTriggered by
Welcome / onboardingOnboarding completed
Booking confirmation (pending)Booking created
Booking approved / rejectedReceptionist confirms or rejects
Appointment reminder (24h / 1h)Scheduled reminder job
Invoice — serviceBooking moves to completed (+ PDF)
Invoice — membership purchase / sessionMembership created / session recorded (+ PDF)
Membership expiry reminder (30d / 7d / 1d)Daily expiry job
Membership expiredAuto-expire job

Brevo is built for bulk sending to subscriber lists and, critically, manages unsubscribe automatically — a legal requirement for marketing email under CAN-SPAM, GDPR, and India's DPDP Act. Its free tier is 300 emails/day with unlimited contacts.

EmailTriggered by
Post-service follow-up2 days after a completed booking
Re-engagement (inactive 60+ days)Brevo automation
Seasonal / festival offerManual campaign
New offer announcementOwner publishes an offer
Membership renewal nudge7 days after a membership expired

Marketing email requires explicit consent. A customer is only added to Brevo lists when they tick the marketing consent box at onboarding (marketing_consent = true). Transactional email — invoices and booking confirmations — is always sent and cannot be fully disabled.

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.

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

A typical send generates the invoice PDF, then hands the React component and the attachment to Resend:

await resend.emails.send({
  from: 'Royal Glow <[email protected]>',
  to: customer.email,
  subject: `Hey ${customer.firstName}, your Royal Glow receipt is ready.`,
  react: InvoiceServiceEmail(props),
  attachments: [{
    filename: `RoyalGlow-Invoice-${invoice.invoiceNumber}.pdf`,
    content: pdfBuffer,
  }],
})

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

Permission

The browser's native prompt is shown after the customer's first successful booking, not on first load.

Subscribe

Once granted, the service worker calls pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: VAPID_PUBLIC_KEY }), returning a PushSubscription.

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.

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:

Prop

Type

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.

OpenReport an issue

Was this page helpful?

On this page