gmail-workspace-mcp-server 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -262,17 +262,32 @@ Create a draft email, optionally as a reply to an existing conversation.
262
262
 
263
263
  - `to` (string, required): Recipient email address
264
264
  - `subject` (string, required): Email subject
265
- - `body` (string, required): Email body (plain text)
265
+ - `plaintext_body` (string): Plain text body content (at least one of plaintext_body or html_body required)
266
+ - `html_body` (string): HTML body content for rich text formatting (at least one of plaintext_body or html_body required)
267
+ - `cc` (string, optional): CC recipients
268
+ - `bcc` (string, optional): BCC recipients
266
269
  - `thread_id` (string, optional): Thread ID for replies
267
270
  - `reply_to_email_id` (string, optional): Email ID to reply to (sets References/In-Reply-To headers)
268
271
 
269
- **Example:**
272
+ At least one of `plaintext_body` or `html_body` must be provided. If both are provided, a multipart email is sent with both versions.
273
+
274
+ **Example (plain text):**
275
+
276
+ ```json
277
+ {
278
+ "to": "recipient@example.com",
279
+ "subject": "Meeting Follow-up",
280
+ "plaintext_body": "Thanks for the meeting today!"
281
+ }
282
+ ```
283
+
284
+ **Example (HTML):**
270
285
 
271
286
  ```json
272
287
  {
273
288
  "to": "recipient@example.com",
274
289
  "subject": "Meeting Follow-up",
275
- "body": "Thanks for the meeting today!"
290
+ "html_body": "<p>Thanks for the meeting today! Check out <a href=\"https://example.com/notes\">the notes</a>.</p>"
276
291
  }
277
292
  ```
278
293
 
@@ -284,18 +299,31 @@ Send an email directly or from an existing draft.
284
299
 
285
300
  - `to` (string, conditional): Recipient email (required unless sending from draft)
286
301
  - `subject` (string, conditional): Email subject (required unless sending from draft)
287
- - `body` (string, conditional): Email body (required unless sending from draft)
302
+ - `plaintext_body` (string): Plain text body content (at least one of plaintext_body or html_body required, unless sending a draft)
303
+ - `html_body` (string): HTML body content for rich text formatting (at least one of plaintext_body or html_body required, unless sending a draft)
304
+ - `cc` (string, optional): CC recipients
305
+ - `bcc` (string, optional): BCC recipients
288
306
  - `from_draft_id` (string, optional): Send an existing draft by ID
289
307
  - `thread_id` (string, optional): Thread ID for replies
290
308
  - `reply_to_email_id` (string, optional): Email ID to reply to
291
309
 
292
- **Example (new email):**
310
+ **Example (plain text email):**
311
+
312
+ ```json
313
+ {
314
+ "to": "recipient@example.com",
315
+ "subject": "Hello",
316
+ "plaintext_body": "This is a test email."
317
+ }
318
+ ```
319
+
320
+ **Example (HTML email):**
293
321
 
294
322
  ```json
295
323
  {
296
324
  "to": "recipient@example.com",
297
325
  "subject": "Hello",
298
- "body": "This is a test email."
326
+ "html_body": "<p>Check out <a href=\"https://example.com\">our website</a> for more details.</p>"
299
327
  }
300
328
  ```
301
329
 
@@ -165,17 +165,18 @@ function createMockClient() {
165
165
  return { ...email, labelIds: labels };
166
166
  },
167
167
  async createDraft(options) {
168
+ const bodyContent = options.plaintextBody || options.htmlBody || '';
168
169
  const draft = {
169
170
  id: `draft_${draftIdCounter++}`,
170
171
  message: {
171
172
  id: `msg_${messageIdCounter++}`,
172
173
  threadId: options.threadId || `thread_${messageIdCounter}`,
173
174
  labelIds: ['DRAFT'],
174
- snippet: options.body.substring(0, 100),
175
+ snippet: bodyContent.substring(0, 100),
175
176
  historyId: '12347',
176
177
  internalDate: String(Date.now()),
177
178
  payload: {
178
- mimeType: 'text/plain',
179
+ mimeType: options.htmlBody ? 'text/html' : 'text/plain',
179
180
  headers: [
180
181
  { name: 'Subject', value: options.subject },
181
182
  { name: 'From', value: 'me@example.com' },
@@ -183,8 +184,8 @@ function createMockClient() {
183
184
  { name: 'Date', value: new Date().toISOString() },
184
185
  ],
185
186
  body: {
186
- size: options.body.length,
187
- data: Buffer.from(options.body).toString('base64url'),
187
+ size: bodyContent.length,
188
+ data: Buffer.from(bodyContent).toString('base64url'),
188
189
  },
189
190
  },
190
191
  },
@@ -221,15 +222,16 @@ function createMockClient() {
221
222
  mockDrafts.splice(index, 1);
222
223
  },
223
224
  async sendMessage(options) {
225
+ const bodyContent = options.plaintextBody || options.htmlBody || '';
224
226
  const sentMessage = {
225
227
  id: `msg_${messageIdCounter++}`,
226
228
  threadId: options.threadId || `thread_${messageIdCounter}`,
227
229
  labelIds: ['SENT'],
228
- snippet: options.body.substring(0, 100),
230
+ snippet: bodyContent.substring(0, 100),
229
231
  historyId: '12348',
230
232
  internalDate: String(Date.now()),
231
233
  payload: {
232
- mimeType: 'text/plain',
234
+ mimeType: options.htmlBody ? 'text/html' : 'text/plain',
233
235
  headers: [
234
236
  { name: 'Subject', value: options.subject },
235
237
  { name: 'From', value: 'me@example.com' },
@@ -237,8 +239,8 @@ function createMockClient() {
237
239
  { name: 'Date', value: new Date().toISOString() },
238
240
  ],
239
241
  body: {
240
- size: options.body.length,
241
- data: Buffer.from(options.body).toString('base64url'),
242
+ size: bodyContent.length,
243
+ data: Buffer.from(bodyContent).toString('base64url'),
242
244
  },
243
245
  },
244
246
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gmail-workspace-mcp-server",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "MCP server for Gmail integration with OAuth2 and service account support",
5
5
  "main": "build/index.js",
6
6
  "type": "module",
@@ -13,7 +13,8 @@ interface DraftListItem {
13
13
  export declare function createDraft(baseUrl: string, headers: Record<string, string>, from: string, options: {
14
14
  to: string;
15
15
  subject: string;
16
- body: string;
16
+ plaintextBody?: string;
17
+ htmlBody?: string;
17
18
  cc?: string;
18
19
  bcc?: string;
19
20
  threadId?: string;
@@ -4,14 +4,17 @@
4
4
  export interface MimeMessageOptions {
5
5
  to: string;
6
6
  subject: string;
7
- body: string;
7
+ plaintextBody?: string;
8
+ htmlBody?: string;
8
9
  cc?: string;
9
10
  bcc?: string;
10
11
  inReplyTo?: string;
11
12
  references?: string;
12
13
  }
13
14
  /**
14
- * Builds a MIME message from email options
15
+ * Builds a MIME message from email options.
16
+ * If both plaintextBody and htmlBody are provided, creates a multipart/alternative message.
17
+ * If only one is provided, creates a single-part message with the appropriate content type.
15
18
  */
16
19
  export declare function buildMimeMessage(from: string, options: MimeMessageOptions): string;
17
20
  /**
@@ -2,7 +2,9 @@
2
2
  * MIME message utilities for building and encoding email messages
3
3
  */
4
4
  /**
5
- * Builds a MIME message from email options
5
+ * Builds a MIME message from email options.
6
+ * If both plaintextBody and htmlBody are provided, creates a multipart/alternative message.
7
+ * If only one is provided, creates a single-part message with the appropriate content type.
6
8
  */
7
9
  export function buildMimeMessage(from, options) {
8
10
  const headers = [
@@ -10,7 +12,6 @@ export function buildMimeMessage(from, options) {
10
12
  `To: ${options.to}`,
11
13
  `Subject: ${options.subject}`,
12
14
  'MIME-Version: 1.0',
13
- 'Content-Type: text/plain; charset=utf-8',
14
15
  ];
15
16
  if (options.cc) {
16
17
  headers.push(`Cc: ${options.cc}`);
@@ -24,7 +25,24 @@ export function buildMimeMessage(from, options) {
24
25
  if (options.references) {
25
26
  headers.push(`References: ${options.references}`);
26
27
  }
27
- return headers.join('\r\n') + '\r\n\r\n' + options.body;
28
+ // If both plain text and HTML are provided, use multipart/alternative
29
+ if (options.plaintextBody && options.htmlBody) {
30
+ const boundary = `boundary_${Date.now()}_${Math.random().toString(36).substring(2)}`;
31
+ headers.push(`Content-Type: multipart/alternative; boundary="${boundary}"`);
32
+ const parts = [
33
+ `--${boundary}\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n${options.plaintextBody}`,
34
+ `--${boundary}\r\nContent-Type: text/html; charset=utf-8\r\n\r\n${options.htmlBody}`,
35
+ `--${boundary}--`,
36
+ ];
37
+ return headers.join('\r\n') + '\r\n\r\n' + parts.join('\r\n');
38
+ }
39
+ // Single content type
40
+ if (options.htmlBody) {
41
+ headers.push('Content-Type: text/html; charset=utf-8');
42
+ return headers.join('\r\n') + '\r\n\r\n' + options.htmlBody;
43
+ }
44
+ headers.push('Content-Type: text/plain; charset=utf-8');
45
+ return headers.join('\r\n') + '\r\n\r\n' + (options.plaintextBody ?? '');
28
46
  }
29
47
  /**
30
48
  * Converts a string to base64url encoding (RFC 4648)
@@ -5,7 +5,8 @@ import type { Email } from '../../types.js';
5
5
  export declare function sendMessage(baseUrl: string, headers: Record<string, string>, from: string, options: {
6
6
  to: string;
7
7
  subject: string;
8
- body: string;
8
+ plaintextBody?: string;
9
+ htmlBody?: string;
9
10
  cc?: string;
10
11
  bcc?: string;
11
12
  threadId?: string;
@@ -50,7 +50,8 @@ export interface IGmailClient {
50
50
  createDraft(options: {
51
51
  to: string;
52
52
  subject: string;
53
- body: string;
53
+ plaintextBody?: string;
54
+ htmlBody?: string;
54
55
  cc?: string;
55
56
  bcc?: string;
56
57
  threadId?: string;
@@ -85,7 +86,8 @@ export interface IGmailClient {
85
86
  sendMessage(options: {
86
87
  to: string;
87
88
  subject: string;
88
- body: string;
89
+ plaintextBody?: string;
90
+ htmlBody?: string;
89
91
  cc?: string;
90
92
  bcc?: string;
91
93
  threadId?: string;
@@ -167,7 +169,8 @@ declare abstract class BaseGmailClient implements IGmailClient {
167
169
  createDraft(options: {
168
170
  to: string;
169
171
  subject: string;
170
- body: string;
172
+ plaintextBody?: string;
173
+ htmlBody?: string;
171
174
  cc?: string;
172
175
  bcc?: string;
173
176
  threadId?: string;
@@ -190,7 +193,8 @@ declare abstract class BaseGmailClient implements IGmailClient {
190
193
  sendMessage(options: {
191
194
  to: string;
192
195
  subject: string;
193
- body: string;
196
+ plaintextBody?: string;
197
+ htmlBody?: string;
194
198
  cc?: string;
195
199
  bcc?: string;
196
200
  threadId?: string;
@@ -1,26 +1,47 @@
1
1
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
2
  import { z } from 'zod';
3
3
  import type { ClientFactory } from '../server.js';
4
- export declare const DraftEmailSchema: z.ZodObject<{
4
+ export declare const DraftEmailSchema: z.ZodEffects<z.ZodObject<{
5
5
  to: z.ZodString;
6
6
  subject: z.ZodString;
7
- body: z.ZodString;
7
+ plaintext_body: z.ZodOptional<z.ZodString>;
8
+ html_body: z.ZodOptional<z.ZodString>;
8
9
  cc: z.ZodOptional<z.ZodString>;
9
10
  bcc: z.ZodOptional<z.ZodString>;
10
11
  thread_id: z.ZodOptional<z.ZodString>;
11
12
  reply_to_email_id: z.ZodOptional<z.ZodString>;
12
13
  }, "strip", z.ZodTypeAny, {
13
- body: string;
14
14
  to: string;
15
15
  subject: string;
16
+ plaintext_body?: string | undefined;
17
+ html_body?: string | undefined;
16
18
  cc?: string | undefined;
17
19
  bcc?: string | undefined;
18
20
  thread_id?: string | undefined;
19
21
  reply_to_email_id?: string | undefined;
20
22
  }, {
21
- body: string;
22
23
  to: string;
23
24
  subject: string;
25
+ plaintext_body?: string | undefined;
26
+ html_body?: string | undefined;
27
+ cc?: string | undefined;
28
+ bcc?: string | undefined;
29
+ thread_id?: string | undefined;
30
+ reply_to_email_id?: string | undefined;
31
+ }>, {
32
+ to: string;
33
+ subject: string;
34
+ plaintext_body?: string | undefined;
35
+ html_body?: string | undefined;
36
+ cc?: string | undefined;
37
+ bcc?: string | undefined;
38
+ thread_id?: string | undefined;
39
+ reply_to_email_id?: string | undefined;
40
+ }, {
41
+ to: string;
42
+ subject: string;
43
+ plaintext_body?: string | undefined;
44
+ html_body?: string | undefined;
24
45
  cc?: string | undefined;
25
46
  bcc?: string | undefined;
26
47
  thread_id?: string | undefined;
@@ -40,9 +61,13 @@ export declare function draftEmailTool(server: Server, clientFactory: ClientFact
40
61
  type: string;
41
62
  description: "Subject line of the email.";
42
63
  };
43
- body: {
64
+ plaintext_body: {
65
+ type: string;
66
+ description: "Plain text body content of the email. At least one of plaintext_body or html_body must be provided. If both are provided, a multipart email is sent with both versions.";
67
+ };
68
+ html_body: {
44
69
  type: string;
45
- description: "Plain text body content of the email.";
70
+ description: "HTML body content of the email for rich text formatting (links, bold, lists, etc.). At least one of plaintext_body or html_body must be provided. If both are provided, a multipart email is sent with both versions.";
46
71
  };
47
72
  cc: {
48
73
  type: string;
@@ -3,7 +3,8 @@ import { getHeader } from '../utils/email-helpers.js';
3
3
  const PARAM_DESCRIPTIONS = {
4
4
  to: 'Recipient email address(es). For multiple recipients, separate with commas.',
5
5
  subject: 'Subject line of the email.',
6
- body: 'Plain text body content of the email.',
6
+ plaintext_body: 'Plain text body content of the email. At least one of plaintext_body or html_body must be provided. If both are provided, a multipart email is sent with both versions.',
7
+ html_body: 'HTML body content of the email for rich text formatting (links, bold, lists, etc.). At least one of plaintext_body or html_body must be provided. If both are provided, a multipart email is sent with both versions.',
7
8
  cc: 'CC recipient email address(es). For multiple, separate with commas.',
8
9
  bcc: 'BCC recipient email address(es). For multiple, separate with commas.',
9
10
  thread_id: 'Thread ID to add this draft to an existing conversation. ' +
@@ -11,26 +12,37 @@ const PARAM_DESCRIPTIONS = {
11
12
  reply_to_email_id: 'Email ID to reply to. If provided, the draft will be formatted as a reply ' +
12
13
  'with proper In-Reply-To and References headers. Also requires thread_id.',
13
14
  };
14
- export const DraftEmailSchema = z.object({
15
+ export const DraftEmailSchema = z
16
+ .object({
15
17
  to: z.string().min(1).describe(PARAM_DESCRIPTIONS.to),
16
18
  subject: z.string().min(1).describe(PARAM_DESCRIPTIONS.subject),
17
- body: z.string().min(1).describe(PARAM_DESCRIPTIONS.body),
19
+ plaintext_body: z.string().min(1).optional().describe(PARAM_DESCRIPTIONS.plaintext_body),
20
+ html_body: z.string().min(1).optional().describe(PARAM_DESCRIPTIONS.html_body),
18
21
  cc: z.string().optional().describe(PARAM_DESCRIPTIONS.cc),
19
22
  bcc: z.string().optional().describe(PARAM_DESCRIPTIONS.bcc),
20
23
  thread_id: z.string().optional().describe(PARAM_DESCRIPTIONS.thread_id),
21
24
  reply_to_email_id: z.string().optional().describe(PARAM_DESCRIPTIONS.reply_to_email_id),
25
+ })
26
+ .refine((data) => {
27
+ return Boolean(data.plaintext_body) || Boolean(data.html_body);
28
+ }, {
29
+ message: 'At least one of plaintext_body or html_body must be provided.',
22
30
  });
23
31
  const TOOL_DESCRIPTION = `Create a draft email that can be reviewed and sent later.
24
32
 
25
33
  **Parameters:**
26
34
  - to: Recipient email address(es) (required)
27
35
  - subject: Email subject line (required)
28
- - body: Plain text body content (required)
36
+ - plaintext_body: Plain text body content (at least one of plaintext_body or html_body required)
37
+ - html_body: HTML body content for rich text formatting (at least one of plaintext_body or html_body required)
29
38
  - cc: CC recipients (optional)
30
39
  - bcc: BCC recipients (optional)
31
40
  - thread_id: Thread ID to reply to an existing conversation (optional)
32
41
  - reply_to_email_id: Email ID to reply to, sets proper reply headers (optional)
33
42
 
43
+ **Body content:**
44
+ At least one of plaintext_body or html_body must be provided. If both are provided, a multipart email is sent with both plain text and HTML versions. Use html_body for rich formatting like hyperlinks, bold text, or lists.
45
+
34
46
  **Creating a reply:**
35
47
  To create a draft reply to an existing email:
36
48
  1. Get the thread_id and email_id from get_email_conversation
@@ -57,9 +69,13 @@ export function draftEmailTool(server, clientFactory) {
57
69
  type: 'string',
58
70
  description: PARAM_DESCRIPTIONS.subject,
59
71
  },
60
- body: {
72
+ plaintext_body: {
73
+ type: 'string',
74
+ description: PARAM_DESCRIPTIONS.plaintext_body,
75
+ },
76
+ html_body: {
61
77
  type: 'string',
62
- description: PARAM_DESCRIPTIONS.body,
78
+ description: PARAM_DESCRIPTIONS.html_body,
63
79
  },
64
80
  cc: {
65
81
  type: 'string',
@@ -78,7 +94,7 @@ export function draftEmailTool(server, clientFactory) {
78
94
  description: PARAM_DESCRIPTIONS.reply_to_email_id,
79
95
  },
80
96
  },
81
- required: ['to', 'subject', 'body'],
97
+ required: ['to', 'subject'],
82
98
  },
83
99
  handler: async (args) => {
84
100
  try {
@@ -103,7 +119,8 @@ export function draftEmailTool(server, clientFactory) {
103
119
  const draft = await client.createDraft({
104
120
  to: parsed.to,
105
121
  subject: parsed.subject,
106
- body: parsed.body,
122
+ plaintextBody: parsed.plaintext_body,
123
+ htmlBody: parsed.html_body,
107
124
  cc: parsed.cc,
108
125
  bcc: parsed.bcc,
109
126
  threadId: parsed.thread_id,
@@ -117,6 +134,12 @@ export function draftEmailTool(server, clientFactory) {
117
134
  }
118
135
  responseText += `\n\n**To:** ${parsed.to}`;
119
136
  responseText += `\n**Subject:** ${parsed.subject}`;
137
+ const format = parsed.plaintext_body && parsed.html_body
138
+ ? 'Multipart (plain text + HTML)'
139
+ : parsed.html_body
140
+ ? 'HTML'
141
+ : 'Plain text';
142
+ responseText += `\n**Format:** ${format}`;
120
143
  if (parsed.cc) {
121
144
  responseText += `\n**CC:** ${parsed.cc}`;
122
145
  }
@@ -4,43 +4,48 @@ import type { ClientFactory } from '../server.js';
4
4
  export declare const SendEmailSchema: z.ZodEffects<z.ZodObject<{
5
5
  to: z.ZodOptional<z.ZodString>;
6
6
  subject: z.ZodOptional<z.ZodString>;
7
- body: z.ZodOptional<z.ZodString>;
7
+ plaintext_body: z.ZodOptional<z.ZodString>;
8
+ html_body: z.ZodOptional<z.ZodString>;
8
9
  cc: z.ZodOptional<z.ZodString>;
9
10
  bcc: z.ZodOptional<z.ZodString>;
10
11
  thread_id: z.ZodOptional<z.ZodString>;
11
12
  reply_to_email_id: z.ZodOptional<z.ZodString>;
12
13
  from_draft_id: z.ZodOptional<z.ZodString>;
13
14
  }, "strip", z.ZodTypeAny, {
14
- body?: string | undefined;
15
15
  to?: string | undefined;
16
16
  subject?: string | undefined;
17
+ plaintext_body?: string | undefined;
18
+ html_body?: string | undefined;
17
19
  cc?: string | undefined;
18
20
  bcc?: string | undefined;
19
21
  thread_id?: string | undefined;
20
22
  reply_to_email_id?: string | undefined;
21
23
  from_draft_id?: string | undefined;
22
24
  }, {
23
- body?: string | undefined;
24
25
  to?: string | undefined;
25
26
  subject?: string | undefined;
27
+ plaintext_body?: string | undefined;
28
+ html_body?: string | undefined;
26
29
  cc?: string | undefined;
27
30
  bcc?: string | undefined;
28
31
  thread_id?: string | undefined;
29
32
  reply_to_email_id?: string | undefined;
30
33
  from_draft_id?: string | undefined;
31
34
  }>, {
32
- body?: string | undefined;
33
35
  to?: string | undefined;
34
36
  subject?: string | undefined;
37
+ plaintext_body?: string | undefined;
38
+ html_body?: string | undefined;
35
39
  cc?: string | undefined;
36
40
  bcc?: string | undefined;
37
41
  thread_id?: string | undefined;
38
42
  reply_to_email_id?: string | undefined;
39
43
  from_draft_id?: string | undefined;
40
44
  }, {
41
- body?: string | undefined;
42
45
  to?: string | undefined;
43
46
  subject?: string | undefined;
47
+ plaintext_body?: string | undefined;
48
+ html_body?: string | undefined;
44
49
  cc?: string | undefined;
45
50
  bcc?: string | undefined;
46
51
  thread_id?: string | undefined;
@@ -61,9 +66,13 @@ export declare function sendEmailTool(server: Server, clientFactory: ClientFacto
61
66
  type: string;
62
67
  description: "Subject line of the email.";
63
68
  };
64
- body: {
69
+ plaintext_body: {
65
70
  type: string;
66
- description: "Plain text body content of the email.";
71
+ description: "Plain text body content of the email. At least one of plaintext_body or html_body must be provided (unless sending a draft). If both are provided, a multipart email is sent with both versions.";
72
+ };
73
+ html_body: {
74
+ type: string;
75
+ description: "HTML body content of the email for rich text formatting (links, bold, lists, etc.). At least one of plaintext_body or html_body must be provided (unless sending a draft). If both are provided, a multipart email is sent with both versions.";
67
76
  };
68
77
  cc: {
69
78
  type: string;
@@ -3,7 +3,8 @@ import { getHeader } from '../utils/email-helpers.js';
3
3
  const PARAM_DESCRIPTIONS = {
4
4
  to: 'Recipient email address(es). For multiple recipients, separate with commas.',
5
5
  subject: 'Subject line of the email.',
6
- body: 'Plain text body content of the email.',
6
+ plaintext_body: 'Plain text body content of the email. At least one of plaintext_body or html_body must be provided (unless sending a draft). If both are provided, a multipart email is sent with both versions.',
7
+ html_body: 'HTML body content of the email for rich text formatting (links, bold, lists, etc.). At least one of plaintext_body or html_body must be provided (unless sending a draft). If both are provided, a multipart email is sent with both versions.',
7
8
  cc: 'CC recipient email address(es). For multiple, separate with commas.',
8
9
  bcc: 'BCC recipient email address(es). For multiple, separate with commas.',
9
10
  thread_id: 'Thread ID to add this email to an existing conversation. ' +
@@ -11,13 +12,14 @@ const PARAM_DESCRIPTIONS = {
11
12
  reply_to_email_id: 'Email ID to reply to. If provided, the email will be formatted as a reply ' +
12
13
  'with proper In-Reply-To and References headers. Also requires thread_id.',
13
14
  from_draft_id: 'Draft ID to send. If provided, sends the specified draft instead of composing a new email. ' +
14
- 'When using this, other parameters (to, subject, body, etc.) are ignored.',
15
+ 'When using this, other parameters (to, subject, plaintext_body, etc.) are ignored.',
15
16
  };
16
17
  export const SendEmailSchema = z
17
18
  .object({
18
19
  to: z.string().optional().describe(PARAM_DESCRIPTIONS.to),
19
20
  subject: z.string().optional().describe(PARAM_DESCRIPTIONS.subject),
20
- body: z.string().optional().describe(PARAM_DESCRIPTIONS.body),
21
+ plaintext_body: z.string().min(1).optional().describe(PARAM_DESCRIPTIONS.plaintext_body),
22
+ html_body: z.string().min(1).optional().describe(PARAM_DESCRIPTIONS.html_body),
21
23
  cc: z.string().optional().describe(PARAM_DESCRIPTIONS.cc),
22
24
  bcc: z.string().optional().describe(PARAM_DESCRIPTIONS.bcc),
23
25
  thread_id: z.string().optional().describe(PARAM_DESCRIPTIONS.thread_id),
@@ -25,20 +27,21 @@ export const SendEmailSchema = z
25
27
  from_draft_id: z.string().optional().describe(PARAM_DESCRIPTIONS.from_draft_id),
26
28
  })
27
29
  .refine((data) => {
28
- // Either from_draft_id is provided, OR to, subject, and body are all provided
30
+ // Either from_draft_id is provided, OR to, subject, and one of plaintext_body/html_body are all provided
29
31
  if (data.from_draft_id) {
30
32
  return true;
31
33
  }
32
- return data.to && data.subject && data.body;
34
+ return data.to && data.subject && (data.plaintext_body || data.html_body);
33
35
  }, {
34
- message: 'Either provide from_draft_id to send a draft, or provide to, subject, and body to send a new email.',
36
+ message: 'Either provide from_draft_id to send a draft, or provide to, subject, and at least one of plaintext_body or html_body to send a new email.',
35
37
  });
36
38
  const TOOL_DESCRIPTION = `Send an email immediately or send a previously created draft.
37
39
 
38
40
  **Option 1: Send a new email**
39
41
  - to: Recipient email address(es) (required)
40
42
  - subject: Email subject line (required)
41
- - body: Plain text body content (required)
43
+ - plaintext_body: Plain text body content (at least one of plaintext_body or html_body required)
44
+ - html_body: HTML body content for rich text formatting (at least one of plaintext_body or html_body required)
42
45
  - cc: CC recipients (optional)
43
46
  - bcc: BCC recipients (optional)
44
47
  - thread_id: Thread ID to reply to an existing conversation (optional)
@@ -47,6 +50,9 @@ const TOOL_DESCRIPTION = `Send an email immediately or send a previously created
47
50
  **Option 2: Send a draft**
48
51
  - from_draft_id: ID of the draft to send (all other parameters are ignored)
49
52
 
53
+ **Body content:**
54
+ At least one of plaintext_body or html_body must be provided. If both are provided, a multipart email is sent with both plain text and HTML versions. Use html_body for rich formatting like hyperlinks, bold text, or lists.
55
+
50
56
  **Sending a reply:**
51
57
  To send a reply to an existing email:
52
58
  1. Get the thread_id and email_id from get_email_conversation
@@ -73,9 +79,13 @@ export function sendEmailTool(server, clientFactory) {
73
79
  type: 'string',
74
80
  description: PARAM_DESCRIPTIONS.subject,
75
81
  },
76
- body: {
82
+ plaintext_body: {
83
+ type: 'string',
84
+ description: PARAM_DESCRIPTIONS.plaintext_body,
85
+ },
86
+ html_body: {
77
87
  type: 'string',
78
- description: PARAM_DESCRIPTIONS.body,
88
+ description: PARAM_DESCRIPTIONS.html_body,
79
89
  },
80
90
  cc: {
81
91
  type: 'string',
@@ -120,7 +130,6 @@ export function sendEmailTool(server, clientFactory) {
120
130
  // TypeScript knows these are defined due to the refine check
121
131
  const to = parsed.to;
122
132
  const subject = parsed.subject;
123
- const body = parsed.body;
124
133
  let inReplyTo;
125
134
  let references;
126
135
  // If replying to an email, get the Message-ID for proper threading
@@ -140,7 +149,8 @@ export function sendEmailTool(server, clientFactory) {
140
149
  const sentEmail = await client.sendMessage({
141
150
  to,
142
151
  subject,
143
- body,
152
+ plaintextBody: parsed.plaintext_body,
153
+ htmlBody: parsed.html_body,
144
154
  cc: parsed.cc,
145
155
  bcc: parsed.bcc,
146
156
  threadId: parsed.thread_id,
@@ -153,6 +163,12 @@ export function sendEmailTool(server, clientFactory) {
153
163
  }
154
164
  responseText += `\n\n**To:** ${to}`;
155
165
  responseText += `\n**Subject:** ${subject}`;
166
+ const format = parsed.plaintext_body && parsed.html_body
167
+ ? 'Multipart (plain text + HTML)'
168
+ : parsed.html_body
169
+ ? 'HTML'
170
+ : 'Plain text';
171
+ responseText += `\n**Format:** ${format}`;
156
172
  if (parsed.cc) {
157
173
  responseText += `\n**CC:** ${parsed.cc}`;
158
174
  }