PracticeDepth
Personal Project · Commercial SaaS — live in production, taking real Stripe payments

An adaptive AI technical interviewer that asks senior-level questions, takes answers by voice or text, corrects the candidate like a senior engineer would, and drills deeper into whatever they raise instead of marching through a question list. Shipped solo from first commit to a paying production product in about 12 weeks.
Responsibilities
- Built and launched a complete commercial SaaS end to end, alone: the interview engine, accounts and freemium credits, Stripe payments, CV Defense, a public "Depth Passport" profile, a grounded support assistant, a 14-post blog, deployment, and observability.
- Implemented the interview as a LangGraph.js state machine that pauses and resumes across HTTP requests, with Redis as the checkpointer so app instances stay stateless and any container can serve the next turn.
- Kept the agent strictly text-only and pushed audio to the edges: Groq Whisper for speech in, Gemini native TTS for speech out, with all provider keys server-side only.
- Generated every question from a competency blueprint of 16 topics with per-level rubrics rather than a static question bank — code selects the next competency, the model only phrases it.
- Built the money layer: a durable per-account credit balance, an anonymous device-scoped trial, Stripe hosted Checkout fulfilled by webhook (never by the success redirect), a consumption ledger, and proportional credit clawback on charge.refunded.
- Added auth with Better Auth (email OTP plus GitHub/Google) and a five-email lifecycle sequence pre-scheduled on Resend at signup, keeping the project cron-free.
- Deployed self-hosted on Hetzner + Coolify: CI typechecks, lints, tests, builds a linux/amd64 image, boots it as a health gate, and pushes to GHCR; migrations run inside the container before the new version serves traffic; nightly Postgres backups to Cloudflare R2 with a restore verified by hand.
- Instrumented the whole system: Pino with enforced PII redaction, Sentry (EU region), Prometheus metrics scraped by Grafana Alloy into Grafana Cloud, PostHog behind a consent gate, and cookieless Umami outside it.
Key Actions & Decisions
- Separated "which features are reachable" from "are credits enforced" after the two were conflated in one flag — feature access is permanent once paid, metering is not, which closed a hole where a single €7 pack bought unlimited interviews forever, without taking anything away from paying users.
- Hardened the system against real production findings — bound sessions to their owner (a session id had been an unbound bearer token) and authorized the TTS/STT edges so voice could not be metered to nobody or billed to someone else.
- Split model failures into three distinct predicates — quota exhausted, provider busy, and our own timeout — so only a real spend problem pages a human, a provider's bad night is not filed as our bug, and the timeout rate stays countable as evidence for whether the call deadline is sized correctly.
- Chose one self-hosted VPS over serverless for cost control and data residency, and forbade building on the server: the box runs the app, Postgres and Redis, and a next build there would have OOM-killed a live container.
- Priced the credit packs against Stripe's flat per-transaction fee rather than against API cost, since the per-sale processing fee — not the model — is what actually shapes the ladder.
- Recorded every significant decision as an ADR (44 of them) with superseded ones kept intact, so the reasoning behind code written six weeks earlier is still recoverable rather than re-litigated.
- Treated "does this feel like a senior engineer interviewing me?" as a real acceptance test alongside typecheck, lint and tests, verifying interactive behaviour in a browser rather than by curling server-rendered HTML.
Live and monetized at practicedepth.com — in production since 30 Jul 2026 and taking real Stripe payments since 3 Aug 2026. The first real purchase was verified end to end (webhook delivered, credits granted, ledger row written), and external testers have run full sessions, with their feedback driving two shipped fixes: pre-rendered voice auditions that removed ~8s of latency, and a mid-turn retry that resumes a failed turn from the last successful graph node instead of discarding a paid-for session.