Skip to main content
Answers are grouped by audience. Click any question to expand it.

For everyone

It’s the complete digital operations system for Royal Glow Salon & Spa — covering the customer website, online booking, admin portal, billing, memberships, loyalty programme, CRM, and all the automation behind the scenes.
Single developer project. The entire platform — 38 database tables, 104 pages, 35 APIs, 19 background jobs — was built by one developer using AI-assisted development tools.
₹0/month at launch. Every external service (Cloudflare, Neon, Ably, Upstash, Resend, PostHog, Sentry, BetterStack) runs on a free tier generous enough for a salon at this scale.
Each service has a clear upgrade path. The first upgrades needed would be Sentry ($26/mo) and BetterStack ($24/mo) — triggered by growth, not required at launch.

For business stakeholders

Customers click “Book Now” on the homepage, which opens a 4-step booking dialog. They can also book via the Google Maps listing (which uses a special link that tracks the source) or by scanning the in-store QR code.
The booking is created with status “Pending”. The receptionist sees it in the admin portal, assigns a staff member, and confirms it. The customer gets a confirmation notification. 15 minutes before the appointment, they get a reminder.
A customer buys a membership (Silver: 8 hrs/₹10k, Gold: 15 hrs/₹15k, Platinum: custom). Each SPA session deducts hours from their balance. The membership expires after 90 days regardless of remaining hours. Reminders are sent at 30, 7, and 1 day before expiry.
Customers earn 1 gem per ₹100 spent on services (floor, not rounded). Gems expire after 365 days. They can be redeemed against specific catalogue services — not as a general discount. Gems cannot be combined with offers.
Customers who click a Meta ad land on /book — a simple 3-field form (name, phone, service interest). This creates a lead in the CRM. The receptionist follows up via WhatsApp (AiSensy). When the lead books, the lead is marked as converted.
When a receptionist marks a booking as completed, the system automatically generates a GST-compliant invoice (18% inclusive, SAC 999721), creates a PDF, uploads it to Cloudflare R2, and emails it to the customer via Resend — all within the same API request.
Yes. The database is designed for multi-branch from day one. Each booking, invoice, and staff member is scoped to a branch. A second branch is already in the seed data with status “Opens Soon”.

For developers

See Getting Started. Short version: bun install, copy .env.example to .env.local, fill in the keys, bun run dev --filter=@rgss/web.
No. Every external integration is a guarded extension point — with a key absent it no-ops and logs. Set SKIP_ENV_VALIDATION=1 to bypass build-time validation while getting started. You only need DATABASE_URL and BETTER_AUTH_SECRET for core functionality.
To avoid floating-point errors. ₹1,000.00 is stored as 100000 (paise). All calculations happen in integers. Rounding only happens at display time.
nanoid() generates random text IDs that prevent enumeration attacks (a user can’t guess booking/1, booking/2, etc.) and work across distributed systems without coordination.
/book is the Meta ad lead capture page — a distraction-free 3-field form for people who clicked an Instagram ad. The booking dialog is for customers already on the website. Mixing these two flows would confuse both audiences and break attribution tracking.
1
Create the file in apps/web/src/app/api/
2
Wrap the handler with withErrorHandler
3
Validate input with a Zod schema from packages/types/
4
Call business logic from packages/business/
5
Return apiSuccess(data) or throw AppError
1
Create the route in apps/admin/src/app/api/jobs/
2
Verify the QStash signature using verifyQStashSignature
3
Do the work, ping the BetterStack heartbeat on success
4
Schedule it in QStash (all jobs run as QStash HTTP routes)
5
Add the heartbeat URL to the environment variables
packages/business can import from packages/types and packages/errors. What it cannot import is packages/db, any framework code, or UI components. This keeps business logic pure and testable.
bun run test:unit — runs Vitest excluding integration tests and E2E.
bun run typecheck && bun run lint && bun run test:unit — the three gates that run on every PR to dev.
NODE_ENV is always development or production (Next.js convention). APP_ENV is dev, test, pprd, or prod — our four deployment environments. Use APP_ENV for environment-specific logic like data seeding guards.
1
Add the schema in packages/db/src/schema/
2
Add relations in packages/db/src/schema/relations/
3
Export from packages/db/src/schema/index.ts
4
Run bunx drizzle-kit generate to create the migration
5
Apply with bunx drizzle-kit migrate, then add seed data if needed
Mintlify hosts the site itself at no cost and ships built-in search, so we run no infrastructure of our own for the docs. The previous self-hosted docs app was removed to cut infrastructure spend. Content stays as MDX in the repo, with navigation and redirects in docs/docs.json.

For operations

BetterStack monitors all key endpoints and sends alerts via Slack and email. The public status page is at status.theroyalglow.in.
BetterStack heartbeat monitors alert if a job doesn’t ping within its expected window. Check the BetterStack dashboard for heartbeat status.
Redeploy the last known-good tag or SHA through the Deploy AWS workflow by setting its git_ref input. SST replaces the Lambda + CloudFront release in about 3–5 minutes. If a deploy fails, inspect SST/Pulumi state and both health endpoints because an update may be partially applied; redeploy the known-good ref when needed. Use a PostHog flag first when it can contain the issue.
Weekly backups are stored in Cloudflare R2 (backups/weekly/). Download the backup, restore to a new Neon branch, verify the data, then swap the connection string. See Operations for the full procedure.
Sign in to the admin portal at /users (requires Owner role). Create the user and assign the appropriate role. The staff member signs in with their Google account.
Sign in to the admin portal at /services (requires Manager role). Edit the service price. The new price applies to all future bookings. Existing bookings and invoices are unaffected (prices are snapshotted at booking time).
Last modified on September 1, 2026