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

1341 lines
35 KiB
Markdown

---
name: Google Mail API (Node.js/TypeScript)
description: Access, manage, and send Gmail messages using the Google Mail API with Node.js/TypeScript. 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 (Node.js/TypeScript)
RESTful API reference for the Google Mail API — access and manage Gmail mailbox data including messages, threads, labels, and drafts using Node.js or TypeScript.
Official docs: https://developers.google.com/workspace/gmail/api/guides
API Reference: https://developers.google.com/workspace/gmail/api/reference/rest
Node.js Client: https://github.com/googleapis/google-api-nodejs-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
### Node.js/TypeScript Dependencies
```bash
npm install googleapis google-auth-library
# For TypeScript support
npm install --save-dev @types/node typescript
```
## 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 (Node.js/TypeScript)
```typescript
import fs from "fs/promises";
import path from "path";
import { google } from "googleapis";
import { OAuth2Client } from "google-auth-library";
const SCOPES = ["https://www.googleapis.com/auth/gmail.modify"];
const CREDENTIALS_PATH = "credentials.json";
const TOKEN_PATH = "token.json";
async function authenticate(): Promise<OAuth2Client> {
// Load credentials
const credentialsContent = await fs.readFile(CREDENTIALS_PATH, "utf-8");
const credentials = JSON.parse(credentialsContent);
const { client_id, client_secret, redirect_uris } = credentials.installed;
const oauth2Client = new google.auth.OAuth2(
client_id,
client_secret,
redirect_uris[0]
);
// Check if we have a cached token
try {
const tokenContent = await fs.readFile(TOKEN_PATH, "utf-8");
const token = JSON.parse(tokenContent);
oauth2Client.setCredentials(token);
// Refresh if expired
if (token.expiry_date && token.expiry_date < Date.now()) {
const newToken = await oauth2Client.refreshAccessToken();
oauth2Client.setCredentials(newToken.credentials);
await fs.writeFile(TOKEN_PATH, JSON.stringify(newToken.credentials));
}
return oauth2Client;
} catch (err) {
// No token, need to authenticate
return authenticateUser(oauth2Client);
}
}
async function authenticateUser(
oauth2Client: OAuth2Client
): Promise<OAuth2Client> {
const authUrl = oauth2Client.generateAuthUrl({
access_type: "offline",
scope: SCOPES,
});
console.log("Authorize this app by visiting this url:", authUrl);
// In a real app, you'd use a callback server or prompt the user to visit the URL
// and paste the code back
const code = "AUTHORIZATION_CODE_FROM_USER"; // Get this from user input/callback
const { tokens } = await oauth2Client.getToken(code);
oauth2Client.setCredentials(tokens);
// Save token for future use
await fs.writeFile(TOKEN_PATH, JSON.stringify(tokens));
return oauth2Client;
}
// Usage
const auth = await authenticate();
const gmail = google.gmail({ version: "v1", auth });
```
## Core API Methods
### Users
| Method | Description |
|--------|------------|
| `gmail.users.getProfile()` | Get user's Gmail profile |
| `gmail.users.watch()` | Watch for mailbox changes (push notifications) |
| `gmail.users.stop()` | Stop watching mailbox |
### Messages
| Method | Description |
|--------|------------|
| `gmail.users.messages.list()` | List messages in mailbox |
| `gmail.users.messages.get()` | Get full message details |
| `gmail.users.messages.send()` | Send a message |
| `gmail.users.messages.create()` | Insert a message directly |
| `gmail.users.messages.import()` | Import a message |
| `gmail.users.messages.delete()` | Delete a message |
| `gmail.users.messages.trash()` | Move message to trash |
| `gmail.users.messages.untrash()` | Restore message from trash |
| `gmail.users.messages.modify()` | Modify message labels |
| `gmail.users.messages.batchModify()` | Modify multiple messages at once |
| `gmail.users.messages.batchDelete()` | Delete multiple messages at once |
### Attachments
| Method | Description |
|--------|------------|
| `gmail.users.messages.attachments.get()` | Get attachment data |
### Threads
| Method | Description |
|--------|------------|
| `gmail.users.threads.list()` | List threads |
| `gmail.users.threads.get()` | Get thread with all messages |
| `gmail.users.threads.modify()` | Modify thread labels |
| `gmail.users.threads.trash()` | Move thread to trash |
| `gmail.users.threads.untrash()` | Restore thread from trash |
| `gmail.users.threads.delete()` | Delete thread permanently |
### Labels
| Method | Description |
|--------|------------|
| `gmail.users.labels.list()` | List all labels |
| `gmail.users.labels.get()` | Get label details |
| `gmail.users.labels.create()` | Create a new label |
| `gmail.users.labels.update()` | Update label |
| `gmail.users.labels.patch()` | Patch label |
| `gmail.users.labels.delete()` | Delete label |
### Drafts
| Method | Description |
|--------|------------|
| `gmail.users.drafts.list()` | List drafts |
| `gmail.users.drafts.get()` | Get draft |
| `gmail.users.drafts.create()` | Create draft |
| `gmail.users.drafts.update()` | Update draft content |
| `gmail.users.drafts.send()` | Send draft |
| `gmail.users.drafts.delete()` | Delete draft |
### Settings
| Method | Description |
|--------|------------|
| `gmail.users.settings.getAutoForwarding()` | Get auto-forwarding settings |
| `gmail.users.settings.getImap()` | Get IMAP settings |
| `gmail.users.settings.getPop()` | Get POP settings |
| `gmail.users.settings.getVacation()` | Get vacation auto-reply settings |
| `gmail.users.settings.updateAutoForwarding()` | Update auto-forwarding |
| `gmail.users.settings.updateVacation()` | Update vacation settings |
## Common Recipes
### List Labels
```typescript
async function listLabels(gmail: any) {
try {
const result = await gmail.users.labels.list({
userId: "me",
});
const labels = result.data.labels || [];
labels.forEach((label: any) => {
console.log(`${label.name} (ID: ${label.id})`);
});
return labels;
} catch (error) {
console.error("Error listing labels:", error);
throw error;
}
}
```
### List Messages in Inbox
```typescript
async function listInboxMessages(gmail: any, maxResults = 10) {
try {
const result = await gmail.users.messages.list({
userId: "me",
q: "in:inbox",
maxResults,
});
const messages = result.data.messages || [];
for (const message of messages) {
const msgDetails = await gmail.users.messages.get({
userId: "me",
id: message.id,
format: "metadata",
metadataHeaders: ["Subject", "From"],
});
const headers = msgDetails.data.payload.headers;
const subject =
headers.find((h: any) => h.name === "Subject")?.value || "No Subject";
const from =
headers.find((h: any) => h.name === "From")?.value || "Unknown";
console.log(`${from}: ${subject}`);
}
return messages;
} catch (error) {
console.error("Error listing messages:", error);
throw error;
}
}
```
### Get Full Message
```typescript
async function getFullMessage(gmail: any, messageId: string) {
try {
const message = await gmail.users.messages.get({
userId: "me",
id: messageId,
format: "full",
});
const payload = message.data.payload;
const headers = payload.headers;
const bodyData = payload.body?.data || "";
// Decode base64url body
const decodedBody = bodyData
? Buffer.from(bodyData, "base64").toString()
: "";
return {
id: message.data.id,
threadId: message.data.threadId,
headers,
body: decodedBody,
labelIds: message.data.labelIds,
};
} catch (error) {
console.error("Error getting message:", error);
throw error;
}
}
```
### Send a Message
```typescript
import nodemailer from "nodemailer";
async function sendMessage(
gmail: any,
to: string,
subject: string,
body: string
) {
try {
// Create email using nodemailer format for easier composition
const mailOptions = {
to,
subject,
text: body,
};
// Simulate creating RFC 2822 formatted message
const message =
`From: sender@example.com\r\n` +
`To: ${to}\r\n` +
`Subject: ${subject}\r\n` +
`\r\n` +
`${body}`;
const encodedMessage = Buffer.from(message)
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
const result = await gmail.users.messages.send({
userId: "me",
requestBody: {
raw: encodedMessage,
},
});
console.log(`Message sent with ID: ${result.data.id}`);
return result.data;
} catch (error) {
console.error("Error sending message:", error);
throw error;
}
}
```
### Send Email with Attachment
```typescript
import fs from "fs/promises";
async function sendMessageWithAttachment(
gmail: any,
to: string,
subject: string,
body: string,
filePath: string
) {
try {
const fileContent = await fs.readFile(filePath);
const fileName = filePath.split("/").pop() || "attachment";
const mimeType = "application/octet-stream";
const boundary = "boundary123";
const message =
`From: sender@example.com\r\n` +
`To: ${to}\r\n` +
`Subject: ${subject}\r\n` +
`MIME-Version: 1.0\r\n` +
`Content-Type: multipart/mixed; boundary="${boundary}"\r\n` +
`\r\n` +
`--${boundary}\r\n` +
`Content-Type: text/plain; charset="UTF-8"\r\n` +
`\r\n` +
`${body}\r\n` +
`--${boundary}\r\n` +
`Content-Type: ${mimeType}; name="${fileName}"\r\n` +
`Content-Disposition: attachment; filename="${fileName}"\r\n` +
`Content-Transfer-Encoding: base64\r\n` +
`\r\n` +
`${fileContent.toString("base64")}\r\n` +
`--${boundary}--`;
const encodedMessage = Buffer.from(message)
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
const result = await gmail.users.messages.send({
userId: "me",
requestBody: {
raw: encodedMessage,
},
});
console.log(`Message with attachment sent with ID: ${result.data.id}`);
return result.data;
} catch (error) {
console.error("Error sending message with attachment:", error);
throw error;
}
}
```
### Create Draft
```typescript
async function createDraft(
gmail: any,
to: string,
subject: string,
body: string
) {
try {
const message =
`From: sender@example.com\r\n` +
`To: ${to}\r\n` +
`Subject: ${subject}\r\n` +
`\r\n` +
`${body}`;
const encodedMessage = Buffer.from(message)
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
const result = await gmail.users.drafts.create({
userId: "me",
requestBody: {
message: {
raw: encodedMessage,
},
},
});
console.log(`Draft created with ID: ${result.data.id}`);
return result.data;
} catch (error) {
console.error("Error creating draft:", error);
throw error;
}
}
```
### Apply Label to Message
```typescript
async function applyLabelToMessage(
gmail: any,
messageId: string,
labelName: string
) {
try {
// Get label ID first
const labelsResult = await gmail.users.labels.list({
userId: "me",
});
const label = labelsResult.data.labels?.find(
(l: any) => l.name === labelName
);
if (!label) {
throw new Error(`Label "${labelName}" not found`);
}
const result = await gmail.users.messages.modify({
userId: "me",
id: messageId,
requestBody: {
addLabelIds: [label.id],
},
});
console.log(`Label "${labelName}" applied to message ${messageId}`);
return result.data;
} catch (error) {
console.error("Error applying label:", error);
throw error;
}
}
```
### List Threads
```typescript
async function listThreads(gmail: any, maxResults = 10) {
try {
const result = await gmail.users.threads.list({
userId: "me",
q: "in:inbox",
maxResults,
});
const threads = result.data.threads || [];
for (const thread of threads) {
const threadData = await gmail.users.threads.get({
userId: "me",
id: thread.id,
});
const numMessages = threadData.data.messages?.length || 0;
console.log(`Thread ${thread.id}: ${numMessages} messages`);
}
return threads;
} catch (error) {
console.error("Error listing threads:", error);
throw error;
}
}
```
### Get Thread Messages in Order
```typescript
async function getThreadMessages(gmail: any, threadId: string) {
try {
const thread = await gmail.users.threads.get({
userId: "me",
id: threadId,
format: "full",
});
const messages = thread.data.messages || [];
messages.forEach((msg: any) => {
const headers = msg.payload.headers;
const subject =
headers.find((h: any) => h.name === "Subject")?.value || "";
const from =
headers.find((h: any) => h.name === "From")?.value || "";
console.log(`From: ${from}`);
console.log(`Subject: ${subject}`);
console.log("---");
});
return messages;
} catch (error) {
console.error("Error getting thread messages:", error);
throw error;
}
}
```
### Search Messages (Gmail Query Syntax)
```typescript
async function searchMessages(gmail: any, query: string, maxResults = 10) {
try {
// 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
const result = await gmail.users.messages.list({
userId: "me",
q: query,
maxResults,
});
const messages = result.data.messages || [];
console.log(`Found ${messages.length} messages matching: ${query}`);
return messages;
} catch (error) {
console.error("Error searching messages:", error);
throw error;
}
}
// Usage examples:
// searchMessages(gmail, 'is:unread from:boss@example.com after:2024/01/01')
// searchMessages(gmail, 'has:attachment subject:"invoice"')
```
### Create Custom Label
```typescript
async function createLabel(gmail: any, labelName: string) {
try {
const result = await gmail.users.labels.create({
userId: "me",
requestBody: {
name: labelName,
labelListVisibility: "labelShow",
messageListVisibility: "show",
},
});
console.log(`Label created: ${result.data.id}`);
return result.data;
} catch (error) {
console.error("Error creating label:", error);
throw error;
}
}
```
### Modify Thread Labels
```typescript
async function modifyThreadLabels(
gmail: any,
threadId: string,
labelIdsToAdd: string[],
labelIdsToRemove: string[] = []
) {
try {
const result = await gmail.users.threads.modify({
userId: "me",
id: threadId,
requestBody: {
addLabelIds: labelIdsToAdd,
removeLabelIds: labelIdsToRemove,
},
});
console.log(`Thread ${threadId} labels modified`);
return result.data;
} catch (error) {
console.error("Error modifying thread labels:", error);
throw error;
}
}
```
### Move Message to Trash
```typescript
async function moveToTrash(gmail: any, messageId: string) {
try {
const result = await gmail.users.messages.trash({
userId: "me",
id: messageId,
});
console.log(`Message ${messageId} moved to trash`);
return result.data;
} catch (error) {
console.error("Error moving message to trash:", error);
throw error;
}
}
```
### Delete Message Permanently
```typescript
async function deleteMessage(gmail: any, messageId: string) {
try {
await gmail.users.messages.delete({
userId: "me",
id: messageId,
});
console.log(`Message ${messageId} deleted permanently`);
} catch (error) {
console.error("Error deleting message:", error);
throw error;
}
}
```
### Batch Modify Messages
```typescript
async function batchModifyMessages(
gmail: any,
messageIds: string[],
labelIdsToAdd: string[],
labelIdsToRemove: string[] = []
) {
try {
const result = await gmail.users.messages.batchModify({
userId: "me",
requestBody: {
ids: messageIds,
addLabelIds: labelIdsToAdd,
removeLabelIds: labelIdsToRemove,
},
});
console.log(`${messageIds.length} messages modified`);
return result.data;
} catch (error) {
console.error("Error batch modifying messages:", error);
throw error;
}
}
```
### Get User Profile
```typescript
async function getUserProfile(gmail: any) {
try {
const result = await gmail.users.getProfile({
userId: "me",
});
console.log(`Email: ${result.data.emailAddress}`);
console.log(`Messages Total: ${result.data.messagesTotal}`);
console.log(`Threads Total: ${result.data.threadsTotal}`);
return result.data;
} catch (error) {
console.error("Error getting user profile:", error);
throw error;
}
}
```
### Send Email with Multiple Attachments
```typescript
import fs from "fs/promises";
async function sendMessageWithMultipleAttachments(
gmail: any,
to: string,
subject: string,
body: string,
filePaths: string[]
) {
try {
// Generate unique boundary
const boundary = `boundary_${Date.now()}_${Math.random().toString(36).substring(7)}`;
// Start with message headers
let message =
`From: me\r\n` +
`To: ${to}\r\n` +
`Subject: ${subject}\r\n` +
`MIME-Version: 1.0\r\n` +
`Content-Type: multipart/mixed; boundary="${boundary}"\r\n` +
`\r\n`;
// Add body
message +=
`--${boundary}\r\n` +
`Content-Type: text/plain; charset="UTF-8"\r\n` +
`Content-Transfer-Encoding: 7bit\r\n` +
`\r\n` +
`${body}\r\n`;
// Add each attachment
for (const filePath of filePaths) {
try {
const fileContent = await fs.readFile(filePath);
const fileName = filePath.split("/").pop() || "attachment";
// Detect MIME type
const mimeType = getMimeType(fileName);
message +=
`--${boundary}\r\n` +
`Content-Type: ${mimeType}; name="${fileName}"\r\n` +
`Content-Disposition: attachment; filename="${fileName}"\r\n` +
`Content-Transfer-Encoding: base64\r\n` +
`\r\n` +
`${fileContent.toString("base64")}\r\n`;
} catch (error) {
console.warn(`Warning: Could not attach file ${filePath}`);
}
}
// Close boundary
message += `--${boundary}--`;
// Encode and send
const encodedMessage = Buffer.from(message)
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
const result = await gmail.users.messages.send({
userId: "me",
requestBody: {
raw: encodedMessage,
},
});
console.log(`Message with ${filePaths.length} attachments sent: ${result.data.id}`);
return result.data;
} catch (error) {
console.error("Error sending message with multiple attachments:", error);
throw error;
}
}
// Helper function to detect MIME types
function getMimeType(fileName: string): string {
const mimeTypes: { [key: string]: string } = {
".pdf": "application/pdf",
".doc": "application/msword",
".docx":
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
".xls": "application/vnd.ms-excel",
".xlsx":
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
".ppt": "application/vnd.ms-powerpoint",
".pptx":
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
".txt": "text/plain",
".csv": "text/csv",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".png": "image/png",
".gif": "image/gif",
".zip": "application/zip",
};
const ext = fileName.substring(fileName.lastIndexOf(".")).toLowerCase();
return mimeTypes[ext] || "application/octet-stream";
}
// Usage
// await sendMessageWithMultipleAttachments(
// gmail,
// "user@example.com",
// "Multiple Files",
// "Please find the attached documents.",
// ["/path/to/file1.pdf", "/path/to/file2.xlsx", "/path/to/file3.doc"]
// );
```
### Extract Attachments from Received Email
```typescript
import fs from "fs/promises";
import path from "path";
interface AttachmentInfo {
filename: string;
mimeType: string;
size: number;
attachmentId: string;
messageId: string;
}
async function extractAttachments(
gmail: any,
messageId: string,
outputDir: string = "./attachments"
): Promise<AttachmentInfo[]> {
try {
// Create output directory if it doesn't exist
await fs.mkdir(outputDir, { recursive: true });
// Get message with full payload
const message = await gmail.users.messages.get({
userId: "me",
id: messageId,
format: "full",
});
const attachments: AttachmentInfo[] = [];
const payload = message.data.payload;
// Check if message has parts (multipart)
if (!payload.parts) {
console.log("Message has no attachments");
return attachments;
}
// Process each part
for (const part of payload.parts) {
if (part.filename && part.filename.length > 0) {
const attachment = await gmail.users.messages.attachments.get({
userId: "me",
messageId: messageId,
id: part.body.attachmentId,
});
// Decode base64url data
const attachmentData = Buffer.from(attachment.data.data || "", "base64");
// Save file
const filePath = path.join(outputDir, part.filename);
await fs.writeFile(filePath, attachmentData);
console.log(`✅ Saved: ${part.filename} (${attachmentData.length} bytes)`);
attachments.push({
filename: part.filename,
mimeType: part.mimeType || "application/octet-stream",
size: attachmentData.length,
attachmentId: part.body.attachmentId,
messageId: messageId,
});
}
}
console.log(`Extracted ${attachments.length} attachment(s)`);
return attachments;
} catch (error) {
console.error("Error extracting attachments:", error);
throw error;
}
}
// Usage
// const attachments = await extractAttachments(gmail, messageId, "./downloads");
```
### Parse and Decode Full Email Body
```typescript
async function parseFullEmail(gmail: any, messageId: string) {
try {
const message = await gmail.users.messages.get({
userId: "me",
id: messageId,
format: "full",
});
const payload = message.data.payload;
const headers = payload.headers || [];
// Extract headers
const getHeader = (name: string): string =>
headers.find((h) => h.name === name)?.value || "";
const result = {
id: message.data.id,
threadId: message.data.threadId,
labelIds: message.data.labelIds || [],
snippet: message.data.snippet || "",
internalDate: new Date(parseInt(message.data.internalDate || "0")),
headers: {
subject: getHeader("Subject"),
from: getHeader("From"),
to: getHeader("To"),
cc: getHeader("Cc"),
bcc: getHeader("Bcc"),
date: getHeader("Date"),
},
body: "",
html: "",
attachments: [] as Array<{
filename: string;
mimeType: string;
attachmentId: string;
}>,
};
// Extract body - handle multipart messages
if (payload.mimeType?.startsWith("multipart")) {
// Multipart message - has parts
for (const part of payload.parts || []) {
if (part.mimeType === "text/plain" && !result.body) {
const bodyData = part.body?.data || "";
result.body = decodeBase64Url(bodyData);
} else if (part.mimeType === "text/html" && !result.html) {
const bodyData = part.body?.data || "";
result.html = decodeBase64Url(bodyData);
} else if (part.filename && part.body?.attachmentId) {
result.attachments.push({
filename: part.filename,
mimeType: part.mimeType || "application/octet-stream",
attachmentId: part.body.attachmentId,
});
}
}
} else {
// Simple message - single part
const bodyData = payload.body?.data || "";
if (payload.mimeType === "text/html") {
result.html = decodeBase64Url(bodyData);
} else {
result.body = decodeBase64Url(bodyData);
}
}
return result;
} catch (error) {
console.error("Error parsing email:", error);
throw error;
}
}
// Helper function
function decodeBase64Url(data: string): string {
if (!data) return "";
// Convert base64url to base64 and decode
const base64 = data.replace(/-/g, "+").replace(/_/g, "/");
return Buffer.from(base64, "base64").toString("utf-8");
}
// Usage
// const email = await parseFullEmail(gmail, messageId);
// console.log(`Subject: ${email.headers.subject}`);
// console.log(`From: ${email.headers.from}`);
// console.log(`Body: ${email.body}`);
// console.log(`Has ${email.attachments.length} attachments`);
```
## 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
```typescript
async function safeGmailOperation(
operation: () => Promise<any>
): Promise<any> {
try {
return await operation();
} catch (error: any) {
if (error.response) {
const status = error.response.status;
const statusText = error.response.statusText;
const errorDetails = error.response.data.error;
console.error(`Error ${status} ${statusText}:`);
console.error(errorDetails.message);
// Handle specific errors
switch (status) {
case 400:
console.error("Bad request - check parameters");
break;
case 403:
console.error("Forbidden - check scopes or permissions");
break;
case 404:
console.error("Not found - resource doesn't exist");
break;
case 429:
console.error("Rate limited - wait before retrying");
break;
}
} else {
console.error("Network or other error:", error.message);
}
throw error;
}
}
// Usage
try {
await safeGmailOperation(() =>
gmail.users.messages.get({
userId: "me",
id: "INVALID_ID"
})
);
} catch (error) {
// Error is already logged
}
```
---
## Complete Example: Gmail Helper Class
```typescript
import { google } from "googleapis";
import { OAuth2Client } from "google-auth-library";
class GmailHelper {
private gmail: any;
private auth: OAuth2Client;
constructor(auth: OAuth2Client) {
this.auth = auth;
this.gmail = google.gmail({ version: "v1", auth });
}
async getProfile() {
return (await this.gmail.users.getProfile({ userId: "me" })).data;
}
async listLabels() {
return (await this.gmail.users.labels.list({ userId: "me" })).data.labels;
}
async listInboxMessages(maxResults = 10) {
return (
await this.gmail.users.messages.list({
userId: "me",
q: "in:inbox",
maxResults,
})
).data.messages;
}
async getMessage(messageId: string) {
return (
await this.gmail.users.messages.get({
userId: "me",
id: messageId,
format: "full",
})
).data;
}
async searchMessages(query: string, maxResults = 10) {
return (
await this.gmail.users.messages.list({
userId: "me",
q: query,
maxResults,
})
).data.messages;
}
async sendMessage(to: string, subject: string, body: string) {
const message =
`From: me\r\n` +
`To: ${to}\r\n` +
`Subject: ${subject}\r\n` +
`\r\n` +
`${body}`;
const encodedMessage = Buffer.from(message)
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
return (
await this.gmail.users.messages.send({
userId: "me",
requestBody: { raw: encodedMessage },
})
).data;
}
async createDraft(to: string, subject: string, body: string) {
const message =
`From: me\r\n` +
`To: ${to}\r\n` +
`Subject: ${subject}\r\n` +
`\r\n` +
`${body}`;
const encodedMessage = Buffer.from(message)
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
return (
await this.gmail.users.drafts.create({
userId: "me",
requestBody: { message: { raw: encodedMessage } },
})
).data;
}
async applyLabel(messageId: string, labelName: string) {
const labels = await this.listLabels();
const label = labels?.find((l: any) => l.name === labelName);
if (!label) throw new Error(`Label "${labelName}" not found`);
return (
await this.gmail.users.messages.modify({
userId: "me",
id: messageId,
requestBody: { addLabelIds: [label.id] },
})
).data;
}
async deleteMessage(messageId: string) {
return await this.gmail.users.messages.delete({
userId: "me",
id: messageId,
});
}
async trashMessage(messageId: string) {
return (
await this.gmail.users.messages.trash({
userId: "me",
id: messageId,
})
).data;
}
async parseFullEmail(messageId: string) {
const message = await this.gmail.users.messages.get({
userId: "me",
id: messageId,
format: "full",
});
const payload = message.data.payload;
const headers = payload.headers || [];
const getHeader = (name: string): string =>
headers.find((h: any) => h.name === name)?.value || "";
const result: any = {
id: message.data.id,
threadId: message.data.threadId,
labelIds: message.data.labelIds || [],
snippet: message.data.snippet || "",
headers: {
subject: getHeader("Subject"),
from: getHeader("From"),
to: getHeader("To"),
cc: getHeader("Cc"),
date: getHeader("Date"),
},
body: "",
html: "",
attachments: [] as any[],
};
if (payload.mimeType?.startsWith("multipart")) {
for (const part of payload.parts || []) {
if (part.mimeType === "text/plain" && !result.body) {
result.body = this.decodeBase64Url(part.body?.data || "");
} else if (part.mimeType === "text/html" && !result.html) {
result.html = this.decodeBase64Url(part.body?.data || "");
} else if (part.filename && part.body?.attachmentId) {
result.attachments.push({
filename: part.filename,
mimeType: part.mimeType || "application/octet-stream",
attachmentId: part.body.attachmentId,
});
}
}
} else {
const bodyData = payload.body?.data || "";
if (payload.mimeType === "text/html") {
result.html = this.decodeBase64Url(bodyData);
} else {
result.body = this.decodeBase64Url(bodyData);
}
}
return result;
}
private decodeBase64Url(data: string): string {
if (!data) return "";
const base64 = data.replace(/-/g, "+").replace(/_/g, "/");
return Buffer.from(base64, "base64").toString("utf-8");
}
}
export default GmailHelper;
```
---
## Source
- Main Documentation: https://developers.google.com/workspace/gmail/api/guides
- API Reference: https://developers.google.com/workspace/gmail/api/reference/rest
- Node.js Quickstart: https://developers.google.com/workspace/gmail/api/quickstart/nodejs
- Node.js Client Docs: https://github.com/googleapis/google-api-nodejs-client
- Authentication: https://developers.google.com/identity/protocols/oauth2
- Service: gmail.googleapis.com (v1)