Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 63 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,23 +63,24 @@ You can also configure the email server using environment variables, which is pa

#### Available Environment Variables

| Variable | Description | Default | Required |
| --------------------------------------------- | ------------------------------------------------ | ------------- | -------- |
| `MCP_EMAIL_SERVER_ACCOUNT_NAME` | Account identifier | `"default"` | No |
| `MCP_EMAIL_SERVER_FULL_NAME` | Display name | Email prefix | No |
| `MCP_EMAIL_SERVER_EMAIL_ADDRESS` | Email address | - | Yes |
| `MCP_EMAIL_SERVER_USER_NAME` | Login username | Same as email | No |
| `MCP_EMAIL_SERVER_PASSWORD` | Email password | - | Yes |
| `MCP_EMAIL_SERVER_IMAP_HOST` | IMAP server host | - | Yes |
| `MCP_EMAIL_SERVER_IMAP_PORT` | IMAP server port | `993` | No |
| `MCP_EMAIL_SERVER_IMAP_SSL` | Enable IMAP SSL | `true` | No |
| `MCP_EMAIL_SERVER_SMTP_HOST` | SMTP server host | - | Yes |
| `MCP_EMAIL_SERVER_SMTP_PORT` | SMTP server port | `465` | No |
| `MCP_EMAIL_SERVER_SMTP_SSL` | Enable SMTP SSL | `true` | No |
| `MCP_EMAIL_SERVER_SMTP_START_SSL` | Enable STARTTLS | `false` | No |
| `MCP_EMAIL_SERVER_ENABLE_ATTACHMENT_DOWNLOAD` | Enable attachment download | `false` | No |
| `MCP_EMAIL_SERVER_SAVE_TO_SENT` | Save sent emails to IMAP Sent folder | `true` | No |
| `MCP_EMAIL_SERVER_SENT_FOLDER_NAME` | Custom Sent folder name (auto-detect if not set) | - | No |
| Variable | Description | Default | Required |
| --------------------------------------------- | ------------------------------------------------- | ------------- | -------- |
| `MCP_EMAIL_SERVER_ACCOUNT_NAME` | Account identifier | `"default"` | No |
| `MCP_EMAIL_SERVER_FULL_NAME` | Display name | Email prefix | No |
| `MCP_EMAIL_SERVER_EMAIL_ADDRESS` | Email address | - | Yes |
| `MCP_EMAIL_SERVER_USER_NAME` | Login username | Same as email | No |
| `MCP_EMAIL_SERVER_PASSWORD` | Email password | - | Yes |
| `MCP_EMAIL_SERVER_IMAP_HOST` | IMAP server host | - | Yes |
| `MCP_EMAIL_SERVER_IMAP_PORT` | IMAP server port | `993` | No |
| `MCP_EMAIL_SERVER_IMAP_SSL` | Enable IMAP SSL | `true` | No |
| `MCP_EMAIL_SERVER_SMTP_HOST` | SMTP server host | - | Yes |
| `MCP_EMAIL_SERVER_SMTP_PORT` | SMTP server port | `465` | No |
| `MCP_EMAIL_SERVER_SMTP_SSL` | Enable SMTP SSL | `true` | No |
| `MCP_EMAIL_SERVER_SMTP_START_SSL` | Enable STARTTLS | `false` | No |
| `MCP_EMAIL_SERVER_SMTP_VERIFY_SSL` | Verify SSL certificates (disable for self-signed) | `true` | No |
| `MCP_EMAIL_SERVER_ENABLE_ATTACHMENT_DOWNLOAD` | Enable attachment download | `false` | No |
| `MCP_EMAIL_SERVER_SAVE_TO_SENT` | Save sent emails to IMAP Sent folder | `true` | No |
| `MCP_EMAIL_SERVER_SENT_FOLDER_NAME` | Custom Sent folder name (auto-detect if not set) | - | No |

### Enabling Attachment Downloads

Expand Down Expand Up @@ -150,6 +151,35 @@ sent_folder_name = "INBOX.Sent"

**To disable saving to Sent folder**, set `MCP_EMAIL_SERVER_SAVE_TO_SENT=false` or `save_to_sent = false` in your TOML config.

### Self-Signed Certificates (e.g., ProtonMail Bridge)

If you're using a local mail server with self-signed certificates (like ProtonMail Bridge), you'll need to disable SSL certificate verification:

```json
{
"mcpServers": {
"zerolib-email": {
"command": "uvx",
"args": ["mcp-email-server@latest", "stdio"],
"env": {
"MCP_EMAIL_SERVER_SMTP_VERIFY_SSL": "false"
}
}
}
}
```

Or in TOML configuration:

```toml
[[emails]]
account_name = "protonmail"
# ... other settings ...

[emails.outgoing]
verify_ssl = false
```

For separate IMAP/SMTP credentials, you can also use:

- `MCP_EMAIL_SERVER_IMAP_USER_NAME` / `MCP_EMAIL_SERVER_IMAP_PASSWORD`
Expand Down Expand Up @@ -217,6 +247,22 @@ await send_email(

The `in_reply_to` parameter sets the `In-Reply-To` header, and `references` sets the `References` header. Both are used by email clients to thread conversations properly.

### Forwarding Emails

To forward an email to new recipients:

```python
result = await forward_email(
account_name="work",
email_id="123",
recipients=["colleague@example.com"],
additional_message="FYI - please see below.",
)
print(f"Forwarded to: {result.forwarded_to}")
```

The forward preserves the original email's formatting (HTML or plain text) and includes attachments by default. Use `include_attachments=False` to forward without attachments.

## Development

This project is managed using [uv](https://github.com/ai-zerolab/uv).
Expand Down
39 changes: 39 additions & 0 deletions mcp_email_server/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
AttachmentDownloadResponse,
EmailContentBatchResponse,
EmailMetadataPageResponse,
ForwardEmailResponse,
)

mcp = FastMCP("email")
Expand Down Expand Up @@ -204,3 +205,41 @@ async def download_attachment(

handler = dispatch_handler(account_name)
return await handler.download_attachment(email_id, attachment_name, save_path, mailbox)


@mcp.tool(description="Forward an email to new recipients with original body and attachments.")
async def forward_email(
account_name: Annotated[str, Field(description="The name of the email account.")],
email_id: Annotated[
str,
Field(description="The email_id to forward (obtained from list_emails_metadata)."),
],
recipients: Annotated[list[str], Field(description="List of recipient email addresses to forward to.")],
from_address: Annotated[
str | None,
Field(
default=None,
description="Override the sender address. If not specified, uses the account's default email address.",
),
] = None,
additional_message: Annotated[
str | None,
Field(default=None, description="Optional message to include before the forwarded content."),
] = None,
include_attachments: Annotated[
bool,
Field(default=True, description="Whether to include the original attachments in the forwarded email."),
] = True,
mailbox: Annotated[
str, Field(default="INBOX", description="The mailbox containing the email to forward.")
] = "INBOX",
) -> ForwardEmailResponse:
handler = dispatch_handler(account_name)
return await handler.forward_email(
email_id=email_id,
recipients=recipients,
from_address=from_address,
additional_message=additional_message,
include_attachments=include_attachments,
mailbox=mailbox,
)
5 changes: 5 additions & 0 deletions mcp_email_server/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class EmailServer(BaseModel):
port: int
use_ssl: bool = True # Usually port 465
start_ssl: bool = False # Usually port 587
verify_ssl: bool = True # Set to False for self-signed certificates (e.g., ProtonMail Bridge)

def masked(self) -> EmailServer:
return self.model_copy(update={"password": "********"})
Expand Down Expand Up @@ -96,6 +97,7 @@ def init(
smtp_port: int = 465,
smtp_ssl: bool = True,
smtp_start_ssl: bool = False,
smtp_verify_ssl: bool = True,
smtp_user_name: str | None = None,
smtp_password: str | None = None,
save_to_sent: bool = True,
Expand All @@ -119,6 +121,7 @@ def init(
port=smtp_port,
use_ssl=smtp_ssl,
start_ssl=smtp_start_ssl,
verify_ssl=smtp_verify_ssl,
),
save_to_sent=save_to_sent,
sent_folder_name=sent_folder_name,
Expand All @@ -141,6 +144,7 @@ def from_env(cls) -> EmailSettings | None:
- MCP_EMAIL_SERVER_SMTP_PORT (default: 465)
- MCP_EMAIL_SERVER_SMTP_SSL (default: true)
- MCP_EMAIL_SERVER_SMTP_START_SSL (default: false)
- MCP_EMAIL_SERVER_SMTP_VERIFY_SSL (default: true)
- MCP_EMAIL_SERVER_SAVE_TO_SENT (default: true)
- MCP_EMAIL_SERVER_SENT_FOLDER_NAME (default: auto-detect)
"""
Expand Down Expand Up @@ -183,6 +187,7 @@ def parse_bool(value: str | None, default: bool = True) -> bool:
smtp_port=int(os.getenv("MCP_EMAIL_SERVER_SMTP_PORT", "465")),
smtp_ssl=parse_bool(os.getenv("MCP_EMAIL_SERVER_SMTP_SSL"), True),
smtp_start_ssl=parse_bool(os.getenv("MCP_EMAIL_SERVER_SMTP_START_SSL"), False),
smtp_verify_ssl=parse_bool(os.getenv("MCP_EMAIL_SERVER_SMTP_VERIFY_SSL"), True),
smtp_user_name=os.getenv("MCP_EMAIL_SERVER_SMTP_USER_NAME", user_name),
smtp_password=os.getenv("MCP_EMAIL_SERVER_SMTP_PASSWORD", password),
imap_user_name=os.getenv("MCP_EMAIL_SERVER_IMAP_USER_NAME", user_name),
Expand Down
26 changes: 26 additions & 0 deletions mcp_email_server/emails/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
AttachmentDownloadResponse,
EmailContentBatchResponse,
EmailMetadataPageResponse,
ForwardEmailResponse,
)


Expand Down Expand Up @@ -75,3 +76,28 @@ async def download_attachment(
Returns:
AttachmentDownloadResponse with download result information.
"""

@abc.abstractmethod
async def forward_email(
self,
email_id: str,
recipients: list[str],
from_address: str | None = None,
additional_message: str | None = None,
include_attachments: bool = True,
mailbox: str = "INBOX",
) -> "ForwardEmailResponse":
"""
Forward an email to new recipients.

Args:
email_id: The UID of the email to forward.
recipients: List of recipient email addresses.
from_address: Override sender address (uses account default if None).
additional_message: Optional message to prepend to the forwarded email.
include_attachments: Whether to include original attachments.
mailbox: The mailbox containing the email (default: "INBOX").

Returns:
ForwardEmailResponse with forward result information.
"""
Loading
Loading