Skip to content

Commit 9a1e992

Browse files
authored
Fix: Set Message-Id and Date headers on outgoing emails (#115)
* fix: set Message-Id and Date headers on outgoing emails Emails sent via the MCP server are missing Message-Id and Date headers. When using iCloud SMTP (and similar providers), these headers are assigned server-side during delivery, but they don't appear in the copy saved to the IMAP Sent folder via APPEND. This breaks email threading in clients like Apple Mail, which build the References chain from the Sent folder copy rather than the delivered message. Generate Message-Id (via email.utils.make_msgid) and Date (via email.utils.formatdate) headers on the message object before both sending via SMTP and saving to the Sent folder, so the same values appear in both copies. * test: add tests for Message-Id and Date header generation Cover header presence and domain extraction from the sender address, and verify the same Message-Id and Date appear on both the SMTP-sent message and the returned object used for IMAP Sent folder saving.
1 parent c442e33 commit 9a1e992

File tree

2 files changed

+67
-0
lines changed

2 files changed

+67
-0
lines changed

mcp_email_server/emails/classic.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,12 @@ async def send_email(
776776
if references:
777777
msg["References"] = references
778778

779+
# Set Date and Message-Id headers so the same values appear in both
780+
# the SMTP-sent copy and the IMAP Sent folder copy
781+
msg["Date"] = email.utils.formatdate(localtime=True)
782+
sender_domain = self.sender.rsplit("@", 1)[-1].rstrip(">")
783+
msg["Message-Id"] = email.utils.make_msgid(domain=sender_domain)
784+
779785
# Note: BCC recipients are not added to headers (they remain hidden)
780786
# but will be included in the actual recipients for SMTP delivery
781787

tests/test_email_client.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,67 @@ async def test_send_email(self, email_client):
328328
assert "bcc@example.com" in recipients
329329

330330

331+
class TestSendEmailMessageIdAndDate:
332+
@pytest.mark.asyncio
333+
async def test_send_email_sets_message_id_and_date(self, email_client):
334+
"""Test that send_email sets Message-Id and Date headers."""
335+
mock_smtp = AsyncMock()
336+
mock_smtp.__aenter__.return_value = mock_smtp
337+
mock_smtp.__aexit__.return_value = None
338+
mock_smtp.login = AsyncMock()
339+
mock_smtp.send_message = AsyncMock()
340+
341+
with patch("aiosmtplib.SMTP", return_value=mock_smtp):
342+
msg = await email_client.send_email(
343+
recipients=["recipient@example.com"],
344+
subject="Test Subject",
345+
body="Test Body",
346+
)
347+
348+
assert msg["Message-Id"] is not None
349+
assert "@example.com>" in msg["Message-Id"]
350+
assert msg["Date"] is not None
351+
352+
@pytest.mark.asyncio
353+
async def test_send_email_message_id_uses_sender_domain(self, email_server):
354+
"""Test that Message-Id domain is extracted from the sender address."""
355+
client = EmailClient(email_server, sender="user@getsequel.app")
356+
mock_smtp = AsyncMock()
357+
mock_smtp.__aenter__.return_value = mock_smtp
358+
mock_smtp.__aexit__.return_value = None
359+
mock_smtp.login = AsyncMock()
360+
mock_smtp.send_message = AsyncMock()
361+
362+
with patch("aiosmtplib.SMTP", return_value=mock_smtp):
363+
msg = await client.send_email(
364+
recipients=["recipient@example.com"],
365+
subject="Test",
366+
body="Body",
367+
)
368+
369+
assert "@getsequel.app>" in msg["Message-Id"]
370+
371+
@pytest.mark.asyncio
372+
async def test_send_email_same_message_on_smtp_and_return(self, email_client):
373+
"""Test that the same msg object (with Message-Id) is sent and returned."""
374+
mock_smtp = AsyncMock()
375+
mock_smtp.__aenter__.return_value = mock_smtp
376+
mock_smtp.__aexit__.return_value = None
377+
mock_smtp.login = AsyncMock()
378+
mock_smtp.send_message = AsyncMock()
379+
380+
with patch("aiosmtplib.SMTP", return_value=mock_smtp):
381+
returned_msg = await email_client.send_email(
382+
recipients=["recipient@example.com"],
383+
subject="Test",
384+
body="Body",
385+
)
386+
387+
sent_msg = mock_smtp.send_message.call_args[0][0]
388+
assert sent_msg["Message-Id"] == returned_msg["Message-Id"]
389+
assert sent_msg["Date"] == returned_msg["Date"]
390+
391+
331392
class TestParseEmailData:
332393
def test_parse_email_extracts_message_id(self, email_client):
333394
"""Test that Message-ID header is extracted during parsing."""

0 commit comments

Comments
 (0)