How to Connect an AI Agent to Gmail (Python + Gmail API)
Updated 2026-10-15
This guide connects a Python agent to Gmail with the official Gmail API. The agent gets three tools: search messages, read a message, and create a draft (not send), so a human stays in control of what leaves your inbox.
Prerequisites
- A Google account and access to Google Cloud Console
- Python 3.10+
- The agent loop from Build your first AI agent in Python
Steps
1. Enable the Gmail API and create OAuth credentials
- In Google Cloud Console, create a project (or pick one).
- Go to APIs & Services → Library, search Gmail API, click Enable.
- Go to Google Auth Platform → Branding / Audience (older consoles call it OAuth consent screen). Choose External, fill in the app name, and add your own address as a test user.
- Go to Clients → Create client, choose Desktop app, and download the JSON. Save it as
credentials.jsonnext to your script.
2. Install the Google client libraries
pip install google-api-python-client google-auth-httplib2 google-auth-oauthlib openai
3. Authorize once and cache the token
# gmail_tools.py
import base64, os
from email.message import EmailMessage
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
SCOPES = ["https://www.googleapis.com/auth/gmail.readonly",
"https://www.googleapis.com/auth/gmail.compose"]
def gmail_service():
creds = None
if os.path.exists("token.json"):
creds = Credentials.from_authorized_user_file("token.json", SCOPES)
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
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())
return build("gmail", "v1", credentials=creds)
svc = gmail_service()
The first run opens a browser for consent. After that, token.json is reused and refreshed automatically.
4. Write the agent’s Gmail tools
def search_email(query: str, max_results: int = 5) -> str:
"""query uses Gmail search syntax, e.g. 'from:boss is:unread newer_than:2d'"""
res = svc.users().messages().list(userId="me", q=query, maxResults=max_results).execute()
out = []
for m in res.get("messages", []):
msg = svc.users().messages().get(userId="me", id=m["id"], format="metadata",
metadataHeaders=["From", "Subject", "Date"]).execute()
h = {x["name"]: x["value"] for x in msg["payload"]["headers"]}
out.append(f'{m["id"]} | {h.get("From")} | {h.get("Subject")} | {msg.get("snippet","")[:100]}')
return "\n".join(out) or "no results"
def read_email(message_id: str) -> str:
msg = svc.users().messages().get(userId="me", id=message_id, format="full").execute()
def walk(part):
if part.get("mimeType") == "text/plain" and part["body"].get("data"):
return base64.urlsafe_b64decode(part["body"]["data"]).decode(errors="ignore")
for p in part.get("parts", []):
text = walk(p)
if text: return text
return ""
return walk(msg["payload"])[:8000] or msg.get("snippet", "")
def draft_email(to: str, subject: str, body: str) -> str:
m = EmailMessage()
m["To"], m["Subject"] = to, subject
m.set_content(body)
raw = base64.urlsafe_b64encode(m.as_bytes()).decode()
d = svc.users().drafts().create(userId="me", body={"message": {"raw": raw}}).execute()
return f"draft created: {d['id']}"
5. Register them with the agent
Add the three functions to FUNCS and add JSON Schema entries to TOOLS exactly as in the first agent guide, then run:
python agent.py "Find unread emails from the last 2 days about invoices and draft a short reply to the most recent one"
6. (Optional) allow real sending
Add the https://www.googleapis.com/auth/gmail.send scope, delete token.json to re-consent, and call svc.users().messages().send(userId="me", body={"raw": raw}). Put a confirmation prompt in front of it.
No-code alternative
In n8n, add an AI Agent node and attach the Gmail Tool node as a tool, using a Gmail OAuth2 credential. The same scopes and test-user rules apply.
Common errors
Error 403: access_denied during consent: your account isn’t listed as a test user on the consent screen while the app is in Testing mode.
invalid_grant / token stops working after 7 days: refresh tokens for apps in Testing status with external users expire after 7 days. Delete token.json and re-authorize, or publish the app.
Request had insufficient authentication scopes: you changed SCOPES but kept the old token.json. Delete it and log in again.
HttpError 400 ... Invalid To header: the model produced a name instead of an address. Validate to contains @ before creating the draft.
Related
- Build an agent that runs on a schedule (e.g. a morning inbox triage)
- Connect an AI agent to Slack