send_email
Send emails through Fastmail with options for recipients, subject, body content, and email organization.
Instructions
Send an email
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| to | Yes | Recipient email addresses | |
| cc | No | CC email addresses (optional) | |
| bcc | No | BCC email addresses (optional) | |
| from | No | Sender email address (optional, defaults to account primary email) | |
| mailboxId | No | Mailbox ID to save the email to (optional, defaults to Drafts folder) | |
| subject | Yes | Email subject | |
| textBody | No | Plain text body (optional) | |
| htmlBody | No | HTML body (optional) |
Implementation Reference
- src/index.ts:178-222 (registration)Registration of the 'send_email' MCP tool, including name, description, and detailed input schema definition.{ name: 'send_email', description: 'Send an email', inputSchema: { type: 'object', properties: { to: { type: 'array', items: { type: 'string' }, description: 'Recipient email addresses', }, cc: { type: 'array', items: { type: 'string' }, description: 'CC email addresses (optional)', }, bcc: { type: 'array', items: { type: 'string' }, description: 'BCC email addresses (optional)', }, from: { type: 'string', description: 'Sender email address (optional, defaults to account primary email)', }, mailboxId: { type: 'string', description: 'Mailbox ID to save the email to (optional, defaults to Drafts folder)', }, subject: { type: 'string', description: 'Email subject', }, textBody: { type: 'string', description: 'Plain text body (optional)', }, htmlBody: { type: 'string', description: 'HTML body (optional)', }, }, required: ['to', 'subject'], }, },
- src/index.ts:703-734 (handler)MCP tool handler for 'send_email': extracts and validates arguments, calls JmapClient.sendEmail(), and returns success response with submission ID.case 'send_email': { const { to, cc, bcc, from, mailboxId, subject, textBody, htmlBody } = args as any; if (!to || !Array.isArray(to) || to.length === 0) { throw new McpError(ErrorCode.InvalidParams, 'to field is required and must be a non-empty array'); } if (!subject) { throw new McpError(ErrorCode.InvalidParams, 'subject is required'); } if (!textBody && !htmlBody) { throw new McpError(ErrorCode.InvalidParams, 'Either textBody or htmlBody is required'); } const submissionId = await client.sendEmail({ to, cc, bcc, from, mailboxId, subject, textBody, htmlBody, }); return { content: [ { type: 'text', text: `Email sent successfully. Submission ID: ${submissionId}`, }, ], }; }
- src/jmap-client.ts:177-298 (helper)JmapClient.sendEmail(): core helper function implementing JMAP protocol calls (Email/set to create draft, EmailSubmission/set to submit, automatic move to Sent folder on success). Handles identity validation, mailbox resolution, and error cases.async sendEmail(email: { to: string[]; cc?: string[]; bcc?: string[]; subject: string; textBody?: string; htmlBody?: string; from?: string; mailboxId?: string; }): Promise<string> { const session = await this.getSession(); // Get all identities to validate from address const identities = await this.getIdentities(); if (!identities || identities.length === 0) { throw new Error('No sending identities found'); } // Determine which identity to use let selectedIdentity; if (email.from) { // Validate that the from address matches an available identity selectedIdentity = identities.find(id => id.email.toLowerCase() === email.from?.toLowerCase() ); if (!selectedIdentity) { throw new Error('From address is not verified for sending. Choose one of your verified identities.'); } } else { // Use default identity selectedIdentity = identities.find(id => id.mayDelete === false) || identities[0]; } const fromEmail = selectedIdentity.email; // Get the mailbox IDs we need const mailboxes = await this.getMailboxes(); const draftsMailbox = mailboxes.find(mb => mb.role === 'drafts') || mailboxes.find(mb => mb.name.toLowerCase().includes('draft')); const sentMailbox = mailboxes.find(mb => mb.role === 'sent') || mailboxes.find(mb => mb.name.toLowerCase().includes('sent')); if (!draftsMailbox) { throw new Error('Could not find Drafts mailbox to save email'); } if (!sentMailbox) { throw new Error('Could not find Sent mailbox to move email after sending'); } // Use provided mailboxId or default to drafts for initial creation const initialMailboxId = email.mailboxId || draftsMailbox.id; // Ensure we have at least one body type if (!email.textBody && !email.htmlBody) { throw new Error('Either textBody or htmlBody must be provided'); } const initialMailboxIds: Record<string, boolean> = {}; initialMailboxIds[initialMailboxId] = true; const sentMailboxIds: Record<string, boolean> = {}; sentMailboxIds[sentMailbox.id] = true; const emailObject = { mailboxIds: initialMailboxIds, keywords: { $draft: true }, from: [{ email: fromEmail }], to: email.to.map(addr => ({ email: addr })), cc: email.cc?.map(addr => ({ email: addr })) || [], bcc: email.bcc?.map(addr => ({ email: addr })) || [], subject: email.subject, textBody: email.textBody ? [{ partId: 'text', type: 'text/plain' }] : undefined, htmlBody: email.htmlBody ? [{ partId: 'html', type: 'text/html' }] : undefined, bodyValues: { ...(email.textBody && { text: { value: email.textBody } }), ...(email.htmlBody && { html: { value: email.htmlBody } }) } }; const request: JmapRequest = { using: ['urn:ietf:params:jmap:core', 'urn:ietf:params:jmap:mail', 'urn:ietf:params:jmap:submission'], methodCalls: [ ['Email/set', { accountId: session.accountId, create: { draft: emailObject } }, 'createEmail'], ['EmailSubmission/set', { accountId: session.accountId, create: { submission: { emailId: '#draft', identityId: selectedIdentity.id, envelope: { mailFrom: { email: fromEmail }, rcptTo: email.to.map(addr => ({ email: addr })) } } }, onSuccessUpdateEmail: { '#submission': { mailboxIds: sentMailboxIds, keywords: { $seen: true } } } }, 'submitEmail'] ] }; const response = await this.makeRequest(request); // Check if email creation was successful const emailResult = response.methodResponses[0][1]; if (emailResult.notCreated && emailResult.notCreated.draft) { throw new Error('Failed to create email. Please check inputs and try again.'); } // Check if email submission was successful const submissionResult = response.methodResponses[1][1]; if (submissionResult.notCreated && submissionResult.notCreated.submission) { throw new Error('Failed to submit email. Please try again later.'); } return submissionResult.created?.submission?.id || 'unknown'; }