# Gmail Reader Monorepo with a React + Vite + DaisyUI client, a Fastify API, and a Gmail push watcher. The API owns Google OAuth, stores tokens in SQLite, and calls the Gmail API with `gmail.modify`. The browser only receives an HTTP-only session cookie (read-only inbox UI). The watcher reuses those tokens for Pub/Sub receipt processing and for the agent CLI (`npm run gmail`). Agent playbooks: [AGENTS.md](AGENTS.md), [docs/usage.md](docs/usage.md), [docs/development.md](docs/development.md). ## Prerequisites - Node.js 20+ - A Google Cloud OAuth **Web application** client with: - Scope: `https://www.googleapis.com/auth/gmail.modify` - Authorized JavaScript origin: `https://oauth.0dev.web.id` (and `http://localhost:5173` for local) - Authorized redirect URIs: - `https://oauth.0dev.web.id/callback` - `http://localhost:5173/callback` (local; Vite proxies this to Fastify) ### Change Google API permissions If you previously used `gmail.readonly`, update the OAuth consent screen and re-consent: 1. **APIs & Services → Library**: Gmail API enabled. 2. **OAuth consent screen** (or **Google Auth Platform → Data Access**): add `https://www.googleapis.com/auth/gmail.modify`; remove `gmail.readonly` if listed. 3. While the app is **External + Testing**, list every mailbox under **Test users** (`gmail.modify` is sensitive). 4. Keep the same Client ID / secret and redirect URIs. 5. Restart the API and **sign in once per Gmail account** via the web UI so refresh tokens pick up the new scope. Full detail: [docs/development.md](docs/development.md#change-google-api-permissions-gmailmodify). ## Local development ```bash cp .env.example .env ``` Fill in `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, and a long `SESSION_SECRET`. Keep: ``` GOOGLE_REDIRECT_URI=http://localhost:5173/callback ``` Then: ```bash npm install npm run dev ``` - UI: http://localhost:5173 - API: http://localhost:3000 - Watcher (optional): `npm run dev:watcher` → http://localhost:3001 Vite proxies `/auth`, `/callback`, and `/api` to Fastify so the Google redirect stays on the same origin as the SPA. Sign in via the UI at least once so SQLite has a refresh token before starting the watcher or CLI. ### Agent CLI (optional) ```bash npm run gmail -- accounts npm run gmail -- list --query 'newer_than:7d' npm run gmail -- archive MESSAGE_ID --reason "noise" ``` See [docs/usage.md](docs/usage.md). Mutating commands require `--reason`. The CLI never sends mail. ## Production (one process) Build the frontend, then start Fastify. Fastify serves `apps/web/dist` and handles `/callback` itself. ```bash npm install npm run build NODE_ENV=production GOOGLE_REDIRECT_URI=https://oauth.0dev.web.id/callback npm start ``` Point your reverse proxy at the Fastify port (`PORT`, default 3000). Send the whole host to that one process — SPA, `/api`, `/auth`, and `/callback` must share `https://oauth.0dev.web.id`. Example Caddy: ``` oauth.0dev.web.id { reverse_proxy 127.0.0.1:3000 } ``` SQLite is stored at `apps/api/data/app.db`. Keep that directory writable and backed up. ## Gmail push watcher Gmail does not POST to you directly. It publishes to **Cloud Pub/Sub**, which push-delivers to `POST /pubsub/gmail`. ### GCP setup 1. Enable **Gmail API** and **Cloud Pub/Sub** in the same Google Cloud project as the OAuth client. 2. Create a topic, e.g. `projects/YOUR_PROJECT/topics/gmail-push`. 3. Grant **Pub/Sub Publisher** on that topic to `serviceAccount:gmail-api-push@system.gserviceaccount.com`. 4. Create a **push subscription** whose endpoint is publicly reachable HTTPS, e.g. `https://oauth.0dev.web.id/pubsub/gmail?token=YOUR_TOKEN`. 5. Put the topic name and token in `.env` (`GOOGLE_PUBSUB_TOPIC`, `PUBSUB_VERIFICATION_TOKEN`). Start after a user has signed in through the web app: ```bash npm run start:watcher ``` Example Caddy (API on 3000, watcher on 3001): ``` oauth.0dev.web.id { handle /pubsub/gmail* { reverse_proxy 127.0.0.1:3001 } handle { reverse_proxy 127.0.0.1:3000 } } ``` The watcher renews `users.watch` on startup and every 12 hours. New INBOX mail runs through match → parse → PocketBase (`spendings`). Details: [docs/development.md](docs/development.md). ## What it does - Sign in with Google (server-side authorization code flow; `gmail.modify`) - List / search / read mail in the web UI (read-only) - Multi-account tokens in SQLite with a configurable default mailbox - Agent CLI: list, read, summarize, labels, archive, label, draft, forward-as-draft, audit log - Watch INBOX via Gmail push (Pub/Sub) and parse e-receipts into PocketBase