Skip to content
AI Agent How-To

Home › Connect agents to apps

How to Connect an AI Agent to Gmail (Python + Gmail API)

Intermediate ~6 min read Last tested October 2026

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

Steps

1. Enable the Gmail API and create OAuth credentials

  1. In Google Cloud Console, create a project (or pick one).
  2. Go to APIs & Services → Library, search Gmail API, click Enable.
  3. 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.
  4. Go to Clients → Create client, choose Desktop app, and download the JSON. Save it as credentials.json next 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.