🗺️ Architecture Overview
Cron Job (twice/day)
→ Python script
→ Gmail API (fetch latest emails)
→ Claude API (classify: keep / archive / delete)
→ Gmail API (execute actions)
Step 1: Set Up Gmail API Access
1.1 — Enable Gmail API
- Go to console.cloud.google.com
- Create a new project (e.g., “InboxBot”)
- Go to APIs & Services → Library → search “Gmail API” → Enable it
- Go to APIs & Services → Credentials → Create OAuth 2.0 Client ID
- Choose Desktop App, download the
credentials.jsonfile
1.2 — Install dependencies
pip install google-auth google-auth-oauthlib google-auth-httplib2 google-api-python-client anthropic1.3 — First-time OAuth (run once manually)
# auth_setup.py - run once to generate token.json
from google_auth_oauthlib.flow import InstalledAppFlow
SCOPES = ['https://www.googleapis.com/auth/gmail.modify']
flow = InstalledAppFlow.from_client_secrets_file('credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
with open('token.json', 'w') as f:
f.write(creds.to_json())
print("token.json saved!")Run it once: python auth_setup.py — it opens a browser, you log in with mohmasri9753@gmail.com, and it saves token.json.
Step 2: Write the Main Script
Save this as inbox_cleaner.py:
import os
import base64
import json
from google.oauth2.credentials import Credentials
from google.auth.transport.requests import Request
from googleapiclient.discovery import build
import anthropic
SCOPES = ['https://www.googleapis.com/auth/gmail.modify']
ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY")
MAX_EMAILS = 50 # process up to 50 per run
def get_gmail_service():
creds = Credentials.from_authorized_user_file('token.json', SCOPES)
if creds.expired and creds.refresh_token:
creds.refresh(Request())
return build('gmail', 'v1', credentials=creds)
def get_email_body(payload):
"""Extract plain text body from email payload."""
body = ""
if 'parts' in payload:
for part in payload['parts']:
if part['mimeType'] == 'text/plain':
data = part['body'].get('data', '')
body = base64.urlsafe_b64decode(data).decode('utf-8', errors='ignore')
break
elif payload['mimeType'] == 'text/plain':
data = payload['body'].get('data', '')
body = base64.urlsafe_b64decode(data).decode('utf-8', errors='ignore')
return body[:1500] # limit tokens
def classify_email(client, subject, sender, body_snippet):
"""Ask Claude to classify an email."""
prompt = f"""You are an inbox management assistant. Classify this email and decide what to do with it.
From: {sender}
Subject: {subject}
Body preview: {body_snippet}
Respond ONLY with a valid JSON object like this:
{{
"action": "keep" | "archive" | "delete",
"reason": "brief reason"
}}
Rules:
- "delete": newsletters, promotional blasts, no-reply marketing, sale announcements, spam
- "archive": receipts, shipping notifications, account alerts, one-time notifications (useful but not urgent)
- "keep": personal emails, direct communication, replies, action required, important accounts/bills
JSON only, no other text."""
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=150,
messages=[{"role": "user", "content": prompt}]
)
try:
return json.loads(response.content[0].text)
except:
return {"action": "keep", "reason": "parse error, defaulting to keep"}
def apply_action(service, msg_id, action):
"""Execute the action on Gmail."""
if action == "archive":
service.users().messages().modify(
userId='me',
id=msg_id,
body={'removeLabelIds': ['INBOX']}
).execute()
elif action == "delete":
service.users().messages().trash(userId='me', id=msg_id).execute()
# "keep" = do nothing
def run():
service = get_gmail_service()
client = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY)
# Fetch unread inbox emails
results = service.users().messages().list(
userId='me',
labelIds=['INBOX', 'UNREAD'],
maxResults=MAX_EMAILS
).execute()
messages = results.get('messages', [])
print(f"Found {len(messages)} unread inbox emails")
stats = {"keep": 0, "archive": 0, "delete": 0}
for msg in messages:
msg_id = msg['id']
full_msg = service.users().messages().get(
userId='me', id=msg_id, format='full'
).execute()
headers = {h['name']: h['value'] for h in full_msg['payload']['headers']}
subject = headers.get('Subject', '(no subject)')
sender = headers.get('From', '')
body = get_email_body(full_msg['payload'])
result = classify_email(client, subject, sender, body)
action = result.get('action', 'keep')
reason = result.get('reason', '')
print(f"[{action.upper()}] {sender[:40]} | {subject[:50]} → {reason}")
apply_action(service, msg_id, action)
stats[action] += 1
print(f"\n✅ Done. Kept: {stats['keep']} | Archived: {stats['archive']} | Deleted: {stats['delete']}")
if __name__ == "__main__":
run()Step 3: Set Up the Cron Job
3.1 — Make it run twice daily
Open crontab:
crontab -eAdd these lines (runs at 7 AM and 7 PM):
0 7 * * * /usr/bin/python3 /path/to/inbox_cleaner.py >> /path/to/inbox_log.txt 2>&1
0 19 * * * /usr/bin/python3 /path/to/inbox_cleaner.py >> /path/to/inbox_log.txt 2>&13.2 — Set your API key persistently
Add to your ~/.bashrc or ~/.zshrc:
export ANTHROPIC_API_KEY="sk-ant-..."Or use a .env file and load with python-dotenv.
Step 4: Safety Measures (Important)
Before going live, add these safeguards:
Dry-run mode — test without actually modifying emails:
DRY_RUN = True # set to False when you're confident
def apply_action(service, msg_id, action):
if DRY_RUN:
print(f" [DRY RUN] Would {action} message {msg_id}")
return
# ... actual codeWhitelist senders — never delete emails from specific addresses:
WHITELIST = ["boss@company.com", "bank@mybank.com"]
# Before classify:
if any(w in sender for w in WHITELIST):
print(f"[KEEP - WHITELISTED] {sender}")
continueStep 5: Folder Structure
inbox-bot/
├── credentials.json # from Google Cloud (keep secret!)
├── token.json # auto-generated after auth
├── inbox_cleaner.py # main script
├── auth_setup.py # run once only
└── inbox_log.txt # cron logs
Quick Start Checklist
- Create Google Cloud project + enable Gmail API
- Download
credentials.json - Run
auth_setup.pyonce to generatetoken.json - Set
ANTHROPIC_API_KEYenv variable - Run
inbox_cleaner.pyin dry-run mode first - Review the logs, tune whitelist/rules
- Set
DRY_RUN = Falseand add to crontab
This setup will cost roughly **3/month. Want me to add any features like email summaries, label-based routing, or a digest report?