--- 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 { // 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 { 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 { 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 ): Promise { 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)