This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in node_modules/next/dist/docs/ (resolved from this file's directory; in monorepos the next package may not be visible from the repo root) before writing any code. Heed deprecation notices.
This block is written and re-added by next dev — verify at node_modules/next/dist/server/lib/generate-agent-files.js. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.
Dispatch is an open-source collaborative inbox (shared inboxes, internal comments, assignments, rules) — a self-hostable alternative to Missive/Front. Single Next.js 16 app + a worker process.
- Next.js 16 (App Router, Turbopack,
src/proxy.tsinstead of middleware), React 19, TypeScript - Tailwind CSS v4 + shadcn/ui (radix,
src/components/ui/*), lucide-react icons, motion, sonner toasts - PostgreSQL + Drizzle ORM (
src/server/db/schema.ts), postgres.js driver - TanStack Query for client data, SSE + Postgres LISTEN/NOTIFY for realtime (no Redis)
- imapflow / mailparser / nodemailer for mail; worker in
src/worker(separate container)
params/searchParams/cookies()/headers()are async — alwaysawaitthem.- Use the generated global helpers
PageProps<"/w/[slug]/inbox">,LayoutProps<"/w/[slug]">, andRouteContext<"/api/w/[slug]/x">for typing. Runpnpm next typegenafter adding routes. - Middleware is called Proxy (
src/proxy.ts). Parallel routes needdefault.tsx. - Docs for this exact version:
node_modules/next/dist/docs/.
src/server/**— server-only modules (import "server-only"). Never import from client components.db(db,schema),env,crypto(encrypt/decrypt/hash/tokens),settings(instance settings),auth/session,auth/magic-link,authz(org context + permissions),access(inbox visibility),api(route helpers),action(server action helper),audit,realtime(publish/subscribe),jobs(enqueueJob, emitWebhook),notifications(notify),storage(attachments),orgs
src/lib/**— isomorphic helpers (utils,permissions,api-client)src/components/ui— shadcn primitives.src/components/app— shared app chrome (OrgProvider, UserMenu, WorkspaceSwitcher, UserAvatar). Feature components live insrc/components/<area>/.
- Pages/layouts:
const ctx = await requireOrgPage(slug);requirePagePermission(ctx, "members.manage"). - Server actions:
"use server", wrap withaction(zodSchema, async (input) => {...}), inside callconst ctx = await requireOrg(input.slug),assertPermission(ctx, "..."),assertWritable(ctx). - Route handlers:
export const GET = route<{ slug: string }>(async (req, { params }) => { const ctx = await requireApiOrg(req, (await params).slug); ... return json(data) }). Thrownew ApiError(status, message)for errors. Cookie-auth mutations are CSRF-checked automatically. - Super admin:
requireSuperAdminPage()/requireSuperAdmin()/requireApiSuperAdmin(req). - Every tenant query MUST filter by
orgId. Conversation visibility MUST go throughsrc/server/access.ts. - Audit important mutations with
audit({ orgId, actorId, action: "resource.verb", targetType, targetId, metadata }). - After mutations that other users should see:
publish({ orgId, type, conversationId })(clients refetch). - Secrets are stored encrypted (
encrypt/encryptJson); never return secrets to the client (redactSecrets).
.envcontains onlyDOMAIN. Everything else is configured in the UI (instance settings in/admin, workspace settings in/w/[slug]/settings) and stored in Postgres.- Instance settings:
getSettings("email" | "auth" | "general" | "billing" | "oauth" | "storage" | "ai" | "branding" | "security" | "legal" | "setup").
- Warm paper palette:
bg-background(#FAF9F5),bg-surface,bg-card(white), inkforeground, signal green accentbrand(text-brand,bg-brand,bg-brand-soft). Full dark mode support is required. - Headings: tight tracking (
tracking-tight/.tracking-display), two-tone with<span className="text-quiet">. - Small labels:
font-mono text-[11px] uppercase tracking-wider text-muted-foreground. - App UI is dense but calm: 13–14px text, hairline borders, subtle hover states, generous empty states.
- Mobile first: every screen must work at 375px width (stack panes, sheets/drawers instead of side panels,
bottom-safe-area padding). Use
useIsMobile()from@/hooks/use-mobilewhen needed. - Accessible: labelled inputs, focus rings, keyboard navigation,
aria-*on icon buttons.
- Edit
src/server/db/schema.ts, thenpnpm db:generate --name <change>andpnpm db:migrate. - Migrations run automatically on container start.
pnpm dev(web),pnpm dev:worker(worker),pnpm services(Postgres/Mailpit/GreenMail via Docker)pnpm typecheck,pnpm lint,pnpm test,pnpm buildpnpm dev:session you@example.com --super-admin --org "Acme"prints a session cookie for API testing.