fix: preserve inline and positional attachments when parsing multipart emails
linuztx committed
Mar 23, 2026 at 14:01 UTC
67a07e4b9641545ee5fe082fe6726bdc1977aab9
1 file changed
+23
-12
plugins/_email_integration/helpers/imap_client.py
+23
-12
@@ -321,6 +321,7 @@ async def _parse_body(
321
body = ""
322
attachments: list[str] = []
323
cid_map: dict[str, str] = {}
324
+ body_parts: list[str] = []
325
326
if email_msg.is_multipart():
327
for part in email_msg.walk():
@@ -341,17 +342,27 @@ async def _parse_body(
342
cid = part.get("Content-ID")
343
if cid:
344
cid_map[cid.strip("<>")] = path
344
-
345
- elif content_type == "text/plain" and not body:
346
- charset = part.get_content_charset() or "utf-8"
347
- payload = part.get_payload(decode=True)
348
- body = payload.decode(charset, errors="ignore") if isinstance(payload, bytes) else ""
349
-
350
- elif content_type == "text/html" and not body:
351
- charset = part.get_content_charset() or "utf-8"
352
- payload = part.get_payload(decode=True)
353
- html = payload.decode(charset, errors="ignore") if isinstance(payload, bytes) else ""
354
- body = _html_to_text(html, cid_map)
345
+
346
+ if not cid and body_parts:
347
+ body_parts.append(f"\n[attachment://{path}]\n")
348
+
349
+ elif content_type == "text/plain":
350
+ if not body:
351
+ charset = part.get_content_charset() or "utf-8"
352
+ payload = part.get_payload(decode=True)
353
+ body = payload.decode(charset, errors="ignore") if isinstance(payload, bytes) else ""
354
+ body_parts.append(body)
355
+
356
+ elif content_type == "text/html":
357
+ if not body:
358
+ charset = part.get_content_charset() or "utf-8"
359
+ payload = part.get_payload(decode=True)
360
+ html = payload.decode(charset, errors="ignore") if isinstance(payload, bytes) else ""
361
+ body = _html_to_text(html, cid_map)
362
+ body_parts.append(body)
363
+
364
+ if len(body_parts) > 1:
365
+ body = "".join(body_parts)
366
else:
367
content_type = email_msg.get_content_type()
368
charset = email_msg.get_content_charset() or "utf-8"
@@ -394,7 +405,7 @@ def _html_to_text(html_content: str, cid_map: dict[str, str] | None = None) -> s
405
if src.startswith("cid:"):
406
cid = src[4:]
407
if cid in cid_map:
397
- img.replace_with(soup.new_string(f"[file://{cid_map[cid]}]"))
408
+ img.replace_with(soup.new_string(f"[attachment://{cid_map[cid]}]"))
409
html_content = str(soup)
410
411
h = html2text.HTML2Text()