Skip to main content
Glama

gmail_send_email

Send emails or reply to threads through Gmail with confirmation required before sending.

Instructions

Send an email. Can send new emails or reply to existing threads. Requires confirmation before sending.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
toYesComma-separated list of recipient email addresses.
subjectYesEmail subject line.
bodyYesEmail body text in plain text format.
ccNoComma-separated list of CC recipient email addresses. Optional.
bccNoComma-separated list of BCC recipient email addresses. Optional.
reply_to_message_idNoMessage ID to reply to for threading. Optional.
confirmYesMust be true to actually send the email. Set false to preview.

Implementation Reference

  • Core implementation of the gmail_send_email tool in GmailClient class. Constructs MIME message, handles reply-to threading by fetching original message headers, base64 encodes, and sends via Gmail API users.messages.send.
    async def send_email(
        self,
        to: list[str],
        subject: str,
        body: str,
        cc: list[str] | None = None,
        bcc: list[str] | None = None,
        reply_to_message_id: str | None = None,
    ) -> dict:
        """Send an email.
        
        Args:
            to: List of recipient email addresses
            subject: Email subject
            body: Email body (plain text)
            cc: Optional CC recipients
            bcc: Optional BCC recipients
            reply_to_message_id: Optional message ID to reply to (for threading)
            
        Returns:
            Dict with sent message info or error
        """
        try:
            # Create the email message
            message = MIMEText(body)
            message['to'] = ', '.join(to)
            message['subject'] = subject
            
            if cc:
                message['cc'] = ', '.join(cc)
            if bcc:
                message['bcc'] = ', '.join(bcc)
            
            # Handle reply threading
            thread_id = None
            if reply_to_message_id:
                # Get the original message to get thread ID and references
                try:
                    original = self.service.users().messages().get(
                        userId="me", 
                        id=reply_to_message_id,
                        format="metadata",
                        metadataHeaders=["Message-ID", "References", "In-Reply-To"]
                    ).execute()
                    
                    thread_id = original.get("threadId")
                    
                    # Get original Message-ID for threading headers
                    headers = original.get("payload", {}).get("headers", [])
                    original_message_id = None
                    references = None
                    
                    for header in headers:
                        if header["name"].lower() == "message-id":
                            original_message_id = header["value"]
                        elif header["name"].lower() == "references":
                            references = header["value"]
                    
                    # Set threading headers
                    if original_message_id:
                        message['In-Reply-To'] = original_message_id
                        if references:
                            message['References'] = f"{references} {original_message_id}"
                        else:
                            message['References'] = original_message_id
                            
                except HttpError as e:
                    logger.warning(f"Could not get original message for threading: {e}")
            
            # Encode the message
            raw_message = base64.urlsafe_b64encode(message.as_bytes()).decode('utf-8')
            
            # Send the message
            body_data = {'raw': raw_message}
            if thread_id:
                body_data['threadId'] = thread_id
                
            sent_message = self.service.users().messages().send(
                userId="me",
                body=body_data
            ).execute()
            
            logger.info(f"Email sent successfully. Message ID: {sent_message['id']}")
            
            return {
                "success": True,
                "message_id": sent_message["id"],
                "thread_id": sent_message.get("threadId"),
                "to": to,
                "subject": subject,
            }
            
        except HttpError as e:
            logger.error(f"Failed to send email: {e}")
            return {
                "success": False,
                "error": str(e),
                "to": to,
                "subject": subject,
            }
  • MCP tool dispatcher handler for 'gmail_send_email'. Validates input arguments, provides preview mode if confirm=false, and delegates to GmailClient.send_email when confirmed.
    elif name == "gmail_send_email":
        to = arguments.get("to", [])
        subject = arguments.get("subject", "")
        body = arguments.get("body", "")
        cc = arguments.get("cc", [])
        bcc = arguments.get("bcc", [])
        reply_to_message_id = arguments.get("reply_to_message_id")
        confirm = arguments.get("confirm", False)
        
        # Validate required fields
        if not to:
            return [TextContent(type="text", text="Error: 'to' recipients are required.")]
        if not subject:
            return [TextContent(type="text", text="Error: 'subject' is required.")]
        if not body:
            return [TextContent(type="text", text="Error: 'body' is required.")]
        
        if not confirm:
            # Preview mode
            lines = [
                "⚠️ Preview: The following email would be sent:\n",
                f"To: {', '.join(to)}",
            ]
            if cc:
                lines.append(f"CC: {', '.join(cc)}")
            if bcc:
                lines.append(f"BCC: {', '.join(bcc)}")
            lines.append(f"Subject: {subject}")
            if reply_to_message_id:
                lines.append(f"Reply to message: {reply_to_message_id}")
            lines.append(f"\n--- Body ---\n{body[:500]}")
            if len(body) > 500:
                lines.append(f"\n... ({len(body) - 500} more characters)")
            lines.append("\n\nTo send this email, call this tool again with confirm=true")
            
            return [TextContent(type="text", text="\n".join(lines))]
        else:
            # Actually send the email
            result = await client.send_email(
                to=to,
                subject=subject,
                body=body,
                cc=cc if cc else None,
                bcc=bcc if bcc else None,
                reply_to_message_id=reply_to_message_id,
            )
            
            if result["success"]:
                return [TextContent(
                    type="text",
                    text=f"✅ Email sent successfully!\n\nTo: {', '.join(result['to'])}\nSubject: {result['subject']}\nMessage ID: {result['message_id']}"
                )]
            else:
                return [TextContent(
                    type="text",
                    text=f"❌ Failed to send email.\n\nError: {result['error']}"
                )]
  • Tool registration in list_tools() decorator, including name, description, and full inputSchema for parameter validation.
    Tool(
        name="gmail_send_email",
        description="Send an email. Can send new emails or reply to existing threads.",
        inputSchema={
            "type": "object",
            "properties": {
                "to": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "List of recipient email addresses",
                },
                "subject": {
                    "type": "string",
                    "description": "Email subject line",
                },
                "body": {
                    "type": "string",
                    "description": "Email body text (plain text)",
                },
                "cc": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "CC recipients (optional)",
                },
                "bcc": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "BCC recipients (optional)",
                },
                "reply_to_message_id": {
                    "type": "string",
                    "description": "Message ID to reply to (optional, for threading)",
                },
                "confirm": {
                    "type": "boolean",
                    "description": "Must be true to actually send the email. Set to false to preview.",
                    "default": False,
                },
            },
            "required": ["to", "subject", "body"],
        },
    ),
  • JSON schema definition for gmail_send_email tool inputs, defining properties, descriptions, defaults, and required fields.
    inputSchema={
        "type": "object",
        "properties": {
            "to": {
                "type": "array",
                "items": {"type": "string"},
                "description": "List of recipient email addresses",
            },
            "subject": {
                "type": "string",
                "description": "Email subject line",
            },
            "body": {
                "type": "string",
                "description": "Email body text (plain text)",
            },
            "cc": {
                "type": "array",
                "items": {"type": "string"},
                "description": "CC recipients (optional)",
            },
            "bcc": {
                "type": "array",
                "items": {"type": "string"},
                "description": "BCC recipients (optional)",
            },
            "reply_to_message_id": {
                "type": "string",
                "description": "Message ID to reply to (optional, for threading)",
            },
            "confirm": {
                "type": "boolean",
                "description": "Must be true to actually send the email. Set to false to preview.",
                "default": False,
            },
        },
        "required": ["to", "subject", "body"],
    },
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It adds valuable context: 'Requires confirmation before sending' indicates a safety mechanism, and 'Can send new emails or reply to existing threads' clarifies functionality. However, it misses details like rate limits, authentication needs, error handling, or what happens on failure. For a mutation tool with zero annotation coverage, this is a moderate but incomplete effort.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is concise and front-loaded: two sentences that directly state the purpose and a key behavioral trait. Every sentence earns its place by adding value, with no redundant or vague language. It could be slightly more structured (e.g., separating functionality and requirements), but it's efficient overall.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (a mutation tool for sending emails with 7 parameters), no annotations, and no output schema, the description is moderately complete. It covers the basic purpose and a confirmation requirement but lacks details on return values, error cases, or advanced usage. For a tool with this level of responsibility, more context would be beneficial to fully guide an agent.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 100% description coverage, so parameters like 'to', 'subject', 'body', etc., are well-documented in the schema. The description adds no additional parameter semantics beyond what the schema provides (e.g., no extra syntax or format details). According to the rules, with high schema coverage, the baseline is 3 even with no param info in the description.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Send an email. Can send new emails or reply to existing threads.' It specifies the verb ('send') and resource ('email'), distinguishing it from sibling tools like labeling, searching, or reading operations. However, it doesn't explicitly differentiate from hypothetical similar tools (e.g., 'send_email_with_attachments'), which keeps it from a perfect score.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides some usage context by mentioning 'Can send new emails or reply to existing threads,' which implies when to use it for different scenarios. However, it lacks explicit guidance on when to choose this tool over alternatives (e.g., vs. other email-sending tools if they existed) or any prerequisites beyond confirmation. No exclusions or clear alternatives are named, making it implied rather than explicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/murphy360/mcp_gmail'

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