--- 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)