35 KiB
name, description
| name | description |
|---|---|
| Google Mail API (Node.js/TypeScript) | 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
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:
- Create a Google Cloud project
- Enable the Gmail API
- Configure OAuth 2.0 credentials (Desktop/Web application)
- 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)
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
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
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
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
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
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
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
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
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
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)
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
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
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
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
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
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
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
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
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
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:draftis:unread,is:read,is:starred,is:importantfrom:email@example.com,to:email@example.com,cc:email@example.comsubject:text- Search in subject linehas:attachment- Only messages with attachmentsfilename:pdf- Attachment filenamebefore:YYYY/MM/DD,after:YYYY/MM/DDlarger: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
{
"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
{
"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
{
"id": "...",
"historyId": "...",
"messages": [
{ /* Message objects */ }
]
}
Error Handling
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
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)