Skip to main content
Glama
gmail-service.ts7.51 kB
import { google } from 'googleapis'; import { OAuth2Client } from 'google-auth-library'; export interface EmailInfo { id: string; threadId?: string | null; subject: string; from: string; to: string; date: string; snippet?: string; } export interface EmailDetails extends EmailInfo { body: string; } export interface Label { id?: string | null; name?: string | null; type?: string | null; } export class GmailService { private gmail; constructor(auth: OAuth2Client) { this.gmail = google.gmail({ version: 'v1', auth }); } async searchEmails(query: string, maxResults = 10): Promise<EmailInfo[]> { const { data } = await this.gmail.users.messages.list({ userId: 'me', q: query, maxResults }); if (!data.messages?.length) return []; return Promise.all(data.messages.map(async (msg) => { const { data: detail } = await this.gmail.users.messages.get({ userId: 'me', id: msg.id!, format: 'metadata', metadataHeaders: ['Subject', 'From', 'To', 'Date'] }); const h = detail.payload?.headers || []; const findHeader = (name: string) => h.find(x => x.name === name)?.value || ''; return { id: msg.id!, threadId: detail.threadId, subject: findHeader('Subject'), from: findHeader('From'), to: findHeader('To'), date: findHeader('Date'), snippet: detail.snippet || '' }; })); } async readEmail(messageId: string): Promise<EmailDetails> { const { data } = await this.gmail.users.messages.get({ userId: 'me', id: messageId, format: 'full' }); const h = data.payload?.headers || []; const findHeader = (name: string) => h.find(x => x.name?.toLowerCase() === name.toLowerCase())?.value || ''; return { id: messageId, threadId: data.threadId || '', subject: findHeader('subject'), from: findHeader('from'), to: findHeader('to'), date: findHeader('date'), body: this.extractBody(data.payload) }; } async deleteEmail(id: string): Promise<void> { await this.gmail.users.messages.delete({ userId: 'me', id }); } async batchDeleteEmails(ids: string[]): Promise<{ successes: number; failures: number }> { return this.batchOperation(ids, (id) => this.deleteEmail(id)); } async listLabels(): Promise<Label[]> { const { data } = await this.gmail.users.labels.list({ userId: 'me' }); return (data.labels || []) as Label[]; } async createLabel(name: string): Promise<Label> { const { data } = await this.gmail.users.labels.create({ userId: 'me', requestBody: { name, messageListVisibility: 'show', labelListVisibility: 'labelShow' } }); return data as Label; } async deleteLabel(id: string): Promise<void> { await this.gmail.users.labels.delete({ userId: 'me', id }); } async applyLabel(messageId: string, labelId: string): Promise<void> { await this.modifyMessage(messageId, { addLabelIds: [labelId] }); } async removeLabel(messageId: string, labelId: string): Promise<void> { await this.modifyMessage(messageId, { removeLabelIds: [labelId] }); } async batchApplyLabels(messageIds: string[], labelIds: string[]): Promise<{ successes: number; failures: number }> { return this.batchOperation(messageIds, (id) => this.modifyMessage(id, { addLabelIds: labelIds })); } private async modifyMessage(id: string, requestBody: any): Promise<void> { await this.gmail.users.messages.modify({ userId: 'me', id, requestBody }); } private async batchOperation<T>(items: T[], operation: (item: T) => Promise<any>): Promise<{ successes: number; failures: number }> { let successes = 0, failures = 0; const batchSize = 50; for (let i = 0; i < items.length; i += batchSize) { const results = await Promise.allSettled(items.slice(i, i + batchSize).map(operation)); results.forEach(r => r.status === 'fulfilled' ? successes++ : failures++); } return { successes, failures }; } getEmailUrl(messageId: string): string { return `https://mail.google.com/mail/u/0/#inbox/${messageId}`; } private extractBody(payload: any): string { if (!payload) return ''; let text = '', html = ''; if (payload.body?.data) { const content = Buffer.from(payload.body.data, 'base64').toString('utf8'); if (payload.mimeType === 'text/plain') text = content; else if (payload.mimeType === 'text/html') html = content; } if (payload.parts) { for (const part of payload.parts) { const extracted = this.extractBody(part); if (extracted) text = text ? `${text}\n${extracted}` : extracted; } } return text || html || ''; } async createReply(messageId: string, replyMessage: string): Promise<{message: string, to: string, subject: string, replyMessage: string}> { const email = await this.readEmail(messageId); // Create email message for draft const subject = email.subject.startsWith('Re: ') ? email.subject : `Re: ${email.subject}`; const to = email.from; const inReplyTo = email.id; const references = email.threadId; const emailContent = [ `To: ${to}`, `Subject: ${subject}`, `In-Reply-To: ${inReplyTo}`, `References: ${references}`, '', replyMessage ].join('\n'); const encodedMessage = Buffer.from(emailContent).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); try { const { data: draft } = await this.gmail.users.drafts.create({ userId: 'me', requestBody: { message: { threadId: email.threadId, raw: encodedMessage } } }); const draftUrl = `https://mail.google.com/mail/u/0/#drafts/${draft.id}`; return { message: `Reply draft created and saved to Gmail drafts.\n\n**Gmail draft URL:** ${draftUrl}`, to, subject, replyMessage }; } catch (error) { console.error('Failed to create draft:', error); // Fallback to compose URL const gmailComposeUrl = `https://mail.google.com/mail/?view=cm&fs=1&to=${encodeURIComponent(to)}&su=${encodeURIComponent(subject)}&body=${encodeURIComponent(replyMessage)}`; return { message: `Failed to create draft. Gmail compose URL: ${gmailComposeUrl}`, to, subject, replyMessage }; } } }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/muammar-yacoob/GMail-Manager-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server