- Node.js 22+
- pnpm 10.32.1
- Docker and Docker Compose
- Gmail OAuth credentials (optional, for real Gmail testing)
# Install dependencies
pnpm install
# Start local infrastructure (PostgreSQL, etc.)
pnpm docker:up
# Generate and run database migrations
pnpm db:generate
pnpm db:migrate
# Start API and worker in development mode
pnpm devDefault local URLs:
- API:
http://127.0.0.1:3000 - Worker:
http://127.0.0.1:3001
pnpm install # Install all dependencies
pnpm install <pkg> # Add a new dependency
pnpm update # Update dependenciespnpm db:generate # Generate migrations from schema changes
pnpm db:migrate # Run pending migrations
pnpm db:push # Push schema directly (dev only)pnpm dev # Run API and worker together
pnpm dev:api # Run API only
pnpm dev:worker # Run worker only
pnpm docker:up # Start local containers
pnpm docker:down # Stop local containerspnpm build # Build all packages
pnpm build --filter <pkg> # Build a specific packagepnpm test # Run all tests
pnpm test:watch # Run tests in watch mode
pnpm test:coverage # Run tests with coverage reportpnpm lint # Run linter (oxlint)
pnpm lint:fix # Auto-fix linting issues
pnpm format # Format code (oxfmt)
pnpm format:check # Check if code is formatted
pnpm typecheck # Run TypeScript type checkingCreate a .env file in the root:
NODE_ENV=development
DATABASE_URL=postgres://mailmon:mailmon@127.0.0.1:5432/mailmon
MAILMON_ASYNC_TRANSPORT_MODE=local
MAILMON_WORKER_BASE_URL=http://127.0.0.1:3001
# Generate a 32-byte base64 encryption key
MAILMON_GMAIL_REFRESH_TOKEN_ENCRYPTION_KEY=<your-key>
MAILMON_GMAIL_REFRESH_TOKEN_ENCRYPTION_KEY_ID=primary
# For real Gmail OAuth testing
MAILMON_GMAIL_OAUTH_CLIENT_ID=<your-client-id>
MAILMON_GMAIL_OAUTH_CLIENT_SECRET=<your-client-secret>Generate a local encryption key:
node -e "console.log(require('node:crypto').randomBytes(32).toString('base64'))"The CLI is located in apps/cli and provides operator commands for local development and infrastructure operations.
pnpm --filter @mailmon/cli dev -- admin workspace createpnpm --filter @mailmon/cli dev -- admin keys create --workspace-id <workspace-id># Run a sync for a specific mailbox
pnpm --filter @mailmon/cli dev -- sync-mailbox <mailbox-id>
# Run a control job
pnpm --filter @mailmon/cli dev -- control-job recover_stuck_syncs
pnpm --filter @mailmon/cli dev -- control-job recover_webhook_deliveries# Audit persisted Gmail credential envelopes
pnpm --filter @mailmon/cli dev -- gmail-credentials audit
# Rewrap credentials with the current encryption key
pnpm --filter @mailmon/cli dev -- gmail-credentials rewrap# Forward webhook deliveries to a local app
pnpm --filter @mailmon/cli dev -- listen \
--forward-to http://localhost:4000/webhooks/mailmon
# Replay stored events into a local endpoint
pnpm --filter @mailmon/cli dev -- replay \
--mailbox <mailbox-id> \
--last 1h \
--forward-to http://localhost:4000/webhooks/mailmonmailmon-dev/
├── apps/
│ ├── api/ # Hono-based public HTTP API
│ ├── worker/ # Sync, Gmail push, webhook delivery, control jobs
│ ├── cli/ # Local dev and operator commands
│ └── docs/ # Mintlify documentation site
│
├── packages/
│ ├── core/ # Domain contracts, use cases, Effect service interfaces
│ ├── db/ # Drizzle schema, persistence adapters, migrations
│ ├── gmail/ # Gmail OAuth, sync provider, token crypto
│ ├── queue/ # Local dispatch, Pub/Sub, Cloud Tasks adapters
│ └── config/ # Shared configuration and runtime modes
│
├── infra/ # Terraform for GCP infrastructure
├── docs/ # Documentation
├── plans/ # Architecture and implementation plans
└── docker-compose.yml # Local services definition
- Mailbox is the unit of work: All state is mailbox-scoped. No account-scoped queues or workflows.
- Effect service interfaces: Transport-neutral contracts allow the same workflow to run locally or on GCP.
- Transactional state commits: Sync finalization, cursor advancement, events, and lease release happen atomically.
- Durable event log: All mailbox changes are recorded as immutable events in the database.
- Database-backed leases: Only one sync executes per mailbox at a time, coordinated through the database.
DEBUG=mailmon:* pnpm dev# Connect to local Postgres
psql postgres://mailmon:mailmon@127.0.0.1:5432/mailmon
# View recent sync runs
SELECT * FROM sync_runs ORDER BY created_at DESC LIMIT 10;
# View mailbox events
SELECT * FROM mailbox_events ORDER BY created_at DESC LIMIT 20;
# Check mailbox leases
SELECT * FROM mailbox_leases;curl -X POST http://127.0.0.1:3000/v1/mailboxes/connect-sessions \
-H "authorization: Bearer $MAILMON_API_KEY" \
-H "content-type: application/json" \
-d '{
"provider": "gmail",
"tenantExternalId": "tenant_demo",
"mailboxExternalId": "primary",
"redirectUrl": "http://localhost:3000/connected"
}'Tests live alongside source code in __tests__ directories:
pnpm test --run packages/coreWorker and API integration tests verify real workflows:
pnpm test --run apps/worker
pnpm test --run apps/apipnpm test:watch --filter @mailmon/core- Create directory in
packages/orapps/ - Initialize
package.jsonwith name and dependencies - Add to
pnpm-workspace.yamlif needed - Run
pnpm installto link workspaces
- Edit schema in
packages/db/src/schema.ts - Generate migration:
pnpm db:generate - Review the generated SQL in
packages/db/src/migrations/ - Run migration:
pnpm db:migrate
- Define the route in
apps/api/src/routes/ - Implement the handler using core use cases
- Add tests in
apps/api/src/__tests__/ - Update API examples in README if user-facing
Infrastructure is managed by Terraform in infra/:
cd infra
terraform plan
terraform applyAlways consult effect-solutions before writing Effect code:
pnpm exec effect-solutions list
pnpm exec effect-solutions show services-and-layersKey patterns for this project:
- Layers: Service dependencies are wired through Effect layers in app runtimes
- Transport-neutral workflows: Core use cases compose Effect programs without importing HTTP/queue/DB adapters
- Error handling: Use structured Problem envelopes for API errors, Last Error for resource degradation
- Configuration: Runtime modes (local/gcp) are modeled in Effect layers, not ad hoc env checks
# Run with profiling
node --prof apps/api/dist/index.js
node --prof-process isolate-*.log | head -100# Enable query logging
DATABASE_LOG_LEVEL=debug pnpm dev# Clear build caches
rm -rf packages/*/dist apps/*/dist
# Rebuild
pnpm build# Verify Docker containers are running
docker-compose ps
# Check connection string
echo $DATABASE_URL
# Test with psql
psql $DATABASE_URL# Clear tsbuildinfo files
find . -name ".tsbuildinfo" -delete
# Rebuild
pnpm build- PRD: docs/PRD.md - Product requirements and roadmap
- Architecture Plan: plans/mailmon-gmail-sync-infrastructure.md
- Domain Language: UBIQUITOUS_LANGUAGE.md
- ADRs: docs/adr/ - Architecture decision records
- Effect Documentation: Effect Docs