fix(email): thread attachment sends on the caller's reply_to
send_document() accepted reply_to but never passed it down, so attachments always threaded from the cached per-address context (or not at all) even when the caller named the message to reply to. The plain-text path (_send_email) already honored it; the attachment path now does too. The metadata half of #10131 (send_image rejecting metadata=) was already fixed on main by the adapter parity pass. Diagnosis from #10131 and the explicit-reply_to-wins shape from PR #10321 (which targeted the pre-plugin path). Fixes #10131 Co-authored-by: LeonSGP43 <cine.dreamer.one@gmail.com>
This commit is contained in:
@@ -703,9 +703,11 @@ class EmailAdapter(BasePlatformAdapter):
|
||||
logger.info("[Email] Sent reply to %s (subject: %s)", to_addr, subject)
|
||||
return msg_id
|
||||
|
||||
def _send_with_files(self, to_addr: str, body: str, files: List[Tuple[Path, str]], *, lenient: bool) -> str:
|
||||
"""Send a reply with attachments; *lenient* logs-and-skips unattachable files instead of raising."""
|
||||
msg, msg_id, _ = self._new_reply(to_addr, body)
|
||||
def _send_with_files(self, to_addr: str, body: str, files: List[Tuple[Path, str]], *, lenient: bool,
|
||||
reply_to_msg_id: Optional[str] = None) -> str:
|
||||
"""Send a reply with attachments; *lenient* logs-and-skips unattachable files instead of raising.
|
||||
An explicit *reply_to_msg_id* threads the mail like ``_send_email`` does (#10131)."""
|
||||
msg, msg_id, _ = self._new_reply(to_addr, body, reply_to_msg_id)
|
||||
for path, name in files:
|
||||
try:
|
||||
_attach_file(msg, path, name)
|
||||
@@ -755,11 +757,14 @@ class EmailAdapter(BasePlatformAdapter):
|
||||
async def send_document(self, chat_id: str, file_path: str, caption: Optional[str] = None,
|
||||
file_name: Optional[str] = None, reply_to: Optional[str] = None, **kwargs) -> SendResult:
|
||||
"""Send a file as an email attachment."""
|
||||
return await self._run_send(self._send_email_with_attachment, (chat_id, caption or "", file_path, file_name), "[Email] Send document failed: %s")
|
||||
return await self._run_send(self._send_email_with_attachment, (chat_id, caption or "", file_path, file_name, reply_to),
|
||||
"[Email] Send document failed: %s")
|
||||
|
||||
def _send_email_with_attachment(self, to_addr: str, body: str, file_path: str, file_name: Optional[str] = None) -> str:
|
||||
def _send_email_with_attachment(self, to_addr: str, body: str, file_path: str, file_name: Optional[str] = None,
|
||||
reply_to_msg_id: Optional[str] = None) -> str:
|
||||
"""Send an email with a single file attachment via SMTP (raises if unattachable)."""
|
||||
return self._send_with_files(to_addr, body, [(Path(file_path), file_name or Path(file_path).name)], lenient=False)
|
||||
return self._send_with_files(to_addr, body, [(Path(file_path), file_name or Path(file_path).name)], lenient=False,
|
||||
reply_to_msg_id=reply_to_msg_id)
|
||||
|
||||
async def get_chat_info(self, chat_id: str) -> Dict[str, Any]:
|
||||
"""Return basic info about the email chat."""
|
||||
|
||||
@@ -432,6 +432,27 @@ class TestSendMethods(unittest.TestCase):
|
||||
os.unlink(tmp_path)
|
||||
|
||||
|
||||
def test_send_document_threads_on_explicit_reply_to(self):
|
||||
"""An explicit reply_to wins over the cached thread context for attachment sends (#10131)."""
|
||||
import asyncio
|
||||
import tempfile
|
||||
adapter = self._make_adapter()
|
||||
adapter._thread_context["user@test.com"] = {"subject": "Old", "message_id": "<cached@test.com>"}
|
||||
with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as f:
|
||||
f.write(b"doc")
|
||||
tmp_path = f.name
|
||||
try:
|
||||
with patch("smtplib.SMTP") as mock_smtp:
|
||||
mock_server = MagicMock()
|
||||
mock_smtp.return_value = mock_server
|
||||
result = asyncio.run(adapter.send_document("user@test.com", tmp_path, reply_to="<explicit@test.com>"))
|
||||
self.assertTrue(result.success)
|
||||
sent_msg = mock_server.send_message.call_args[0][0]
|
||||
self.assertEqual(sent_msg["In-Reply-To"], "<explicit@test.com>")
|
||||
self.assertEqual(sent_msg["References"], "<explicit@test.com>")
|
||||
finally:
|
||||
os.unlink(tmp_path)
|
||||
|
||||
def test_get_chat_info(self):
|
||||
"""get_chat_info should return email address as chat info."""
|
||||
import asyncio
|
||||
|
||||
Reference in New Issue
Block a user