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.
3.2 KiB
3.2 KiB
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, orOTHER - moves LinkedIn emails to a
LinkedInlabel/folder - moves advertising emails to an
Advertisinglabel/folder - scans the
Advertisinglabel 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)- 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
- In Google Cloud Console, create a desktop OAuth client.
- Download the client JSON file.
- 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
uv sync
cp .env.example .env
Edit .env and set:
AGENT_API_KEYto a strong secret for agent-to-agent callsLLM_API_KEYand optionalLLM_MODEL/LLM_BASE_URL- optional unsubscribe digest settings (
UNSUBSCRIBE_*) - optional scan frequency and Gmail query
4) Run
uv run uvicorn app.main:app --reload
At startup, the scheduler runs every GMAIL_SCAN_INTERVAL_MINUTES.
5) API usage
Health check
curl http://127.0.0.1:8000/health
Manual Gmail scan
curl -X POST "http://127.0.0.1:8000/scan?max_results=100" \
-H "X-API-Key: your-secret"
Availability for other AI agents
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
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=trueonly if you want rules-based backup when LLM calls fail. - Every scanned message gets an
AgentProcessedlabel to avoid reprocessing loops.
Unsubscribe digest behavior
- Reads emails from
UNSUBSCRIBE_QUERY(defaultlabel:Advertising). - Extracts unsubscribe URLs from
List-Unsubscribeheaders 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:
LinkedInAdvertisingAgentProcessed
- Messages classified as LinkedIn/Advertising are removed from
INBOX(moved out of inbox).