Architecture
What's it built with, and how does it hold together?
A single Next.js app on Vercel over Supabase Postgres, Stripe Connect for money, Twilio and Resend for messaging. Standard CRUD — no novel technical risk.
- Stack
- Next.js + Supabase Postgres + Stripe Connect
- Auth
- Magic-link instructors, phone-OTP pupils, RLS by role
- Money
- Stripe Connect — no card data ever in DriveDesk's database
- AI
- Assist-only, human-in-the-loop, post-MVP
Relational data (bookings, payments, pupils) with RLS scoping every query by instructor or school at the database layer — not just in application code.
DriveDesk never holds card data or builds a payout system. Stripe Connect (Express accounts) handles instructor KYC, payments, refunds and payouts. This is permanent, not a placeholder to revisit.
PCI-DSS scope, chargebacks, KYC and an ongoing regulatory burden — all unrelated to what makes DriveDesk useful, and cheaper to rent from Stripe than to build worse.
An installable, offline-tolerant booking experience without an App Store dependency covers the initial need.
No-show flags and note summaries are surfaced to the instructor; nothing AI-generated reaches a pupil or moves money without a human in the loop first.
Tech stack
| Layer | Choice | Why |
|---|---|---|
| Frontend | Next.js (App Router), TypeScript, Tailwind CSS | Server components for fast initial loads on mobile connections (instructors are often between jobs on 4G); one codebase serves instructor, pupil, and admin views |
| PWA layer | next-pwa / native web app manifest | Installable, offline-tolerant booking screen without an App Store dependency — see the honest "say no" in 00 — Overview |
| Backend | Next.js API routes / server actions | No separate backend service needed at this scale; keeps deployment and mental model simple |
| Database | Postgres, hosted via Supabase | Relational data (bookings, payments, pupils) fits SQL cleanly; Supabase gives auth, row-level security, and Postgres in one managed service |
| Auth | Supabase Auth (magic link for instructors/admins, phone OTP for pupils) | See "Auth model" below |
| Payments | Stripe Connect (Standard or Express accounts per instructor) | Handles PCI compliance, payouts, and refunds — see the "say no" below |
| SMS | Twilio | Reminder and no-show-adjacent messaging; mature UK SMS delivery |
| Resend | Transactional email (receipts, booking confirmations) | |
| Hosting | Vercel | Native fit for Next.js, zero-config preview deployments for testing changes before release |
| Calendar sync (Should have) | Google Calendar API, CalDAV for Apple Calendar | Deferred to fast-follow phase, not MVP |
The say-no: don't build your own payments
A driving-school SaaS handling instructor payouts touches real money and real PCI scope the moment it stores a card number or moves funds between parties. Building that in-house means: PCI-DSS compliance work, dispute/chargeback handling, KYC for every instructor, and an ongoing regulatory burden that has nothing to do with what makes DriveDesk useful. Stripe Connect (Express accounts) does all of this — instructor onboarding via Stripe's hosted KYC flow, automatic payouts to their own bank account, and built-in dispute tooling — for a transaction fee that is cheaper than the engineering time it would take to build a worse version in-house. This isn't a placeholder decision to revisit later; it's permanent.
System architecture
A single Next.js application serves three logical surfaces from one codebase: the instructor/admin dashboard, the pupil booking PWA, and a set of API routes/server actions that both talk to. Supabase Postgres is the single source of truth; Stripe and Twilio are called from server-side routes only — no payment or SMS logic ever runs client-side. Scheduled jobs (reminder SMS sends, package-balance checks, nightly no-show-risk scoring once that feature ships) run via Vercel Cron hitting dedicated API routes, avoiding the need for a separate job-queue service at this scale.
Pupil PWA ──┐
├──> Next.js (Vercel) ──> Supabase Postgres (RLS)
Instructor │ │
Dashboard ──┘ ├──> Stripe Connect (payments, payouts)
├──> Twilio (SMS)
└──> Resend (email)
Data model
Core tables (fields abbreviated to what matters for build decisions):
| Table | Key fields | Relationships |
|---|---|---|
schools | id, name, owner_user_id | has many instructors |
instructors | id, user_id, school_id (nullable), stripe_account_id, default_hourly_rate, cancellation_policy_hours, cancellation_charge_pct | belongs to schools; has many availability_slots, pupils, bookings |
pupils | id, instructor_id, name, phone, licence_status (provisional/full), created_at | belongs to instructors; has many bookings, lesson_notes, packages |
availability_slots | id, instructor_id, day_of_week, start_time, end_time, is_recurring | belongs to instructors |
bookings | id, instructor_id, pupil_id, scheduled_at, duration_minutes, status (booked/completed/no_show/cancelled), payment_status, stripe_payment_intent_id, package_id (nullable) | belongs to instructors, pupils; optionally to packages |
packages | id, pupil_id, instructor_id, total_hours, hours_remaining, price_paid, stripe_payment_intent_id | belongs to pupils; has many bookings |
lesson_notes | id, booking_id, instructor_id, pupil_id, note_text, created_at | belongs to bookings, pupils |
payments | id, booking_id (nullable), package_id (nullable), amount, stripe_charge_id, status, payout_status | belongs to bookings or packages |
test_bookings | id, pupil_id, test_type (theory/practical), test_centre, scheduled_date, result (nullable) | belongs to pupils; manual entry only in MVP |
Row-level security in Supabase scopes every query by instructor_id (or school_id for admins), so an instructor's Postgres role can only ever see their own pupils, bookings, and payment records — enforced at the database layer, not just in application code.
APIs & integrations
- Stripe Connect: Express account creation per instructor at onboarding; Payment Intents for single lessons and packages; webhooks (
payment_intent.succeeded,charge.refunded,account.updated) drive booking/payment status updates. - Twilio: outbound SMS only in MVP (reminders, booking-link invites); inbound SMS handling deferred.
- Resend: transactional email templates for receipts and booking confirmations, triggered from the same server actions that handle Stripe webhooks.
- Google/Apple Calendar (fast-follow, not MVP): one-way sync of an instructor's bookings out to their personal calendar, via Google Calendar API and CalDAV respectively.
Auth model — dual instructor/pupil
Instructors and school admins authenticate via Supabase Auth magic link (email-based, no password to remember or reset — instructors are not power users of software and password-reset support tickets are avoidable friction). Pupils authenticate via phone number + one-time SMS code, since a pupil arriving via an invite link should never hit a "create a password" wall. Both identity types map to a single users table with a role enum (instructor, school_admin, pupil), and Postgres RLS policies branch on that role to scope data access. Session tokens are short-lived JWTs issued by Supabase; refresh handled client-side by the Supabase SDK.
Security & scaling
- All PII (pupil phone numbers, names) encrypted at rest by Supabase's underlying Postgres encryption; no card data ever touches DriveDesk's own database — Stripe holds it entirely.
- Pupils under 17 with a provisional licence are a safeguarding-adjacent population: DriveDesk stores only what's operationally necessary (name, phone, licence status, lesson notes) and nothing else — no unnecessary personal data fields exist to be misused or breached.
- At the scale this product will realistically reach in year one (low thousands of instructors, tens of thousands of bookings/month), a single Postgres instance and serverless Next.js deployment on Vercel has no meaningful scaling risk. Read-heavy calendar queries are indexed on
(instructor_id, scheduled_at); this is a straightforward CRUD workload, not a systems-scaling problem.
Where AI genuinely fits — and its guardrails
Two AI features are scoped for the "Intelligence" roadmap phase (see 02 — Product), deliberately not in MVP, because both need real booking history to be worth anything:
- No-show risk flagging. A model (or, initially, a simple weighted heuristic before any ML is justified) flags a booking as elevated no-show risk based on factors like lead time since booking, pupil's prior cancellation history, and day/time. This is surfaced to the instructor as a soft flag on the booking card ("this pupil has cancelled last-minute 2 of their last 5 bookings") — never used to auto-cancel or auto-charge. The instructor always makes the final call.
- Lesson-note summarisation. Instructor's freeform notes across several lessons are summarised into a short, structured progress update a pupil (or a parent, for under-18 pupils) can read — "steady improvement on roundabouts, still building confidence with reverse parking." The instructor reviews and can edit the summary before it's shown to the pupil; it is never sent unreviewed.
Guardrail principle for both: AI here assists an instructor's judgement, it never replaces it, and nothing AI-generated reaches a pupil or affects money without a human in the loop first. Neither feature ships until there's enough real usage data (a few months of live bookings) to make the outputs meaningful rather than a novelty bolted on for launch.
Six documents · £8,000 fixed · ~3–4 weeks
Scoped to your real product or internal system — not this fictional one.
Book a Blueprint call Back to overview