The Problem
Organizations needing scheduling capability typically face a choice between expensive SaaS platforms with limited customization or building from scratch—both paths lock users into recurring costs or undifferentiated engineering effort. SnagTime addresses this by providing a self-hostable codebase that can be run locally, customized, and hosted on infrastructure the operator controls.
What This Does
SnagTime is a Next.js 14 application (apps/web/) built on top of Prisma ORM with a PostgreSQL backend. The core scheduling logic lives in apps/web/src/server/services/—availability, bookings, event-types, and calendar integration each have dedicated service files with corresponding test suites. The API routes under apps/web/src/app/api/ map directly to these services: /api/availability, /api/bookings, /api/event-types, and /api/integrations for Google Calendar and email. State management for availability editing lives in apps/web/src/components/availability-editor-state.tsx and availability-editor-state.test.ts, while the booking flow is orchestrated through apps/web/src/components/public-booking-flow.tsx. Payments are handled via Stripe Checkout in apps/web/src/app/api/webhooks/stripe/route.ts with test-mode webhook verification. The database schema is defined in prisma/ with 23 migration files tracking phases from initial setup through provider backfill and OAuth custody fencing. A background worker at apps/web/src/worker.ts processes outbox-dispatched notifications (apps/web/src/server/services/outbox-worker.ts) and handles booking recovery. Local development uses SQLite via npm run demo:free; production requires PostgreSQL and a running worker process.
How It Is Wired
Execution starts at the Next.js server: apps/web/src/app/layout.tsx and apps/web/src/pages.tsx (implicit from the app router structure) handle the initial route tree. Public booking pages under apps/web/src/app/book/[slug]/page.tsx route to apps/web/src/app/api/public/[slug]/route.ts, which queries the database for an event type by slug and renders available slots via apps/web/src/app/api/public/[slug]/slots/route.ts. Bookings create a record through apps/web/src/app/api/bookings/route.ts, which calls the booking service and triggers Stripe Checkout. The Stripe webhook at apps/web/src/app/api/webhooks/stripe/route.ts confirms payments and updates booking state. Google Calendar integration flows through apps/web/src/app/api/integrations/google/authorize/route.ts → callback → status checks, with free/busy queries executed in apps/web/src/server/services/calendar.ts. Email notifications are dispatched via the outbox worker pattern: services write to the outbox, the worker reads and sends via SMTP configured in apps/web/src/server/email-config.ts. The rate-limit middleware at apps/web/src/server/rate-limit.ts guards API endpoints, and health checks live at apps/web/src/app/api/health/live/route.ts and ready/route.ts. The database connection and retry logic are in apps/web/src/server/db.ts and apps/web/src/server/database-retry.ts.
Internal call graph highlights:
apps/web/src/app/api/bookings/route.ts→apps/web/src/server/services/bookings.ts→ Prismabookingoperations; also triggers Stripe and outbox writeapps/web/src/app/api/integrations/google/callback/route.ts→apps/web/src/server/services/calendar.ts→ Google Calendar API free/busy checksapps/web/src/server/worker.ts→apps/web/src/server/services/outbox-worker.ts→ SMTP send; also handles booking-recovery linksapps/web/src/app/api/auth/register/route.ts→apps/web/src/server/auth/session.ts→ Prisma user creation with email verification
Hubs & cycles: The Prisma client is the central database abstraction—every service imports from apps/web/src/server/db-context.ts. The outbox-worker cycle (write → enqueue → dequeue → send) is the widest blast radius for changes involving notifications and payment confirmations.
How To Use It
Setup commands are documented in the README and inferred from the present config files:
Setup:
git clone https://github.com/nateherkai/snagtime.git
cd snagtime
npm run setup
# optional: npm run setup -- --email you@example.com --password "YourStrong!Password7"
npm run demo:free
Open http://localhost:3000 and use the login printed by the setup command.
Configuration: Required env vars are listed in .env.example (not included in the file listing but referenced in the README). At minimum: DATABASE_URL, NEXTAUTH_URL, NEXTAUTH_SECRET, STRIPE_SECRET, STRIPE_WEBHOOK_SECRET, SMTP_HOST, SMTP_USER, SMTP_PASSWORD, SMTP_FROM. Google Calendar requires GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET per the integration docs.
Running it: npm run demo:free starts the local dev server with SQLite, a local calendar adapter, stub email inbox, and test payments. For production, Dockerfile at root and compose.production.yml define a Next.js service and a PostgreSQL container; the worker process must be started separately.
Real-World Use
A freelance consultant sets up SnagTime on a $5/month VPS. They run npm run demo:free to validate the booking flow, then replace DATABASE_URL with a managed PostgreSQL connection string, add their Stripe test keys and SMTP credentials, and configure Google Calendar OAuth per docs/INTEGRATION-SETUP.md. The public booking link (/book/[slug]) can be shared with clients; the calendar adapter prevents double-booking against the consultant’s actual Google Calendar. When a client books and pays via Stripe Checkout, the webhook updates the booking status and triggers a confirmation email. If a client needs to reschedule, the manage-session route (apps/web/src/app/api/bookings/[id]/manage-session/route.ts) generates a recovery link from the outbox worker.
Code Health & Issues
Measured analysis findings:
- 37 test files found under
tests/andapps/web/src/components/*test.tsandapps/web/src/server/*test.ts— coverage includes authentication, database retry, rate limiting, calendar, payments, and outbox dispatch - GitHub Actions CI defined in
.github/workflows/ci.ymlruns on push and pull requests - Prisma migration lock file present:
prisma/migration_lock.toml - No license file detected in the root directory structure
.env.examplepresent but no.env.localcommitted (expected for local secrets)
SDLC observations: The repo has structured test suites per domain, which is above average for a project of this size. The absence of a LICENSE file is notable for a self-hosted codebase; users cannot legally redistribute or modify without clarifying terms. The Prisma migration lock prevents accidental schema drift but requires explicit unlocking for changes. No SECURITY.md content visible in the file listing, though a SECURITY.md file exists at root.
The Bottom Line
SnagTime is a well-structured Next.js + Prisma scheduling platform that delivers on its promise of a free, self-hostable alternative to commercial booking tools. The codebase is modular—services, API routes, and integrations are clearly separated—and the local demo mode works out of the box with SQLite. Production readiness depends on adding PostgreSQL, configuring the background worker, and attaching your own Stripe/Google/email accounts; the architecture supports this but requires operational attention. It’s a solid choice for teams that need customizable scheduling without recurring fees and are comfortable operating a Node.js + PostgreSQL stack.