Skip to content

Commit 28631b5

Browse files
feat: Add verify_ssl support for IMAP connections (#123)
* feat: Add verify_ssl support for IMAP connections * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 9a1e992 commit 28631b5

File tree

3 files changed

+51
-28
lines changed

3 files changed

+51
-28
lines changed

README.md

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -63,24 +63,25 @@ You can also configure the email server using environment variables, which is pa
6363

6464
#### Available Environment Variables
6565

66-
| Variable | Description | Default | Required |
67-
| --------------------------------------------- | ------------------------------------------------- | ------------- | -------- |
68-
| `MCP_EMAIL_SERVER_ACCOUNT_NAME` | Account identifier | `"default"` | No |
69-
| `MCP_EMAIL_SERVER_FULL_NAME` | Display name | Email prefix | No |
70-
| `MCP_EMAIL_SERVER_EMAIL_ADDRESS` | Email address | - | Yes |
71-
| `MCP_EMAIL_SERVER_USER_NAME` | Login username | Same as email | No |
72-
| `MCP_EMAIL_SERVER_PASSWORD` | Email password | - | Yes |
73-
| `MCP_EMAIL_SERVER_IMAP_HOST` | IMAP server host | - | Yes |
74-
| `MCP_EMAIL_SERVER_IMAP_PORT` | IMAP server port | `993` | No |
75-
| `MCP_EMAIL_SERVER_IMAP_SSL` | Enable IMAP SSL | `true` | No |
76-
| `MCP_EMAIL_SERVER_SMTP_HOST` | SMTP server host | - | Yes |
77-
| `MCP_EMAIL_SERVER_SMTP_PORT` | SMTP server port | `465` | No |
78-
| `MCP_EMAIL_SERVER_SMTP_SSL` | Enable SMTP SSL | `true` | No |
79-
| `MCP_EMAIL_SERVER_SMTP_START_SSL` | Enable STARTTLS | `false` | No |
80-
| `MCP_EMAIL_SERVER_SMTP_VERIFY_SSL` | Verify SSL certificates (disable for self-signed) | `true` | No |
81-
| `MCP_EMAIL_SERVER_ENABLE_ATTACHMENT_DOWNLOAD` | Enable attachment download | `false` | No |
82-
| `MCP_EMAIL_SERVER_SAVE_TO_SENT` | Save sent emails to IMAP Sent folder | `true` | No |
83-
| `MCP_EMAIL_SERVER_SENT_FOLDER_NAME` | Custom Sent folder name (auto-detect if not set) | - | No |
66+
| Variable | Description | Default | Required |
67+
| --------------------------------------------- | ------------------------------------------------------ | ------------- | -------- |
68+
| `MCP_EMAIL_SERVER_ACCOUNT_NAME` | Account identifier | `"default"` | No |
69+
| `MCP_EMAIL_SERVER_FULL_NAME` | Display name | Email prefix | No |
70+
| `MCP_EMAIL_SERVER_EMAIL_ADDRESS` | Email address | - | Yes |
71+
| `MCP_EMAIL_SERVER_USER_NAME` | Login username | Same as email | No |
72+
| `MCP_EMAIL_SERVER_PASSWORD` | Email password | - | Yes |
73+
| `MCP_EMAIL_SERVER_IMAP_HOST` | IMAP server host | - | Yes |
74+
| `MCP_EMAIL_SERVER_IMAP_PORT` | IMAP server port | `993` | No |
75+
| `MCP_EMAIL_SERVER_IMAP_SSL` | Enable IMAP SSL | `true` | No |
76+
| `MCP_EMAIL_SERVER_IMAP_VERIFY_SSL` | Verify IMAP SSL certificates (disable for self-signed) | `true` | No |
77+
| `MCP_EMAIL_SERVER_SMTP_HOST` | SMTP server host | - | Yes |
78+
| `MCP_EMAIL_SERVER_SMTP_PORT` | SMTP server port | `465` | No |
79+
| `MCP_EMAIL_SERVER_SMTP_SSL` | Enable SMTP SSL | `true` | No |
80+
| `MCP_EMAIL_SERVER_SMTP_START_SSL` | Enable STARTTLS | `false` | No |
81+
| `MCP_EMAIL_SERVER_SMTP_VERIFY_SSL` | Verify SSL certificates (disable for self-signed) | `true` | No |
82+
| `MCP_EMAIL_SERVER_ENABLE_ATTACHMENT_DOWNLOAD` | Enable attachment download | `false` | No |
83+
| `MCP_EMAIL_SERVER_SAVE_TO_SENT` | Save sent emails to IMAP Sent folder | `true` | No |
84+
| `MCP_EMAIL_SERVER_SENT_FOLDER_NAME` | Custom Sent folder name (auto-detect if not set) | - | No |
8485

8586
### Enabling Attachment Downloads
8687

@@ -162,6 +163,7 @@ If you're using a local mail server with self-signed certificates (like ProtonMa
162163
"command": "uvx",
163164
"args": ["mcp-email-server@latest", "stdio"],
164165
"env": {
166+
"MCP_EMAIL_SERVER_IMAP_VERIFY_SSL": "false",
165167
"MCP_EMAIL_SERVER_SMTP_VERIFY_SSL": "false"
166168
}
167169
}
@@ -176,6 +178,9 @@ Or in TOML configuration:
176178
account_name = "protonmail"
177179
# ... other settings ...
178180

181+
[emails.incoming]
182+
verify_ssl = false
183+
179184
[emails.outgoing]
180185
verify_ssl = false
181186
```

mcp_email_server/config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ def init(
102102
imap_password: str | None = None,
103103
imap_port: int = 993,
104104
imap_ssl: bool = True,
105+
imap_verify_ssl: bool = True,
105106
smtp_port: int = 465,
106107
smtp_ssl: bool = True,
107108
smtp_start_ssl: bool = False,
@@ -121,6 +122,7 @@ def init(
121122
host=imap_host,
122123
port=imap_port,
123124
use_ssl=imap_ssl,
125+
verify_ssl=imap_verify_ssl,
124126
),
125127
outgoing=EmailServer(
126128
user_name=smtp_user_name or user_name,
@@ -148,6 +150,7 @@ def from_env(cls) -> EmailSettings | None:
148150
- MCP_EMAIL_SERVER_IMAP_HOST
149151
- MCP_EMAIL_SERVER_IMAP_PORT (default: 993)
150152
- MCP_EMAIL_SERVER_IMAP_SSL (default: true)
153+
- MCP_EMAIL_SERVER_IMAP_VERIFY_SSL (default: true)
151154
- MCP_EMAIL_SERVER_SMTP_HOST
152155
- MCP_EMAIL_SERVER_SMTP_PORT (default: 465)
153156
- MCP_EMAIL_SERVER_SMTP_SSL (default: true)
@@ -185,6 +188,7 @@ def from_env(cls) -> EmailSettings | None:
185188
imap_host=imap_host,
186189
imap_port=int(os.getenv("MCP_EMAIL_SERVER_IMAP_PORT", "993")),
187190
imap_ssl=_parse_bool_env(os.getenv("MCP_EMAIL_SERVER_IMAP_SSL"), True),
191+
imap_verify_ssl=_parse_bool_env(os.getenv("MCP_EMAIL_SERVER_IMAP_VERIFY_SSL"), True),
188192
smtp_host=smtp_host,
189193
smtp_port=int(os.getenv("MCP_EMAIL_SERVER_SMTP_PORT", "465")),
190194
smtp_ssl=_parse_bool_env(os.getenv("MCP_EMAIL_SERVER_SMTP_SSL"), True),

mcp_email_server/emails/classic.py

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ async def _send_imap_id(imap: aioimaplib.IMAP4 | aioimaplib.IMAP4_SSL) -> None:
7979
logger.warning(f"IMAP ID command failed: {e!s}")
8080

8181

82-
def _create_smtp_ssl_context(verify_ssl: bool) -> ssl.SSLContext | None:
83-
"""Create SSL context for SMTP connections.
82+
def _create_ssl_context(verify_ssl: bool) -> ssl.SSLContext | None:
83+
"""Create SSL context for SMTP/IMAP connections.
8484
8585
Returns None for default verification, or permissive context
8686
for self-signed certificates when verify_ssl=False.
@@ -93,6 +93,10 @@ def _create_smtp_ssl_context(verify_ssl: bool) -> ssl.SSLContext | None:
9393
return ctx
9494

9595

96+
# Backwards-compatible alias
97+
_create_smtp_ssl_context = _create_ssl_context
98+
99+
96100
class EmailClient:
97101
def __init__(self, email_server: EmailServer, sender: str | None = None):
98102
self.email_server = email_server
@@ -104,9 +108,16 @@ def __init__(self, email_server: EmailServer, sender: str | None = None):
104108
self.smtp_start_tls = self.email_server.start_ssl
105109
self.smtp_verify_ssl = self.email_server.verify_ssl
106110

111+
def _imap_connect(self) -> aioimaplib.IMAP4_SSL | aioimaplib.IMAP4:
112+
"""Create a new IMAP connection with the configured SSL context."""
113+
if self.email_server.use_ssl:
114+
imap_ssl_context = _create_ssl_context(self.email_server.verify_ssl)
115+
return self.imap_class(self.email_server.host, self.email_server.port, ssl_context=imap_ssl_context)
116+
return self.imap_class(self.email_server.host, self.email_server.port)
117+
107118
def _get_smtp_ssl_context(self) -> ssl.SSLContext | None:
108119
"""Get SSL context for SMTP connections based on verify_ssl setting."""
109-
return _create_smtp_ssl_context(self.smtp_verify_ssl)
120+
return _create_ssl_context(self.smtp_verify_ssl)
110121

111122
@staticmethod
112123
def _parse_recipients(email_message) -> list[str]:
@@ -403,7 +414,7 @@ async def get_email_count(
403414
flagged: bool | None = None,
404415
answered: bool | None = None,
405416
) -> int:
406-
imap = self.imap_class(self.email_server.host, self.email_server.port)
417+
imap = self._imap_connect()
407418
try:
408419
# Wait for the connection to be established
409420
await imap._client_task
@@ -449,7 +460,7 @@ async def get_emails_metadata_stream(
449460
flagged: bool | None = None,
450461
answered: bool | None = None,
451462
) -> AsyncGenerator[dict[str, Any], None]:
452-
imap = self.imap_class(self.email_server.host, self.email_server.port)
463+
imap = self._imap_connect()
453464
try:
454465
# Wait for the connection to be established
455466
await imap._client_task
@@ -563,7 +574,7 @@ async def _fetch_email_with_formats(self, imap, email_id: str) -> list | None:
563574
return None
564575

565576
async def get_email_body_by_id(self, email_id: str, mailbox: str = "INBOX") -> dict[str, Any] | None:
566-
imap = self.imap_class(self.email_server.host, self.email_server.port)
577+
imap = self._imap_connect()
567578
try:
568579
# Wait for the connection to be established
569580
await imap._client_task
@@ -618,7 +629,7 @@ async def download_attachment(
618629
Returns:
619630
A dictionary with download result information.
620631
"""
621-
imap = self.imap_class(self.email_server.host, self.email_server.port)
632+
imap = self._imap_connect()
622633
try:
623634
await imap._client_task
624635
await imap.wait_hello_from_server()
@@ -853,8 +864,11 @@ async def append_to_sent(
853864
Returns:
854865
True if successfully saved, False otherwise
855866
"""
856-
imap_class = aioimaplib.IMAP4_SSL if incoming_server.use_ssl else aioimaplib.IMAP4
857-
imap = imap_class(incoming_server.host, incoming_server.port)
867+
if incoming_server.use_ssl:
868+
imap_ssl_context = _create_ssl_context(incoming_server.verify_ssl)
869+
imap = aioimaplib.IMAP4_SSL(incoming_server.host, incoming_server.port, ssl_context=imap_ssl_context)
870+
else:
871+
imap = aioimaplib.IMAP4(incoming_server.host, incoming_server.port)
858872

859873
# Common Sent folder names across different providers
860874
sent_folder_candidates = [
@@ -928,7 +942,7 @@ async def append_to_sent(
928942

929943
async def delete_emails(self, email_ids: list[str], mailbox: str = "INBOX") -> tuple[list[str], list[str]]:
930944
"""Delete emails by their UIDs. Returns (deleted_ids, failed_ids)."""
931-
imap = self.imap_class(self.email_server.host, self.email_server.port)
945+
imap = self._imap_connect()
932946
deleted_ids = []
933947
failed_ids = []
934948

0 commit comments

Comments
 (0)