Skip to main content

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

  1. Initialize monorepo

    • Create root package.json with pnpm workspaces (packages/*, apps/*).
    • Add Turborepo; configure turbo.json with pipeline (build, dev, lint, test).
    • Ensure pnpm install and turbo build run from root.
  2. Create apps

    • apps/web: Next.js 14+ with App Router; TypeScript; minimal app/ 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.
  3. 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

CriterionStatus
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

  1. 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).
  2. 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 with updated_at (Section 5 updated_at strategy).
    • Add RLS policies for tenant-scoped tables (Section 4.2): when app.current_tenant_id is set, restrict rows to that tenant.
  3. 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).
  4. Neon

    • Document or script: Neon project, pooled URL (for app), unpooled URL (for migrations). Use in packages/db and env.

Acceptance criteria

CriterionStatus
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

  1. 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.
  2. .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

CriterionStatus
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

  1. 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 to app/[slug]/tickets or placeholder.
    • app/api/ — empty or health route only (e.g. api/health/route.ts returning { status: "ok" }).
  2. No middleware yet

    • Do not implement auth or tenant resolution in M01; pages are reachable without protection. Satisfied: no middleware.ts in apps/web; add in M02.
  3. Worker

    • Inngest client init with INNGEST_EVENT_KEY and INNGEST_SIGNING_KEY from config; no functions registered yet (or one no-op function so dev server runs).

Acceptance criteria

CriterionStatus
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

  1. Orchestrator rule

    • Create .cursor/rules/inboxops-orchestrator.mdc with alwaysApply: 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, use inboxops-email. When working on tenant context, slug, RLS, or multi-tenant isolation, use inboxops-tenant. When working on Stripe, subscription, trial, or entitlements, use inboxops-billing. When writing or updating internal or customer-facing documentation, use inboxops-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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.

Acceptance criteria

CriterionStatus
.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

  1. Install and configure next-intl

    • In apps/web: install next-intl; configure for Next.js App Router (provider in root layout, default locale en). 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.
  2. 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.
  3. Usage rule

    • Document: every user-visible string in the UI must use useTranslations (client) or getTranslations (server). No hardcoded English in components. Dates and numbers use useFormatter (formatRelativeTime, formatDateTime). Email templates (react-email) are not run through next-intl — English only for now (Section 3.13).

Acceptance criteria

CriterionStatus
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

CriterionStatus
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).