# Personal Gmail + Calendar Agent This project runs a small local API service that: - scans new Gmail inbox messages - classifies emails with an LLM as `LINKEDIN`, `ADVERTISING`, or `OTHER` - moves LinkedIn emails to a `LinkedIn` label/folder - moves advertising emails to an `Advertising` label/folder - exposes a secure availability endpoint powered by Google Calendar free/busy ## 1) Prerequisites - Python 3.11+ - A Google account - An OpenAI-compatible API key for the LLM classifier - A Google Cloud project with: - Gmail API enabled - Google Calendar API enabled - OAuth consent configured - OAuth Client ID of type **Desktop app** ## 2) Google OAuth setup 1. In Google Cloud Console, create a desktop OAuth client. 2. Download the client JSON file. 3. Save it in this project as `credentials.json`. The first run opens a browser window for consent and creates `token.json`. ## 3) Install and configure ```bash python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt cp .env.example .env ``` Edit `.env` and set: - `AGENT_API_KEY` to a strong secret for agent-to-agent calls - `LLM_API_KEY` and optional `LLM_MODEL` / `LLM_BASE_URL` - optional scan frequency and Gmail query ## 4) Run ```bash uvicorn app.main:app --reload ``` At startup, the scheduler runs every `GMAIL_SCAN_INTERVAL_MINUTES`. ## 5) API usage ### Health check ```bash curl http://127.0.0.1:8000/health ``` ### Manual Gmail scan ```bash curl -X POST "http://127.0.0.1:8000/scan?max_results=100" \ -H "X-API-Key: your-secret" ``` ### Availability for other AI agents ```bash curl -X POST "http://127.0.0.1:8000/availability" \ -H "Content-Type: application/json" \ -H "X-API-Key: your-secret" \ -d '{ "start": "2026-03-09T09:00:00+01:00", "end": "2026-03-09T10:00:00+01:00", "calendar_ids": ["primary"] }' ``` If `available` is `true`, there are no busy slots in that range. ## Classification behavior - LLM classification is used for each email (`LINKEDIN`, `ADVERTISING`, `OTHER`). - LinkedIn has priority over advertising inside the classifier prompt. - Set `LLM_FALLBACK_TO_RULES=true` only if you want rules-based backup when LLM calls fail. - Every scanned message gets an `AgentProcessed` label to avoid reprocessing loops. ## Notes - Gmail "folders" are labels. This agent creates: - `LinkedIn` - `Advertising` - `AgentProcessed` - Messages classified as LinkedIn/Advertising are removed from `INBOX` (moved out of inbox).