558 lines
16 KiB
Markdown
558 lines
16 KiB
Markdown
---
|
|
name: Google Mail API
|
|
description: 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
|
|
|
|
```bash
|
|
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)
|
|
|
|
```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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
# 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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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)
|
|
|
|
```python
|
|
# 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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
service.users().messages().trash(
|
|
userId="me",
|
|
id=MESSAGE_ID
|
|
).execute()
|
|
```
|
|
|
|
### Delete Message Permanently
|
|
|
|
```python
|
|
service.users().messages().delete(
|
|
userId="me",
|
|
id=MESSAGE_ID
|
|
).execute()
|
|
```
|
|
|
|
### Batch Modify Messages
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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
|
|
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```json
|
|
{
|
|
"id": "...",
|
|
"historyId": "...",
|
|
"messages": [
|
|
{ /* Message objects */ }
|
|
]
|
|
}
|
|
```
|
|
|
|
## Error Handling
|
|
|
|
```python
|
|
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
|
|
|
|
- Main Documentation: https://developers.google.com/workspace/gmail/api/guides
|
|
- API Reference: https://developers.google.com/workspace/gmail/api/reference/rest
|
|
- Python Quickstart: https://developers.google.com/workspace/gmail/api/quickstart/python
|
|
- Python Client Docs: https://googleapis.github.io/google-api-python-client/docs/dyn/gmail_v1.html
|
|
- Authentication: https://developers.google.com/identity/protocols/oauth2
|
|
- Service: gmail.googleapis.com (v1)
|