Files
platform/seed/skills/google-mail-api/SKILL.md
T
2026-02-24 21:47:36 +00:00

16 KiB

name, description
name description
Google Mail API Access, manage, and send Gmail messages using the Google Mail API. Use when the user wants to read emails, send messages, manage labels, organize threads, work with drafts, or automate email operations programmatically.

Google Mail API

RESTful API reference for the Google Mail API — access and manage Gmail mailbox data including messages, threads, labels, and drafts.

Official docs: https://developers.google.com/workspace/gmail/api/guides API Reference: https://developers.google.com/workspace/gmail/api/reference/rest Python Client: https://googleapis.github.io/google-api-python-client/

Key Concepts

Message An email message containing sender, recipients, subject, and body. Messages are immutable after creation. Each message has a unique id.

Thread A collection of related messages forming a conversation. When one or more recipients respond to a message, a thread is formed. Threads cannot be created directly, only deleted. Labels can be applied to threads.

Label A mechanism for organizing messages and threads. Two types exist:

  • System labels: Pre-defined labels like INBOX, TRASH, SPAM, DRAFT, SENT. Cannot be deleted or modified (but some can be applied/removed).
  • User labels: Custom labels created by users. Can be created, modified, and deleted.

Draft An unsent message. The message within a draft can be replaced before sending. Sending a draft automatically deletes it and creates a message with the SENT label.

Installation

Python Client Library

python3 -m pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib

Authentication & Authorization

The Gmail API uses OAuth 2.0 for authentication. You must:

  1. Create a Google Cloud project
  2. Enable the Gmail API
  3. Configure OAuth 2.0 credentials (Desktop/Web application)
  4. Obtain credentials file (credentials.json)

Scopes

Different scopes control the level of access. Common scopes:

Scope Permissions
https://www.googleapis.com/auth/gmail.readonly Read-only access to Gmail mailbox and metadata
https://www.googleapis.com/auth/gmail.send Send messages only
https://www.googleapis.com/auth/gmail.modify Full access to read/write/modify messages and labels
https://www.googleapis.com/auth/gmail.compose Compose drafts and send messages
https://www.googleapis.com/auth/gmail.labels Manage labels

OAuth 2.0 Setup (Python)

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
from googleapiclient.errors import HttpError
import os.path

SCOPES = ["https://www.googleapis.com/auth/gmail.modify"]

def authenticate():
    creds = None
    
    # Load cached credentials if available
    if os.path.exists("token.json"):
        creds = Credentials.from_authorized_user_file("token.json", SCOPES)
    
    # If no valid credentials, authenticate
    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)
        
        # Save credentials for next run
        with open("token.json", "w") as token:
            token.write(creds.to_json())
    
    return build("gmail", "v1", credentials=creds)

# Usage
service = authenticate()

Core API Methods

Users

Method Description
users().getProfile(userId="me") Get user's Gmail profile
users().watch(userId="me", body={...}) Watch for mailbox changes (push notifications)
users().stop(userId="me") Stop watching mailbox

Messages

Method Description
users().messages().list(userId="me", ...) List messages in mailbox
users().messages().get(userId="me", id=MESSAGE_ID, ...) Get full message details
users().messages().send(userId="me", body={...}) Send a message
users().messages().create(userId="me", body={...}) Insert a message directly
users().messages().import_(userId="me", body={...}) Import a message
users().messages().delete(userId="me", id=MESSAGE_ID) Delete a message
users().messages().trash(userId="me", id=MESSAGE_ID) Move message to trash
users().messages().untrash(userId="me", id=MESSAGE_ID) Restore message from trash
users().messages().modify(userId="me", id=MESSAGE_ID, body={...}) Modify message labels
users().messages().batchModify(userId="me", body={...}) Modify multiple messages at once
users().messages().batchDelete(userId="me", body={...}) Delete multiple messages at once

Attachments

Method Description
users().messages().attachments().get(userId="me", messageId=MESSAGE_ID, id=ATTACHMENT_ID) Get attachment data

Threads

Method Description
users().threads().list(userId="me", ...) List threads
users().threads().get(userId="me", id=THREAD_ID, ...) Get thread with all messages
users().threads().modify(userId="me", id=THREAD_ID, body={...}) Modify thread labels
users().threads().trash(userId="me", id=THREAD_ID) Move thread to trash
users().threads().untrash(userId="me", id=THREAD_ID) Restore thread from trash
users().threads().delete(userId="me", id=THREAD_ID) Delete thread permanently

Labels

Method Description
users().labels().list(userId="me") List all labels
users().labels().get(userId="me", id=LABEL_ID) Get label details
users().labels().create(userId="me", body={...}) Create a new label
users().labels().update(userId="me", id=LABEL_ID, body={...}) Update label
users().labels().patch(userId="me", id=LABEL_ID, body={...}) Patch label
users().labels().delete(userId="me", id=LABEL_ID) Delete label

Drafts

Method Description
users().drafts().list(userId="me", ...) List drafts
users().drafts().get(userId="me", id=DRAFT_ID) Get draft
users().drafts().create(userId="me", body={...}) Create draft
users().drafts().update(userId="me", id=DRAFT_ID, body={...}) Update draft content
users().drafts().send(userId="me", body={...}) Send draft
users().drafts().delete(userId="me", id=DRAFT_ID) Delete draft

Settings

Method Description
users().settings().getAutoForwarding(userId="me") Get auto-forwarding settings
users().settings().getImap(userId="me") Get IMAP settings
users().settings().getPop(userId="me") Get POP settings
users().settings().getVacation(userId="me") Get vacation auto-reply settings
users().settings().updateAutoForwarding(userId="me", body={...}) Update auto-forwarding
users().settings().updateVacation(userId="me", body={...}) Update vacation settings

Common Recipes

List Labels

service = authenticate()
results = service.users().labels().list(userId="me").execute()
labels = results.get("labels", [])

for label in labels:
    print(f"{label['name']} (ID: {label['id']})")

List Messages in Inbox

results = service.users().messages().list(
    userId="me",
    q="in:inbox",  # Gmail query syntax
    maxResults=10
).execute()

messages = results.get("messages", [])
for message in messages:
    msg_details = service.users().messages().get(
        userId="me",
        id=message["id"],
        format="metadata",
        metadataHeaders=["Subject", "From"]
    ).execute()
    
    headers = msg_details["payload"]["headers"]
    subject = next((h["value"] for h in headers if h["name"] == "Subject"), "No Subject")
    sender = next((h["value"] for h in headers if h["name"] == "From"), "Unknown")
    print(f"{sender}: {subject}")

Get Full Message

message = service.users().messages().get(
    userId="me",
    id=MESSAGE_ID,
    format="full"  # or "minimal" for metadata only
).execute()

# Access payload
payload = message["payload"]
headers = payload["headers"]
body = payload.get("body", {}).get("data", "")

# Decode body if base64url encoded
import base64
if body:
    decoded_body = base64.urlsafe_b64decode(body).decode()
    print(decoded_body)

Send a Message

import base64
from email.message import EmailMessage

# Create email message
message = EmailMessage()
message.set_content("This is the email body")
message["To"] = "recipient@example.com"
message["From"] = "sender@example.com"
message["Subject"] = "Test Email"

# Encode to base64url
encoded_message = base64.urlsafe_b64encode(message.as_bytes()).decode()

# Send
result = service.users().messages().send(
    userId="me",
    body={"raw": encoded_message}
).execute()

print(f"Message sent with ID: {result['id']}")

Send Email with Attachment

import base64
import mimetypes
from email.message import EmailMessage

message = EmailMessage()
message.set_content("Email with attachment")
message["To"] = "recipient@example.com"
message["From"] = "sender@example.com"
message["Subject"] = "Message with File"

# Add attachment
with open("document.pdf", "rb") as fp:
    attachment_data = fp.read()
    message.add_attachment(
        attachment_data,
        maintype="application",
        subtype="pdf",
        filename="document.pdf"
    )

# Send
encoded_message = base64.urlsafe_b64encode(message.as_bytes()).decode()
result = service.users().messages().send(
    userId="me",
    body={"raw": encoded_message}
).execute()

Create Draft

import base64
from email.message import EmailMessage

message = EmailMessage()
message.set_content("Draft email body")
message["To"] = "recipient@example.com"
message["From"] = "sender@example.com"
message["Subject"] = "Draft Subject"

encoded_message = base64.urlsafe_b64encode(message.as_bytes()).decode()

draft = service.users().drafts().create(
    userId="me",
    body={"message": {"raw": encoded_message}}
).execute()

print(f"Draft created with ID: {draft['id']}")

Apply Label to Message

# Get label ID first
labels = service.users().labels().list(userId="me").execute()
label_id = next(
    (l["id"] for l in labels["labels"] if l["name"] == "Important"),
    None
)

if label_id:
    service.users().messages().modify(
        userId="me",
        id=MESSAGE_ID,
        body={"addLabelIds": [label_id]}
    ).execute()

List Threads

results = service.users().threads().list(
    userId="me",
    q="in:inbox",
    maxResults=10
).execute()

threads = results.get("threads", [])
for thread in threads:
    thread_data = service.users().threads().get(
        userId="me",
        id=thread["id"]
    ).execute()
    
    num_messages = len(thread_data["messages"])
    print(f"Thread {thread['id']}: {num_messages} messages")

Get Thread Messages in Order

thread = service.users().threads().get(
    userId="me",
    id=THREAD_ID,
    format="full"
).execute()

messages = thread.get("messages", [])
for msg in messages:
    headers = msg["payload"]["headers"]
    subject = next((h["value"] for h in headers if h["name"] == "Subject"), "")
    sender = next((h["value"] for h in headers if h["name"] == "From"), "")
    print(f"From: {sender}")
    print(f"Subject: {subject}")
    print("---")

Search Messages (Gmail Query Syntax)

# Common query operators:
# in:inbox, in:trash, in:spam, in:sent, in:draft
# is:unread, is:read, is:starred
# from:sender@example.com, to:recipient@example.com
# subject:"search term", has:attachment
# after:YYYY/MM/DD, before:YYYY/MM/DD
# larger:1M, smaller:100K

results = service.users().messages().list(
    userId="me",
    q='is:unread from:boss@example.com after:2024/01/01',
    maxResults=10
).execute()

messages = results.get("messages", [])

Create Custom Label

label = service.users().labels().create(
    userId="me",
    body={
        "name": "My Project",
        "labelListVisibility": "labelShow",  # Show in label list
        "messageListVisibility": "show"       # Show messages in list
    }
).execute()

print(f"Label created: {label['id']}")

Modify Thread Labels

service.users().threads().modify(
    userId="me",
    id=THREAD_ID,
    body={
        "addLabelIds": [LABEL_ID_1, LABEL_ID_2],
        "removeLabelIds": [LABEL_ID_3]
    }
).execute()

Move Message to Trash

service.users().messages().trash(
    userId="me",
    id=MESSAGE_ID
).execute()

Delete Message Permanently

service.users().messages().delete(
    userId="me",
    id=MESSAGE_ID
).execute()

Batch Modify Messages

service.users().messages().batchModify(
    userId="me",
    body={
        "ids": [MESSAGE_ID_1, MESSAGE_ID_2, MESSAGE_ID_3],
        "addLabelIds": [LABEL_ID],
        "removeLabelIds": []
    }
).execute()

Get User Profile

profile = service.users().getProfile(userId="me").execute()
print(f"Email: {profile['emailAddress']}")
print(f"Messages Total: {profile['messagesTotal']}")
print(f"Threads Total: {profile['threadsTotal']}")

Query String Format

Gmail API uses Gmail search syntax for q parameter. Common operators:

  • in:inbox, in:trash, in:spam, in:sent, in:draft
  • is:unread, is:read, is:starred, is:important
  • from:email@example.com, to:email@example.com, cc:email@example.com
  • subject:text - Search in subject line
  • has:attachment - Only messages with attachments
  • filename:pdf - Attachment filename
  • before:YYYY/MM/DD, after:YYYY/MM/DD
  • larger:1M, smaller:500K - Message size
  • Combine with AND, OR, - (NOT)

Example: q='is:unread has:attachment from:boss@example.com after:2024/01/01'

Message Resource Structure

{
  "id": "...",                    // Message ID
  "threadId": "...",              // Thread ID
  "labelIds": ["INBOX", "..."],  // Applied labels
  "snippet": "...",               // Preview text
  "historyId": "...",             // History record ID
  "internalDate": "...",          // Unix timestamp
  "payload": {
    "partId": "",
    "mimeType": "...",            // e.g., "text/plain", "multipart/mixed"
    "filename": "",
    "headers": [
      { "name": "Subject", "value": "..." },
      { "name": "From", "value": "..." },
      { "name": "To", "value": "..." },
      { "name": "Date", "value": "..." }
    ],
    "body": {
      "size": 0,
      "data": "base64url encoded body"
    },
    "parts": [...]                // For multipart messages
  },
  "sizeEstimate": 0               // Size in bytes
}

Label Resource Structure

{
  "id": "...",
  "name": "...",
  "messageListVisibility": "show",     // or "hide"
  "labelListVisibility": "labelShow",  // or "labelHide"
  "type": "user",                      // or "system"
  "messagesTotal": 0,
  "messagesUnread": 0,
  "threadsTotal": 0,
  "threadsUnread": 0
}

Thread Resource Structure

{
  "id": "...",
  "historyId": "...",
  "messages": [
    { /* Message objects */ }
  ]
}

Error Handling

from googleapiclient.errors import HttpError

try:
    result = service.users().messages().get(
        userId="me",
        id=MESSAGE_ID
    ).execute()
except HttpError as error:
    print(f"Error {error.resp.status}: {error.content}")
    # Common errors:
    # 400 - Bad request
    # 403 - Forbidden (missing scope or permission)
    # 404 - Not found
    # 429 - Rate limited

Source