You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
114 lines
3.2 KiB
Markdown
114 lines
3.2 KiB
Markdown
# 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
|
|
- scans the `Advertising` label and emails you new unsubscribe links (deduplicated)
|
|
- exposes a secure availability endpoint powered by Google Calendar free/busy
|
|
|
|
## 1) Prerequisites
|
|
|
|
- Python 3.11+
|
|
- `uv` ([installation guide](https://docs.astral.sh/uv/getting-started/installation/))
|
|
- 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`.
|
|
If your existing token was created before `gmail.send` was added, you may be prompted again.
|
|
|
|
## 3) Install and configure
|
|
|
|
```bash
|
|
uv sync
|
|
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 unsubscribe digest settings (`UNSUBSCRIBE_*`)
|
|
- optional scan frequency and Gmail query
|
|
|
|
## 4) Run
|
|
|
|
```bash
|
|
uv run 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.
|
|
|
|
### Manual unsubscribe digest
|
|
|
|
```bash
|
|
curl -X POST "http://127.0.0.1:8000/unsubscribe-digest?max_results=500" \
|
|
-H "X-API-Key: your-secret"
|
|
```
|
|
|
|
## 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.
|
|
|
|
## Unsubscribe digest behavior
|
|
|
|
- Reads emails from `UNSUBSCRIBE_QUERY` (default `label:Advertising`).
|
|
- Extracts unsubscribe URLs from `List-Unsubscribe` headers and message content.
|
|
- Removes duplicates within the run and across runs.
|
|
- Persists already sent links in `UNSUBSCRIBE_STATE_FILE`.
|
|
- Sends only new links by email, unless `UNSUBSCRIBE_SEND_EMPTY_DIGEST=true`.
|
|
|
|
## Notes
|
|
|
|
- Gmail "folders" are labels. This agent creates:
|
|
- `LinkedIn`
|
|
- `Advertising`
|
|
- `AgentProcessed`
|
|
- Messages classified as LinkedIn/Advertising are removed from `INBOX` (moved out of inbox).
|