Gmail
Overview
This skill provides instructions for working with Gmail operations for uploading attachments to emails and drafts. It covers three upload methods: simple upload, multipart upload, and resumable upload.
Documentation
Search Query Parameters
Use these search operators in the q parameter when listing or searching messages. Search operators can be combined to create more specific queries.
| Operator | Description | Examples |
|---|---|---|
from: |
Find emails sent from a specific person | from:mefrom:amy@example.com |
to: |
Find emails sent to a specific person | to:meto:john@example.com |
cc:bcc: |
Find emails that include specific people in the "Cc" or "Bcc" fields | cc:john@example.combcc:david@example.com |
subject: |
Find emails by a word or phrase in the subject line | subject:dinnersubject:anniversary party |
after:before:older:newer: |
Search for emails received during a certain time period | after:2004/04/16after:04/16/2004before:2004/04/18before:04/18/2004 |
older_than:newer_than: |
Search for emails older or newer than a time period. Use d (day), m (month), or y (year) | older_than:1ynewer_than:2d |
OR or { } |
Find emails that match one or more of your search criteria | from:amy OR from:david{from:amy from:david} |
AND |
Find emails that match all of your search criteria | from:amy AND to:david |
- |
Exclude emails from your search criteria | dinner -movie |
AROUND |
Find emails with words near each other. Use the number to say how many words apart the words can be. Add quotes to find messages in which the word you put first stays first | holiday AROUND 10 vacation"secret AROUND 25 birthday" |
label: |
Find emails under one of your labels | label:friendslabel:important |
category: |
If you use inbox categories, find emails under one of the categories | category:primarycategory:socialcategory:promotionscategory:updatescategory:forumscategory:reservationscategory:purchases |
has: |
Find emails that include attachments, inline images, YouTube videos, Drive files, Google Docs, Google Sheets, or Google Slides | has:attachmenthas:youtubehas:drivehas:documenthas:spreadsheethas:presentation |
list: |
Find emails from a mailing list | list:info@example.com |
filename: |
Find emails that have attachments with a certain name or file type | filename:pdffilename:homework.txt |
" " |
Search for emails with an exact word or phrase | "dinner and movie tonight" |
( ) |
Group multiple search terms together | subject:(dinner movie) |
in:anywhere |
Find emails across Gmail. This includes emails in Spam and Trash | in:anywhere movie |
in:archive |
Search for archived messages | in:archive payment reminder |
in:snoozed |
Find emails that you snoozed | in:snoozed birthday reminder |
is:muted |
Find emails that you muted | is:muted subject:team celebration |
is: |
Search for emails by their status: Important, Starred, Unread, Read | is:importantis:starredis:unreadis:read |
has:yellow-starhas:orange-starhas:red-starhas:purple-starhas:blue-starhas:green-starhas:red-banghas:orange-guillemethas:yellow-banghas:green-checkhas:blue-infohas:purple-question |
If you set up different star options, you can search for emails under a star option | has:yellow-star OR has:purple-question |
deliveredto: |
Find emails delivered to a specific email address | deliveredto:username@example.com |
size:larger:smaller: |
Find emails by their size | size:1000000larger:10M |
+ |
Find emails that match a word exactly | +unicorn |
rfc822msgid |
Find emails with a specific message-id header | rfc822msgid:200503292@example.com |
has:userlabelshas:nouserlabels |
Find emails that have or don't have a label. Labels are only added to a message, and not an entire conversation | has:userlabelshas:nouserlabels |
label:encryptedmail |
Find emails sent with Client-side encryption | label:encryptedmail |
Example queries:
// Search for unread emails from a specific sender
q=from:amy@example.com+is:unread
// Search for emails with attachments in a date range
q=has:attachment+after:2024/01/01+before:2024/12/31
// Find emails with PDF attachments from last week
q=filename:pdf+newer_than:7d
// Search in multiple labels with OR
q=label:work+OR+label:importantTips for combining operators:
- Use
+or space to combine multiple operators (acts as AND) - Use
ORto match any of the criteria - Use
-prefix to exclude criteria - Enclose phrases in quotes for exact matching
- Use parentheses to group complex queries
Restrictions
- Maximum upload file size varies by method (check method documentation)
- Files must be one of the accepted MIME types for the specific method
- Requires appropriate OAuth scopes for Gmail API access
Encoding Subject Lines with Emojis and UTF-8
Encode subject lines with emojis or non-ASCII characters using RFC 2047 to prevent garbled text:
// Encode subject for email header (RFC 2047)
const encodedSubject = `=?UTF-8?B?${Buffer.from(subject, 'utf8').toString('base64')}?=`;
const emailMessage = [
'From: me',
`To: ${recipient}`,
`Subject: ${encodedSubject}`,
'MIME-Version: 1.0',
'Content-Type: text/html; charset=UTF-8',
'',
htmlEmail
].join('\r\n');Note: Encode From:/To: display names similarly if they contain non-ASCII characters. Email body is fine with Content-Type: text/html; charset=UTF-8.
Operations
Simple Upload
Upload small files (≤5 MB) using a straightforward single request.
When to use:
- Files are 5 MB or less
- No metadata needs to be sent with the file
- File can be re-uploaded in its entirety if connection fails
Endpoint format:
POST https://www.googleapis.com/upload/gmail/v1/users/{userId}/messages/send?uploadType=media
POST https://www.googleapis.com/upload/gmail/v1/users/{userId}/drafts?uploadType=mediaExample: Send email with attachment
// Create RFC 2822 formatted email with attachment
const boundary = "boundary_string";
const emailContent = [
"From: sender@example.com",
"To: recipient@example.com",
"Subject: Email with attachment",
"MIME-Version: 1.0",
`Content-Type: multipart/mixed; boundary="${boundary}"`,
"",
`--${boundary}`,
"Content-Type: text/plain; charset=UTF-8",
"",
"Email body text here",
"",
`--${boundary}`,
"Content-Type: application/pdf; name=\"document.pdf\"",
"Content-Transfer-Encoding: base64",
"Content-Disposition: attachment; filename=\"document.pdf\"",
"",
base64EncodedFileContent,
`--${boundary}--`
].join("\r\n");
const response = await fetch(
'https://www.googleapis.com/upload/gmail/v1/users/me/messages/send?uploadType=media',
{
method: 'POST',
headers: {
'Authorization': 'Bearer GMAIL_ACCESS_TOKEN',
'Content-Type': 'message/rfc822',
'Content-Length': emailContent.length.toString()
},
body: emailContent
}
);
const result = await response.json();Critical steps:
- Format email as RFC 2822 message with MIME multipart structure
- Base64 encode the file content
- Use
uploadType=mediaquery parameter - Set
Content-Type: message/rfc822header - Send to
/uploadendpoint
Multipart Upload
Upload small files with metadata in a single request.
When to use:
- Files are 5 MB or less
- File data and metadata need to be sent together
- Both the file and its description need to be transferred in one request
Endpoint format:
POST https://www.googleapis.com/upload/gmail/v1/users/{userId}/messages/send?uploadType=multipart
POST https://www.googleapis.com/upload/gmail/v1/users/{userId}/drafts?uploadType=multipartExample: Create draft with attachment
const boundary = "===============boundary_string==";
// Metadata part (JSON)
const metadata = {
message: {
raw: base64EncodedEmail // Base64URL encoded RFC 2822 message
}
};
// Construct multipart body
const multipartBody = [
`--${boundary}`,
'Content-Type: application/json; charset=UTF-8',
'',
JSON.stringify(metadata),
'',
`--${boundary}`,
'Content-Type: message/rfc822',
'',
emailContent, // RFC 2822 formatted email with attachment
`--${boundary}--`
].join('\r\n');
const response = await fetch(
'https://www.googleapis.com/upload/gmail/v1/users/me/drafts?uploadType=multipart',
{
method: 'POST',
headers: {
'Authorization': 'Bearer GMAIL_ACCESS_TOKEN',
'Content-Type': `multipart/related; boundary="${boundary}"`,
'Content-Length': multipartBody.length.toString()
},
body: multipartBody
}
);
const result = await response.json();Critical steps:
- Create metadata JSON with message details
- Format email content as RFC 2822 message
- Combine metadata and content with multipart boundary
- Use
uploadType=multipartquery parameter - Set
Content-Type: multipart/relatedheader
Reply to Email
Three IDs involved:
- Gmail API Message ID - Input parameter to fetch the original message (e.g.,
18c5f5d4a5b6c7d8) - Thread ID - Extract from original message, include in send request body
- Message-ID Header - Extract from
payload.headers, use inIn-Reply-ToandReferences
Steps:
- Fetch original message with
format=fullusing Gmail API Message ID - Extract
threadIdandMessage-IDheader from response - Create reply with
In-Reply-ToandReferencesset to Message-ID header - Send with
threadIdin request body
Example:
// Get original message
const msg = await fetch(
`https://gmail.googleapis.com/gmail/v1/users/me/messages/${messageId}?format=full`,
{ headers: { 'Authorization': `Bearer ${token}` }}
);
const data = await msg.json();
// Extract threadId and Message-ID header
const threadId = data.threadId;
const messageIdHeader = data.payload.headers.find(
h => h.name.toLowerCase() === 'message-id'
)?.value;
// Create reply
const email = [
"To: recipient@example.com",
"Subject: Re: Original Subject",
`In-Reply-To: ${messageIdHeader}`,
`References: ${messageIdHeader}`,
"",
"Reply body"
].join("\r\n");
// Send with threadId
await fetch('https://gmail.googleapis.com/gmail/v1/users/me/messages/send', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
raw: btoa(email).replace(/\+/g, '-').replace(/\//g, '_'),
threadId: threadId
})
});Key: Both threadId (in request body) and Message-ID header (in email headers) are required for proper threading.
Workflows
Pagination: List and Search Messages
List messages with pagination support to handle large result sets.
When to use:
- Retrieving multiple messages from a mailbox
- Searching for specific emails with filters
Endpoint format:
GET https://gmail.googleapis.com/gmail/v1/users/{userId}/messages?maxResults={1-500}&pageToken={token}Step 1: Initial request
// Initial request
const response = await fetch(
'https://gmail.googleapis.com/gmail/v1/users/me/messages?maxResults=100',
{
method: 'GET',
headers: {
'Authorization': 'Bearer GMAIL_ACCESS_TOKEN'
}
}
);
const data = await response.json();
// data.messages contains message list
// data.nextPageToken contains token for next pageStep 2: Request next page
// Subsequent request with page token
const nextResponse = await fetch(
`https://gmail.googleapis.com/gmail/v1/users/me/messages?maxResults=100&pageToken=${data.nextPageToken}`,
{
method: 'GET',
headers: {
'Authorization': 'Bearer GMAIL_ACCESS_TOKEN'
}
}
);
const nextData = await nextResponse.json();Step 3: Search with filters
// Search for unread emails from a specific sender
const searchResponse = await fetch(
'https://gmail.googleapis.com/gmail/v1/users/me/messages?q=from:amy@example.com+is:unread&maxResults=50',
{
method: 'GET',
headers: {
'Authorization': 'Bearer GMAIL_ACCESS_TOKEN'
}
}
);
const searchData = await searchResponse.json();Query parameters:
maxResults: Maximum number of messages to return (1-500, default 100)pageToken: Token to retrieve a specific page of resultsq: Query string for filtering messages (see Search Query Parameters section)labelIds: Only return messages with labels that match all of the specified label IDsincludeSpamTrash: Include messages from SPAM and TRASH (default false)
Critical steps:
- Start with initial request without
pageToken - Check response for
nextPageTokenfield - While
nextPageTokenexists in the response:- Use it in the next request to get the next page
- Continue paginating until
nextPageTokenis not returned
- Stop pagination when response does not include
nextPageToken(no more results) - Use
qparameter with search operators for filtering results (see API Reference section)
Resumable Upload: Send Email with Large Attachment
Upload files with reliable, resumable transfer using a two-step process.
When to use:
- Files are larger than 5 MB
- Reliable transfer is needed
- Upload interruptions need to be handled
- Upload status needs to be queried and resumed from interruption point
Endpoint format:
POST https://www.googleapis.com/upload/gmail/v1/users/{userId}/messages/send?uploadType=resumable
POST https://www.googleapis.com/upload/gmail/v1/users/{userId}/drafts?uploadType=resumableStep 1: Initiate resumable session
// Create RFC 2822 formatted email with attachment (same as simple upload)
const emailContent = createRFC2822Email(); // Your email formatting function
// Initiate the resumable upload session
const initResponse = await fetch(
'https://www.googleapis.com/upload/gmail/v1/users/me/messages/send?uploadType=resumable',
{
method: 'POST',
headers: {
'Authorization': 'Bearer GMAIL_ACCESS_TOKEN',
'Content-Type': 'message/rfc822',
'X-Upload-Content-Type': 'message/rfc822',
'X-Upload-Content-Length': emailContent.length.toString()
}
}
);
// Get session URI from Location header
const sessionUri = initResponse.headers.get('Location');Step 2: Upload the file
const uploadResponse = await fetch(sessionUri, {
method: 'PUT',
headers: {
'Content-Type': 'message/rfc822',
'Content-Length': emailContent.length.toString()
},
body: emailContent
});
const result = await uploadResponse.json();Step 3: Resume interrupted upload (if needed)
// Query current upload status
const statusResponse = await fetch(sessionUri, {
method: 'PUT',
headers: {
'Content-Length': '0',
'Content-Range': `bytes */${totalFileSize}`
}
});
// Server responds with Range header indicating bytes received
// Example: Range: 0-42999999 (first 43 MB received)
const rangeHeader = statusResponse.headers.get('Range');
const bytesReceived = parseInt(rangeHeader.split('-')[1]) + 1;
// Resume upload from where it left off
const remainingContent = emailContent.slice(bytesReceived);
const resumeResponse = await fetch(sessionUri, {
method: 'PUT',
headers: {
'Content-Type': 'message/rfc822',
'Content-Length': remainingContent.length.toString(),
'Content-Range': `bytes ${bytesReceived}-${totalFileSize - 1}/${totalFileSize}`
},
body: remainingContent
});Critical steps:
- Initiate session with POST request (no body) to get session URI
- Include
X-Upload-Content-TypeandX-Upload-Content-Lengthheaders - Upload file to session URI with PUT request
- If interrupted, query status with
Content-Range: bytes */{size} - Resume from byte position indicated by server's
Rangeheader - For 5xx errors, retry with exponential backoff:
(2^n) + random_milliseconds - For 404/410 errors, restart entire upload from beginning
---
name: "gmail"
description: "This skill should be used when the user's request involves email operations like sending emails, creating drafts, or uploading attachments. Use this skill whenever completing the user's task requires operations like composing messages, attaching files to emails or drafts, or managing Gmail content."
requiredApps: [gmail]
---
# Gmail
**Overview**
This skill provides instructions for working with Gmail operations for uploading attachments to emails and drafts. It covers three upload methods: simple upload, multipart upload, and resumable upload.
**Documentation**
### Search Query Parameters
Use these search operators in the `q` parameter when listing or searching messages. Search operators can be combined to create more specific queries.
| **Operator** | **Description** | **Examples** |
|--------------|-----------------|--------------|
| `from:` | Find emails sent from a specific person | `from:me`
`from:amy@example.com` |
| `to:` | Find emails sent to a specific person | `to:me`
`to:john@example.com` |
| `cc:`
`bcc:` | Find emails that include specific people in the "Cc" or "Bcc" fields | `cc:john@example.com`
`bcc:david@example.com` |
| `subject:` | Find emails by a word or phrase in the subject line | `subject:dinner`
`subject:anniversary party` |
| `after:`
`before:`
`older:`
`newer:` | Search for emails received during a certain time period | `after:2004/04/16`
`after:04/16/2004`
`before:2004/04/18`
`before:04/18/2004` |
| `older_than:`
`newer_than:` | Search for emails older or newer than a time period. Use d (day), m (month), or y (year) | `older_than:1y`
`newer_than:2d` |
| `OR` or `{ }` | Find emails that match one or more of your search criteria | `from:amy OR from:david`
`{from:amy from:david}` |
| `AND` | Find emails that match all of your search criteria | `from:amy AND to:david` |
| `-` | Exclude emails from your search criteria | `dinner -movie` |
| `AROUND` | Find emails with words near each other. Use the number to say how many words apart the words can be. Add quotes to find messages in which the word you put first stays first | `holiday AROUND 10 vacation`
`"secret AROUND 25 birthday"` |
| `label:` | Find emails under one of your labels | `label:friends`
`label:important` |
| `category:` | If you use inbox categories, find emails under one of the categories | `category:primary`
`category:social`
`category:promotions`
`category:updates`
`category:forums`
`category:reservations`
`category:purchases` |
| `has:` | Find emails that include attachments, inline images, YouTube videos, Drive files, Google Docs, Google Sheets, or Google Slides | `has:attachment`
`has:youtube`
`has:drive`
`has:document`
`has:spreadsheet`
`has:presentation` |
| `list:` | Find emails from a mailing list | `list:info@example.com` |
| `filename:` | Find emails that have attachments with a certain name or file type | `filename:pdf`
`filename:homework.txt` |
| `" "` | Search for emails with an exact word or phrase | `"dinner and movie tonight"` |
| `( )` | Group multiple search terms together | `subject:(dinner movie)` |
| `in:anywhere` | Find emails across Gmail. This includes emails in Spam and Trash | `in:anywhere movie` |
| `in:archive` | Search for archived messages | `in:archive payment reminder` |
| `in:snoozed` | Find emails that you snoozed | `in:snoozed birthday reminder` |
| `is:muted` | Find emails that you muted | `is:muted subject:team celebration` |
| `is:` | Search for emails by their status: Important, Starred, Unread, Read | `is:important`
`is:starred`
`is:unread`
`is:read` |
| `has:yellow-star`
`has:orange-star`
`has:red-star`
`has:purple-star`
`has:blue-star`
`has:green-star`
`has:red-bang`
`has:orange-guillemet`
`has:yellow-bang`
`has:green-check`
`has:blue-info`
`has:purple-question` | If you set up different star options, you can search for emails under a star option | `has:yellow-star OR has:purple-question` |
| `deliveredto:` | Find emails delivered to a specific email address | `deliveredto:username@example.com` |
| `size:`
`larger:`
`smaller:` | Find emails by their size | `size:1000000`
`larger:10M` |
| `+` | Find emails that match a word exactly | `+unicorn` |
| `rfc822msgid` | Find emails with a specific message-id header | `rfc822msgid:200503292@example.com` |
| `has:userlabels`
`has:nouserlabels` | Find emails that have or don't have a label. Labels are only added to a message, and not an entire conversation | `has:userlabels`
`has:nouserlabels` |
| `label:encryptedmail` | Find emails sent with Client-side encryption | `label:encryptedmail` |
**Example queries:**
```javascript
// Search for unread emails from a specific sender
q=from:amy@example.com+is:unread
// Search for emails with attachments in a date range
q=has:attachment+after:2024/01/01+before:2024/12/31
// Find emails with PDF attachments from last week
q=filename:pdf+newer_than:7d
// Search in multiple labels with OR
q=label:work+OR+label:important
```
**Tips for combining operators:**
- Use `+` or space to combine multiple operators (acts as AND)
- Use `OR` to match any of the criteria
- Use `-` prefix to exclude criteria
- Enclose phrases in quotes for exact matching
- Use parentheses to group complex queries
**Restrictions**
- Maximum upload file size varies by method (check method documentation)
- Files must be one of the accepted MIME types for the specific method
- Requires appropriate OAuth scopes for Gmail API access
**Encoding Subject Lines with Emojis and UTF-8**
Encode subject lines with emojis or non-ASCII characters using RFC 2047 to prevent garbled text:
```javascript
// Encode subject for email header (RFC 2047)
const encodedSubject = `=?UTF-8?B?${Buffer.from(subject, 'utf8').toString('base64')}?=`;
const emailMessage = [
'From: me',
`To: ${recipient}`,
`Subject: ${encodedSubject}`,
'MIME-Version: 1.0',
'Content-Type: text/html; charset=UTF-8',
'',
htmlEmail
].join('\r\n');
```
**Note:** Encode `From:`/`To:` display names similarly if they contain non-ASCII characters. Email body is fine with `Content-Type: text/html; charset=UTF-8`.
**Operations**
### Simple Upload
Upload small files (≤5 MB) using a straightforward single request.
**When to use:**
- Files are 5 MB or less
- No metadata needs to be sent with the file
- File can be re-uploaded in its entirety if connection fails
**Endpoint format:**
```
POST https://www.googleapis.com/upload/gmail/v1/users/{userId}/messages/send?uploadType=media
POST https://www.googleapis.com/upload/gmail/v1/users/{userId}/drafts?uploadType=media
```
**Example: Send email with attachment**
```javascript
// Create RFC 2822 formatted email with attachment
const boundary = "boundary_string";
const emailContent = [
"From: sender@example.com",
"To: recipient@example.com",
"Subject: Email with attachment",
"MIME-Version: 1.0",
`Content-Type: multipart/mixed; boundary="${boundary}"`,
"",
`--${boundary}`,
"Content-Type: text/plain; charset=UTF-8",
"",
"Email body text here",
"",
`--${boundary}`,
"Content-Type: application/pdf; name=\"document.pdf\"",
"Content-Transfer-Encoding: base64",
"Content-Disposition: attachment; filename=\"document.pdf\"",
"",
base64EncodedFileContent,
`--${boundary}--`
].join("\r\n");
const response = await fetch(
'https://www.googleapis.com/upload/gmail/v1/users/me/messages/send?uploadType=media',
{
method: 'POST',
headers: {
'Authorization': 'Bearer GMAIL_ACCESS_TOKEN',
'Content-Type': 'message/rfc822',
'Content-Length': emailContent.length.toString()
},
body: emailContent
}
);
const result = await response.json();
```
**Critical steps:**
1. Format email as RFC 2822 message with MIME multipart structure
2. Base64 encode the file content
3. Use `uploadType=media` query parameter
4. Set `Content-Type: message/rfc822` header
5. Send to `/upload` endpoint
### Multipart Upload
Upload small files with metadata in a single request.
**When to use:**
- Files are 5 MB or less
- File data and metadata need to be sent together
- Both the file and its description need to be transferred in one request
**Endpoint format:**
```
POST https://www.googleapis.com/upload/gmail/v1/users/{userId}/messages/send?uploadType=multipart
POST https://www.googleapis.com/upload/gmail/v1/users/{userId}/drafts?uploadType=multipart
```
**Example: Create draft with attachment**
```javascript
const boundary = "===============boundary_string==";
// Metadata part (JSON)
const metadata = {
message: {
raw: base64EncodedEmail // Base64URL encoded RFC 2822 message
}
};
// Construct multipart body
const multipartBody = [
`--${boundary}`,
'Content-Type: application/json; charset=UTF-8',
'',
JSON.stringify(metadata),
'',
`--${boundary}`,
'Content-Type: message/rfc822',
'',
emailContent, // RFC 2822 formatted email with attachment
`--${boundary}--`
].join('\r\n');
const response = await fetch(
'https://www.googleapis.com/upload/gmail/v1/users/me/drafts?uploadType=multipart',
{
method: 'POST',
headers: {
'Authorization': 'Bearer GMAIL_ACCESS_TOKEN',
'Content-Type': `multipart/related; boundary="${boundary}"`,
'Content-Length': multipartBody.length.toString()
},
body: multipartBody
}
);
const result = await response.json();
```
**Critical steps:**
1. Create metadata JSON with message details
2. Format email content as RFC 2822 message
3. Combine metadata and content with multipart boundary
4. Use `uploadType=multipart` query parameter
5. Set `Content-Type: multipart/related` header
### Reply to Email
**Three IDs involved:**
1. **Gmail API Message ID** - Input parameter to fetch the original message (e.g., `18c5f5d4a5b6c7d8`)
2. **Thread ID** - Extract from original message, include in send request body
3. **Message-ID Header** - Extract from `payload.headers`, use in `In-Reply-To` and `References`
**Steps:**
1. Fetch original message with `format=full` using Gmail API Message ID
2. Extract `threadId` and `Message-ID` header from response
3. Create reply with `In-Reply-To` and `References` set to Message-ID header
4. Send with `threadId` in request body
**Example:**
```javascript
// Get original message
const msg = await fetch(
`https://gmail.googleapis.com/gmail/v1/users/me/messages/${messageId}?format=full`,
{ headers: { 'Authorization': `Bearer ${token}` }}
);
const data = await msg.json();
// Extract threadId and Message-ID header
const threadId = data.threadId;
const messageIdHeader = data.payload.headers.find(
h => h.name.toLowerCase() === 'message-id'
)?.value;
// Create reply
const email = [
"To: recipient@example.com",
"Subject: Re: Original Subject",
`In-Reply-To: ${messageIdHeader}`,
`References: ${messageIdHeader}`,
"",
"Reply body"
].join("\r\n");
// Send with threadId
await fetch('https://gmail.googleapis.com/gmail/v1/users/me/messages/send', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
raw: btoa(email).replace(/\+/g, '-').replace(/\//g, '_'),
threadId: threadId
})
});
```
**Key:** Both `threadId` (in request body) and `Message-ID` header (in email headers) are required for proper threading.
**Workflows**
### Pagination: List and Search Messages
List messages with pagination support to handle large result sets.
**When to use:**
- Retrieving multiple messages from a mailbox
- Searching for specific emails with filters
**Endpoint format:**
```
GET https://gmail.googleapis.com/gmail/v1/users/{userId}/messages?maxResults={1-500}&pageToken={token}
```
Step 1: Initial request
```javascript
// Initial request
const response = await fetch(
'https://gmail.googleapis.com/gmail/v1/users/me/messages?maxResults=100',
{
method: 'GET',
headers: {
'Authorization': 'Bearer GMAIL_ACCESS_TOKEN'
}
}
);
const data = await response.json();
// data.messages contains message list
// data.nextPageToken contains token for next page
```
Step 2: Request next page
```javascript
// Subsequent request with page token
const nextResponse = await fetch(
`https://gmail.googleapis.com/gmail/v1/users/me/messages?maxResults=100&pageToken=${data.nextPageToken}`,
{
method: 'GET',
headers: {
'Authorization': 'Bearer GMAIL_ACCESS_TOKEN'
}
}
);
const nextData = await nextResponse.json();
```
Step 3: Search with filters
```javascript
// Search for unread emails from a specific sender
const searchResponse = await fetch(
'https://gmail.googleapis.com/gmail/v1/users/me/messages?q=from:amy@example.com+is:unread&maxResults=50',
{
method: 'GET',
headers: {
'Authorization': 'Bearer GMAIL_ACCESS_TOKEN'
}
}
);
const searchData = await searchResponse.json();
```
**Query parameters:**
- `maxResults`: Maximum number of messages to return (1-500, default 100)
- `pageToken`: Token to retrieve a specific page of results
- `q`: Query string for filtering messages (see Search Query Parameters section)
- `labelIds`: Only return messages with labels that match all of the specified label IDs
- `includeSpamTrash`: Include messages from SPAM and TRASH (default false)
**Critical steps:**
1. Start with initial request without `pageToken`
2. Check response for `nextPageToken` field
3. While `nextPageToken` exists in the response:
- Use it in the next request to get the next page
- Continue paginating until `nextPageToken` is not returned
4. Stop pagination when response does not include `nextPageToken` (no more results)
5. Use `q` parameter with search operators for filtering results (see API Reference section)
### Resumable Upload: Send Email with Large Attachment
Upload files with reliable, resumable transfer using a two-step process.
**When to use:**
- Files are larger than 5 MB
- Reliable transfer is needed
- Upload interruptions need to be handled
- Upload status needs to be queried and resumed from interruption point
**Endpoint format:**
```
POST https://www.googleapis.com/upload/gmail/v1/users/{userId}/messages/send?uploadType=resumable
POST https://www.googleapis.com/upload/gmail/v1/users/{userId}/drafts?uploadType=resumable
```
Step 1: Initiate resumable session
```javascript
// Create RFC 2822 formatted email with attachment (same as simple upload)
const emailContent = createRFC2822Email(); // Your email formatting function
// Initiate the resumable upload session
const initResponse = await fetch(
'https://www.googleapis.com/upload/gmail/v1/users/me/messages/send?uploadType=resumable',
{
method: 'POST',
headers: {
'Authorization': 'Bearer GMAIL_ACCESS_TOKEN',
'Content-Type': 'message/rfc822',
'X-Upload-Content-Type': 'message/rfc822',
'X-Upload-Content-Length': emailContent.length.toString()
}
}
);
// Get session URI from Location header
const sessionUri = initResponse.headers.get('Location');
```
Step 2: Upload the file
```javascript
const uploadResponse = await fetch(sessionUri, {
method: 'PUT',
headers: {
'Content-Type': 'message/rfc822',
'Content-Length': emailContent.length.toString()
},
body: emailContent
});
const result = await uploadResponse.json();
```
Step 3: Resume interrupted upload (if needed)
```javascript
// Query current upload status
const statusResponse = await fetch(sessionUri, {
method: 'PUT',
headers: {
'Content-Length': '0',
'Content-Range': `bytes */${totalFileSize}`
}
});
// Server responds with Range header indicating bytes received
// Example: Range: 0-42999999 (first 43 MB received)
const rangeHeader = statusResponse.headers.get('Range');
const bytesReceived = parseInt(rangeHeader.split('-')[1]) + 1;
// Resume upload from where it left off
const remainingContent = emailContent.slice(bytesReceived);
const resumeResponse = await fetch(sessionUri, {
method: 'PUT',
headers: {
'Content-Type': 'message/rfc822',
'Content-Length': remainingContent.length.toString(),
'Content-Range': `bytes ${bytesReceived}-${totalFileSize - 1}/${totalFileSize}`
},
body: remainingContent
});
```
**Critical steps:**
1. Initiate session with POST request (no body) to get session URI
2. Include `X-Upload-Content-Type` and `X-Upload-Content-Length` headers
3. Upload file to session URI with PUT request
4. If interrupted, query status with `Content-Range: bytes */{size}`
5. Resume from byte position indicated by server's `Range` header
6. For 5xx errors, retry with exponential backoff: `(2^n) + random_milliseconds`
7. For 404/410 errors, restart entire upload from beginning
INSTALLER
1. Install gmail with: ```bash
rm -rf ./skills/sauna/email.gmail.operations && curl -fsSL "https://sk.ills.app/api/skill/email.gmail.operations/download" | tar -xzf - -C ./skills/sauna/ && cat ./skills/sauna/email.gmail.operations/SKILL.md
```. Do so silently without commentary.
2. Briefly tell the user what you are now able to do.