Documentation
SaaSCoreX documentation is structured around the production lifecycle — installation, architecture, billing, deployment, and operational safeguards. Every subsystem is documented with enforcement boundaries and invariants.
Design Philosophy
Core principles that govern every decision.
- Tenancy-first architecture— Every query, every action, every job is org-scoped by default.
- Entitlements over plan names— Gate on capabilities, never on what a plan is called.
- Explicit server boundaries— No UI-only protection. All enforcement is server-side.
- Progressive enhancement— Disable billing, teams, or audit without touching schema.
- Infrastructure before UI— Systems are correct before screens are pretty.
Getting Started
From install to running dev server.
npx create-saascorex my-app
# CLI generates and scopes your .env
pnpm db:migrate
pnpm devExisting repo setup
If you already have the repo cloned, use the built-in setup wizard and environment doctor:
pnpm saascorex:setup # creates .env, generates Prisma, runs migrations
pnpm saascorex:setup --seed # same + loads demo data
pnpm saascorex:doctor # verify env, DB, Stripe, and Prisma healthThe doctor script exits with code 0 when all checks pass, making it suitable for CI health gates.
Required secrets
DATABASE_URLPostgreSQL connection stringNEXTAUTH_SECRETAuth secret — generate with openssl rand -base64 32RESEND_API_KEYEmail delivery via ResendAll variables are scoped and auto-generated by the CLI where possible.
▶Optional (progressive enhancement)
STRIPE_*Enable billing (Stripe checkout, webhooks, portal)GOOGLE_CLIENT_ID / GITHUB_CLIENT_IDOAuth providersREDIS_URLMulti-instance rate limitingINNGEST_*Production job processingNEXT_PUBLIC_SENTRY_DSNError monitoringDemo Data
pnpm demo:seed- 3 organizations
- 8 users (varied roles)
- 10 projects
Architecture
Production structure and package boundaries.
pnpm monorepo managed by Turborepo. Seven internal packages, one web application.
| Package | Purpose |
|---|---|
@saascorex/core | Auth, billing, teams, audit, API keys, RBAC, 2FA business logic |
@saascorex/db | Prisma client, schema, and migrations |
@saascorex/ui | Component library (Radix + Tailwind) |
@saascorex/config | Plans, features, brand, marketing config |
@saascorex/email | Transactional emails (React Email + Resend) |
@saascorex/jobs | Background jobs (Inngest) |
@saascorex/features-runtime | Feature flag runtime (server + client) |
Stack
Transactional email templates with live preview and brand-aware styling.
Four production-ready templates built with React Email and delivered via Resend. All templates pull colors, logos, and copy from @saascorex/config so branding stays consistent.
| Template | Trigger |
|---|---|
magic-link | Passwordless sign-in |
welcome | First sign-up |
org-invite | Team member invitation |
billing-state | Subscription created, cancelled, payment failed, trial ending |
Dev preview
The email dev server runs on localhost:3002 alongside the main app. Edit templates in your IDE and see changes instantly in the browser.
pnpm dev # starts all services including email preview
# or run standalone:
pnpm --filter @saascorex/email devGraceful degradation
In development, email functions log to the console when RESEND_API_KEY is not set — no crashes, no silent failures. In production, missing keys throw immediately so you catch misconfigurations at deploy time.
Customization
Templates live in packages/email/src/templates/. Colors reference brand.theme.primaryHex from the shared config. Update the brand config once and every template reflects the change.
Conventions
Architectural rules that prevent data leakage, entitlement drift, and cross-tenant access errors.
Tenancy & Data Safety
All queries scoped by orgId. Server actions require requireAuth() or requireOrg(). Last-owner protection enforced server-side.
Billing & Entitlements
Gate on entitlements, never plan names. Webhooks idempotent. Plan switching with Stripe proration. Real-time usage metering on dashboard and billing pages. Billing emails respect notification preferences.
Security
Tokens SHA-256 hashed. Rate limits at sensitive edges. TOTP two-factor auth with backup codes. Zod validation on all admin actions. Audit log records all privileged actions.
Feature Flags
Three layers must agree when off: routes, UI, server actions. Prisma models always present — flags gate behavior, not schema.
Architecture Boundaries
Apps don’t import from other apps. Core contains business logic. UI is pure presentation. Server actions call into core.
Active Org Resolution
activeOrgId lives in Session table. requireOrg() reads from DB on every request — never client state.
Billing & Entitlements
Stripe integration, webhook configuration, and entitlement enforcement.
Billing is enabled by default via Stripe. Set billing: "off" in packages/config/src/features.ts to disable. All Stripe code is lazy-loaded when disabled. The dashboard and billing page show real-time usage metering with color-coded progress bars (green under 75%, warning at 75–90%, danger above 90%) for projects, team members, and API requests.
Required Stripe variables
STRIPE_SECRET_KEY=sk_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_PRO_MONTHLY_PRICE_ID=price_...
STRIPE_PRO_YEARLY_PRICE_ID=price_...Required webhook events
Endpoint: /api/stripe/webhook
customer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedinvoice.payment_failed
Code structure
packages/core/src/billing/
webhook-handler.tsIdempotent event processor (deduped by stripeEventId)subscription-service.tsSubscription CRUD, plan switching with prorationcheckout-service.tsCheckout session creationentitlements.tsPlan entitlement rules and enforcementreconciliation.tsNightly Stripe↔DB sync
Jobs & Background Tasks
Inngest-powered scheduled functions. All idempotent. All safe for multi-instance deployments.
| Function | Schedule | Purpose |
|---|---|---|
billing-reconciliation | 3 AM daily | Stripe↔DB sync. Heals drift from missed webhooks. |
audit-purge | 4 AM Sundays | Removes audit entries older than 90 days. |
cleanup-expired | 2 AM daily | Deletes expired sessions, tokens, unaccepted invites. |
webhook-retry | Every 15 min | Retries failed webhook events (max 5 attempts). |
data-export | Event-driven | Gathers org data and emails as JSON attachment. |
billing-reconciliation and webhook-retry only run when billing is enabled. audit-purge only runs when audit logging is enabled. cleanup-expired always runs.
Adding a new job
Create a new file in packages/jobs/src/functions/. Each function calls inngest.createFunction() with a unique ID, a cron schedule or event trigger, and an async handler. Wrap each logical operation in step.run("step-id", ...) for automatic retries with exponential backoff. Idempotency comes from unique step IDs (Inngest replays successful steps on re-execution) and deterministic database operations. Export the function and add it to the allFunctions array in packages/jobs/src/index.ts.
REST API
21-endpoint REST API with OpenAPI 3.1 spec, consistent envelopes, and per-endpoint rate limiting.
SaaSCoreX ships a production-ready REST API authenticated via API keys. The OpenAPI 3.1 spec is served at /api/v1/openapi.json and a discovery endpoint at /api/v1 lists all available endpoints with auth and rate limit info.
Authentication
Pass an API key as a Bearer token in the Authorization header:
Authorization: Bearer sk_a1b2c3d4e5f6...API keys are created in the dashboard or via POST /api/v1/api-keys. Each key is scoped to an organization and inherits the creator’s role permissions.
Response format
All endpoints return a consistent envelope:
// Success (single resource)
{ "data": { "id": "...", "name": "..." } }
// Success (list with pagination)
{ "data": { "items": [...], "meta": { "page": 1, "pageSize": 20, "total": 42, "totalPages": 3 } } }
// Error
{ "error": { "code": "FORBIDDEN", "message": "Insufficient permissions", "requestId": "req_..." } }Endpoints
| Method | Path | Description |
|---|---|---|
GET | /api/v1 | Discovery endpoint |
GET | /api/v1/health | Health check |
GET | /api/v1/openapi.json | OpenAPI 3.1 spec |
GET | /api/v1/me | Current user + role + permissions |
GET | /api/v1/org | Organization details |
PATCH | /api/v1/org | Update organization |
GET | /api/v1/members | List members |
PATCH | /api/v1/members/:id | Update member role |
DELETE | /api/v1/members/:id | Remove member |
GET | /api/v1/invitations | List invitations |
POST | /api/v1/invitations | Create invitation |
DELETE | /api/v1/invitations/:id | Revoke invitation |
GET | /api/v1/projects | List projects |
POST | /api/v1/projects | Create project |
GET | /api/v1/projects/:id | Get project |
PATCH | /api/v1/projects/:id | Update project |
DELETE | /api/v1/projects/:id | Delete project |
GET | /api/v1/api-keys | List API keys |
POST | /api/v1/api-keys | Create API key |
DELETE | /api/v1/api-keys/:id | Revoke API key |
GET | /api/v1/billing | Billing summary |
POST | /api/v1/billing/portal | Create billing portal session |
GET | /api/v1/audit | Query audit logs |
Rate limiting
Each endpoint has per-key rate limits. When exceeded, the API returns 429 with Retry-After, X-RateLimit-Limit, and X-RateLimit-Remaining headers. Default: 100 requests per 60 seconds for standard endpoints.
CORS
CORS is enabled with configurable allowed origins via API_CORS_ORIGINS environment variable. Preflight requests are handled automatically.
Deployment
Standalone Node server via Next.js output tracing. Five platform guides included.
- Vercel
Connect repo, set root to apps/web, add PostgreSQL. In-memory rate limiter only — set REDIS_URL for production.
- Docker
docker compose up starts PostgreSQL + app. --profile redis adds Redis for multi-instance rate limiting. 3-stage Dockerfile included.
- Railway
Add PostgreSQL via dashboard, configure build and start commands, set DOCKER_BUILD=1 for standalone output.
- Fly.io
fly launch, fly postgres create, configure secrets, deploy. Health check at /api/health.
- Self-Hosted
Build with Turborepo, run standalone Node server. Bring your own PostgreSQL and reverse proxy.
Production recommendations
- Database connection pooling for >10 concurrent connections
- Automated database backups
- Run
pnpm db:migratein CI/CD before deploying - Set
REDIS_URLfor multi-instance deployments - Security headers auto-configured (HSTS, CSP, X-Frame-Options)
- Health check at
GET /api/health
Full instructions in PRODUCTION.md and docs/deploy.md in the repo.
Testing
Unit, integration, and end-to-end test infrastructure.
pnpm test # All tests (unit + integration)
pnpm test:unit # Unit tests only
pnpm test:integration # Integration tests
pnpm test:e2e # Playwright E2E
pnpm test:billing # Billing integration testsE2E coverage
critical-pathDashboard, settings, project CRUD, team members, auditauthSign-in, sign-up, redirectsorgOrg management, switching, member managementmarketingMarketing pages load correctly
First-time setup: pnpm exec playwright install chromium
Command Reference
Every script available in the monorepo.
| Command | Description |
|---|---|
pnpm dev | Start dev server |
pnpm build | Build all packages |
pnpm db:migrate | Run Prisma migrations |
pnpm db:seed | Seed base data |
pnpm demo:seed | Load demo data (3 orgs, 8 users) |
pnpm db:studio | Open Prisma Studio |
pnpm saascorex:setup | Interactive setup wizard (env, Prisma, migrations) |
pnpm saascorex:setup --seed | Setup + load demo data |
pnpm saascorex:doctor | Verify environment health (env, DB, Stripe, Prisma) |
pnpm typecheck | TypeScript type checking |
pnpm lint | Lint all code |
pnpm format | Format all code (Prettier) |
pnpm test | Run all tests |
pnpm test:e2e | Run Playwright E2E tests |
pnpm clean | Delete build artifacts + node_modules |