# Contributing Thanks for considering a contribution. The codebase is small and opinionated — please read this once before opening a PR so we stay aligned. ## Ground rules - **Read `AGENTS.md` first.** It captures the invariants (single Worker, RBAC on every route, R2 key namespaces, etc.) that keep the project safe to change. They apply to humans too. - **One Worker.** Don't split `apps/worker` into multiple Workers or add a separate API service. ## Branching and PRs - Branch off `main`. Name branches `/` (e.g. `feat/labels-ui`, `fix/thread-merge`, `docs/deploy-guide`). - There is no protected `develop` branch — main is the trunk. Schema changes don't need a migration window: edit `packages/db/src/schema.ts`, regenerate (`pnpm db:generate`), and ship. - Keep PRs focused. A bug fix doesn't need surrounding cleanup; a new feature doesn't need a refactor folded in. - If your PR addresses an item in [issue #1](https://github.com/Newspicel/cloudflare-mail/issues/1), link it in the description so the tracker stays accurate. ### Required local checks Run before opening the PR: ```bash pnpm typecheck # tsgo across all packages pnpm lint # oxlint + biome pnpm build # Vite + wrangler deploy --dry-run pnpm test # vitest (worker) ``` All four must be green. `pnpm lint` runs oxlint and Biome — both. Don't silence a rule in one without considering the other. ### Commit messages Conventional Commits, scoped where it helps reviewers: ``` feat(worker): bounce-loop protection on inbound fix(web): keep compose dock open after attachment upload perf(worker): parallelize attachment fetch in send pipeline docs: add deployment guide ``` Squash-merge is fine; the PR title becomes the commit on `main`. ## Running locally against a real Cloudflare account `wrangler dev` against the real Workers runtime is the only way to exercise the email bindings end-to-end. Local-only D1/R2 is good for UI work; for mail flow you want both ends real. ### One-time setup Follow [`docs/DEPLOY.md`](./docs/DEPLOY.md) against a **personal / staging zone** you control. You'll end up with: - Wrangler logged in (`wrangler login`) - A real D1 database and R2 bucket - The zone's MX records pointed at Cloudflare Email Routing - A verified Email Sending domain No `BETTER_AUTH_SECRET` setup is required — the Worker lazy-generates one on first request and stores it in D1 (`system_config`). Per-domain config (which mailbox kinds a domain accepts, who can create what) lives in D1 too. ### Local dev loop ```bash # Vite (:5173) proxies /api to Wrangler (:8787) pnpm dev ``` Two important flags when developing against real bindings: - **D1**: `wrangler dev` uses a local SQLite by default. To hit your real D1, run `pnpm --filter @cfmail/worker exec wrangler dev --remote` instead. Same for R2. - **Email Sending**: the `EMAIL` binding is already `"remote": true` in `wrangler.jsonc`, so `env.EMAIL.send()` reaches Cloudflare Email Sending even under local `wrangler dev`. Use a test sending domain so you don't spam real users. - **Inbound (`email()`)**: cannot be triggered by `wrangler dev`. Either deploy to a staging Worker and use Email Routing for real inbound, or write a `.eml` fixture and feed it through the integration test suite (`apps/worker/test/inbound.test.ts` shows the shape). - **Cron (`scheduled()`)**: doesn't fire under `wrangler dev` automatically. Run `wrangler dev --test-scheduled` and POST to `/__scheduled` to trigger it. ### Testing your changes - `pnpm test` runs the worker integration suite via `@cloudflare/vitest-plugin` — it spins up a real Workers runtime against a local D1 + R2, so inbound, RBAC, threading, and cron logic are all exercised without a real Cloudflare account. - For UI changes, run `pnpm dev` and click through the affected flows in a browser. Type-checking does not catch UX regressions. ## Code style - Follow the existing patterns. When in doubt, grep for similar code rather than introducing a new abstraction. - No comments that restate the code; only comments that explain *why* a non-obvious decision was made. - No backwards-compat shims or feature flags for code that doesn't exist yet. There's no production deployment to migrate from. - Don't reintroduce `composite: true` project references — the workspace resolves via `paths`, not `tsc --build`. - New runtime dependencies must work on the Workers runtime (no `fs`, no native modules, no Node-only APIs unless `nodejs_compat` covers them). ## Reporting bugs / requesting features Open an issue with a minimal repro (or, for features, the user-visible behavior you want). If it's a sub-task of [the roadmap tracker](https://github.com/Newspicel/cloudflare-mail/issues/1), link back to it. ## License By contributing, you agree your contributions are licensed under the [MIT License](./LICENSE) that covers the project.