Milestone 1: Foundation and repository
Purpose: Establish the monorepo, database schema, config, and app skeleton so all later work has a consistent base. No auth or tenant logic yet.
Exit state: Repo runs; DB migrates; apps/web and apps/worker start; route groups exist; no feature logic.
Spec reference: §3.1 Monorepo, §3.4 Database, §3.13 Internationalisation (i18n), §4.1 Monorepo structure, §5 Data Model, §5.22 Indexes, §9 Environment Variables.
Prerequisites: None.
1.1 Monorepo setup
Tasks
-
Initialize monorepo
- Create root
package.jsonwith pnpm workspaces (packages/*,apps/*). - Add Turborepo; configure
turbo.jsonwith pipeline (build, dev, lint, test). - Ensure pnpm install and turbo build run from root.
- Create root
-
Create apps
apps/web: Next.js 14+ with App Router; TypeScript; minimalapp/layout (single root layout only).apps/worker: Node entrypoint that registers Inngest client and serves Inngest dev handler (or empty); no functions yet.- Both depend on shared packages via workspace protocol.
-
Create packages
packages/db: Drizzle ORM, schema (see 1.2), migrations directory, export client and schema.packages/auth: Empty or placeholder; used in M02.packages/api: Empty or placeholder for tRPC router; used in M02+.packages/config: Env validation (Zod) and typed config; used by web and worker.packages/ui: Placeholder or shadcn/ui init; used from M03.packages/email: Placeholder; used from M05/M06.packages/billing: Placeholder; used from M10.packages/notifications: Placeholder; used from M08.
Acceptance criteria
| Criterion | Status |
|---|---|
pnpm install and pnpm build (or turbo build) succeed at root. | ✅ |
apps/web runs with pnpm dev and serves a minimal page. | ✅ |
apps/worker starts without error (can be no-op). | ✅ |
All packages resolve via workspace names in app package.json. | ✅ |
1.2 Database schema and migrations
Tasks
-
Define Drizzle schema (Section 5)
- Tables:
tenants,users,tenant_memberships,sessions,inboxes,tickets,messages,attachments,subscriptions,tenant_entitlements,notifications,sla_policies,audit_logs,tags,ticket_tags,invitations,snippets,webhook_endpoints,webhook_deliveries,api_keys,feature_flags,feature_flag_overrides,notification_preferences,business_hours,contact_submissions,inbox_aliases. - All IDs UUID (except Session id per Lucia). Tenant-scoped tables include
tenant_id. - Enums: ticket status, priority, message direction/type/send_status, role, connection_type, subscription status, etc.
- Message: include
inbox_id,bounce_received_at(Section 5.6). Unique(inbox_id, message_id_header). - Ticket:
ticket_number,tsv(generated column via raw SQL). Unique(tenant_id, ticket_number).
- Tables:
-
Migrations
- First migration: create all tables, FKs, and unique constraints.
- Second (or same): add indexes from Section 5.22 (primary lookups, ticket queries, message queries, notifications/search, audit/billing).
- Add
set_updated_at()trigger and apply to all tables withupdated_at(Section 5updated_atstrategy). - Add RLS policies for tenant-scoped tables (Section 4.2): when
app.current_tenant_idis set, restrict rows to that tenant.
-
Generated columns (FTS)
tickets.tsv:GENERATED ALWAYS AS (to_tsvector('english', coalesce(subject, ''))) STORED.messages.tsv:GENERATED ALWAYS AS (to_tsvector('english', coalesce(body_text, ''))) STORED.- Implement via raw SQL in migration (Drizzle may not support generated columns natively).
- GIN indexes on
(tenant_id, tsv)for tickets and messages (Section 5.22).
-
Neon
- Document or script: Neon project, pooled URL (for app), unpooled URL (for migrations). Use in
packages/dband env.
- Document or script: Neon project, pooled URL (for app), unpooled URL (for migrations). Use in
Acceptance criteria
| Criterion | Status |
|---|---|
| All tables from Section 5 exist with correct columns and types. | ✅ |
| All indexes from Section 5.22 are present. | ✅ |
updated_at trigger applied; RLS policies created. | ✅ |
| Generated columns and GIN indexes for FTS exist (used in M12). | ✅ |
Migrations run cleanly with drizzle-kit migrate (or equivalent) against a Neon DB. | ✅ |
1.3 Config and environment
Tasks
-
Env validation (
packages/config)- Zod schema for all variables in Section 9 (Database, Auth, Encryption, Email Google/Microsoft, Postmark, Stripe, Inngest, S3, Application, Sentry, Marketing).
- Export typed config object; validate on app and worker startup; fail fast with clear message if required vars missing.
-
.env.example- One entry per variable in Section 9 with short comment; no real secrets.
- Document which vars are required for local dev vs production.
Acceptance criteria
| Criterion | Status |
|---|---|
| Importing config in web or worker validates env and throws with actionable error if invalid. | ✅ |
.env.example exists and matches Section 9; copy to .env.local is sufficient for minimal local run (with placeholders). | ✅ |
1.4 Next.js skeleton and route groups
Tasks
-
Route structure (Section 6.1, 4.1)
app/(marketing)/page.tsx→ landing placeholder (e.g. "Marketing").app/(auth)/login/page.tsx,signup/page.tsx,reset-password/page.tsx,reset-password/[token]/page.tsx,invite/[token]/page.tsx→ placeholder pages (e.g. "Login", "Signup").app/app/[slug]/layout.tsx→ minimal layout (e.g. "App — slug" without auth).app/app/[slug]/page.tsx→ redirect toapp/[slug]/ticketsor placeholder.app/api/— empty or health route only (e.g.api/health/route.tsreturning{ status: "ok" }).
-
No middleware yet
- Do not implement auth or tenant resolution in M01; pages are reachable without protection. Satisfied: no
middleware.tsinapps/web; add in M02.
- Do not implement auth or tenant resolution in M01; pages are reachable without protection. Satisfied: no
-
Worker
- Inngest client init with
INNGEST_EVENT_KEYandINNGEST_SIGNING_KEYfrom config; no functions registered yet (or one no-op function so dev server runs).
- Inngest client init with
Acceptance criteria
| Criterion | Status |
|---|---|
Visiting / shows marketing placeholder; /login, /signup show placeholders. | ✅ |
Visiting /app/any-slug shows app placeholder (no 404). | ✅ |
GET /api/health returns 200 and { status: "ok" } (and optional version). | ✅ |
| Inngest dev server can run and see worker app (empty or no-op). | ✅ |
1.5 Cursor rules and skills (InboxOps)
Purpose: Add project-scoped Cursor rules and skills so the AI follows InboxOps conventions and uses the right skills for each domain. Rules orchestrate when to use which skills.
Format: Rules in .cursor/rules/*.mdc (description, globs, alwaysApply). Skills in .cursor/skills/<name>/SKILL.md (name, description, instructions). See Cursor docs and the create-rule / create-skill skills for structure.
Tasks
-
Orchestrator rule
- Create
.cursor/rules/inboxops-orchestrator.mdcwithalwaysApply: true. - Content: List all InboxOps skills and when to use them. Example: "When working on authentication, sign-in, sign-out, session, or password reset, read and apply the skill
inboxops-auth. When working on email ingestion, IMAP/Gmail/Graph, or send pipeline, useinboxops-email. When working on tenant context, slug, RLS, or multi-tenant isolation, useinboxops-tenant. When working on Stripe, subscription, trial, or entitlements, useinboxops-billing. When writing or updating internal or customer-facing documentation, useinboxops-docs. When implementing a milestone from the implementation plan, reference the corresponding Mnn file and the PRODUCT-AND-ARCHITECTURE spec." - Ensure the rule is the single place that directs which skill to use for which task.
- Create
-
Skill: inboxops-auth
- Create
.cursor/skills/inboxops-auth/SKILL.md. - Description: Auth, Lucia, session, tenant membership, middleware; use when changing sign-up, sign-in, password reset, or session handling.
- Instructions: Reference PRODUCT-AND-ARCHITECTURE §2.5, §4.3, §4.4, §5.2, §5.12, §7.2; use Lucia for session; bcrypt cost 12; HTTP-only cookie; middleware order (slug → session → membership → onboarding → lock). Point to M02 milestone for tasks.
- Create
-
Skill: inboxops-email
- Create
.cursor/skills/inboxops-email/SKILL.md. - Description: Email pipeline, IMAP/Gmail/Graph, mailparser, send; use when changing fetch, send, or credential handling.
- Instructions: Reference §2.3, §4.7, §5.4, §5.6, §6.7, §8; packages/email; imapflow, mailparser, nodemailer; dedupe (inbox_id, message_id_header); thread match; encrypt credentials; bounce_received_at. Point to M05–M06.
- Create
-
Skill: inboxops-tenant
- Create
.cursor/skills/inboxops-tenant/SKILL.md. - Description: Multi-tenancy, slug, RLS, tenant context; use when changing tenant resolution, path routing, or data isolation.
- Instructions: Reference §2.1, §4.2, §4.3, §4.5, §5.1; path-based routing only (no subdomain); useTenantPath; every query scoped by tenant_id; RLS as safety net; worker payloads include tenant_id. Point to M02–M03.
- Create
-
Skill: inboxops-billing
- Create
.cursor/skills/inboxops-billing/SKILL.md. - Description: Stripe, subscription, trial, entitlements; use when changing Checkout, Portal, webhooks, or plan limits.
- Instructions: Reference §1.7, §2.6, §5.7, §5.8, §9.7; trial_ends_at; entitlement sync idempotent; webhook handler verify signature; Enterprise no price ID. Point to M10.
- Create
-
Skill: inboxops-docs
- Create
.cursor/skills/inboxops-docs/SKILL.md. - Description: InboxOps documentation; use when writing or updating internal docs or customer-facing docs.
- Instructions: Internal: align with PRODUCT-AND-ARCHITECTURE and docs/implementation-plan; traceability to section numbers. Customer-facing (inboxops-docs): Markdown for Docusaurus; audience = users and integrators; link to API reference and guides. Point to M18 for customer docs.
- Create
Acceptance criteria
| Criterion | Status |
|---|---|
.cursor/rules/inboxops-orchestrator.mdc exists and lists all InboxOps skills with clear trigger conditions (when to use each). | ✅ |
All five skills exist under .cursor/skills/<name>/SKILL.md with valid frontmatter (name, description) and instructions that reference the spec and relevant milestones. | ✅ |
| Rules and skills are committed in the inboxops repo; rules orchestrate skills (no standalone skills without rule guidance). | ✅ |
| Agent can discover and apply the correct skill from the orchestrator rule when working in each domain. | ✅ |
1.6 i18n (next-intl) setup
Purpose: Put i18n infrastructure in place from day one (Section 3.13) so all UI strings use translations and additional locales can be added later without refactoring. English only at launch; no locale switcher in v1.
Spec reference: §3.13 Internationalisation (i18n).
Tasks
-
Install and configure next-intl
- In
apps/web: installnext-intl; configure for Next.js App Router (provider in root layout, default localeen). Section 3.13. - Ensure locale is set globally (e.g. middleware or layout); no per-user switching in v1 — infrastructure for locale switching present but toggle not exposed.
- In
-
Message file structure
- Create
messages/en/with namespace files per Section 3.13:common.json,auth.json,tickets.json,settings.json,billing.json,onboarding.json,notifications.json,reports.json,inboxes.json. Stub keys only in M01 (e.g.common.submit,auth.login); each milestone will add keys as needed. - Typed message keys: enable next-intl codegen so TypeScript errors on missing or mistyped keys; codegen runs as part of build.
- Create
-
Usage rule
- Document: every user-visible string in the UI must use
useTranslations(client) orgetTranslations(server). No hardcoded English in components. Dates and numbers useuseFormatter(formatRelativeTime, formatDateTime). Email templates (react-email) are not run through next-intl — English only for now (Section 3.13).
- Document: every user-visible string in the UI must use
Acceptance criteria
| Criterion | Status |
|---|---|
next-intl is installed and configured in apps/web; default locale en; provider in root layout. | ✅ |
messages/en/ exists with namespace files (common, auth, tickets, settings, billing, onboarding, notifications, reports, inboxes); stub keys acceptable. | ✅ |
| Message key codegen runs at build; typed keys available. | ✅ |
| Rule documented: no hardcoded user-visible strings; use useTranslations/getTranslations and useFormatter for dates/numbers. | ✅ |
Milestone 1 sign-off
| Criterion | Status |
|---|---|
| All tasks in 1.1–1.6 complete. | ✅ |
| All acceptance criteria for 1.1–1.6 met. | ✅ |
| Repo builds, migrates, and runs locally; Cursor rules and skills in place; i18n infrastructure ready; ready for M02 (Auth and tenant identity). | ✅ |