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