Deploy
Build & deploy
bun run build # tsc -b && vite build
bun run deploy # bun run build && wrangler deployvite build emits the SPA into dist/, then @cloudflare/vite-plugin bundles the Worker against worker/index.ts. wrangler deploy uploads both, wiring the SPA up via assets.not_found_handling: "single-page-application" in wrangler.jsonc.
Type checking
The project ships two TypeScript projects (tsconfig.app.json covers src/, tsconfig.worker.json covers worker/). Run them in sequence:
tsc -p tsconfig.app.json && tsc -p tsconfig.worker.jsonThis is also wired up as bun run typecheck.
Migrating the schema separately
The InitPage runs SCHEMA_STATEMENTS against your D1 database. If you prefer migrations:
bunx wrangler d1 migrations apply lumen-db --local
bunx wrangler d1 migrations apply lumen-db --remoteThe migrations/ folder ships with 0001_init.sql, which mirrors SCHEMA_STATEMENTS. Add new migrations there as the schema evolves.
Custom domain
In wrangler.jsonc, add a routes block pointing at your domain:
"routes": [
{ "pattern": "mail.example.com", "custom_domain": true }
]The redirect URI in the Prism app must match the same hostname; update prism_redirect_uri via the InitPage or PUT /api/init/config if you move domains.
Operational tips
- Use
bunx wrangler tailto follow runtime logs, including failed inbound parse attempts and rejected recipients. - Treat
init:configuredin KV as the gate for first-boot setup. Delete it to re-run the InitPage; it doesn't drop the schema. - For heavier mailboxes, mirror raw
.emlfiles to a backup R2 bucket and sync regularly — D1 holds metadata, but the source-of-truth message body is the R2 object.