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.
 
 
oabrivard 3333a4e06d Added unsubscribe email recap 1 week ago
app Added unsubscribe email recap 1 week ago
.env.example Added unsubscribe email recap 1 week ago
.gitignore Added unsubscribe email recap 1 week ago
README.md Added unsubscribe email recap 1 week ago
pyproject.toml Switched from pip to uv 1 week ago

README.md

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)
  • 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

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

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=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).