feat(email): send attachments as WhatsApp documents, fix forward sender
- Add /send-document endpoint to WhatsApp bridge (base64 document send) - save_email_as_note() now saves attachment files to disk alongside note - email_digest: extract original sender for Fwd: emails so header shows the real author, not the forwarder; send attachment files after summary - email_forward: send attachment files as documents after text parts - Add extract_original_sender() and save_email_attachment_files() helpers Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -93,6 +93,46 @@ def get_email_attachments(msg) -> list:
|
||||
attachments.append(f"[{part.get_content_type()}]")
|
||||
return attachments
|
||||
|
||||
def save_email_attachment_files(msg, dest_dir: Path) -> list:
|
||||
"""Save attachment files from email to dest_dir. Returns list of saved file paths."""
|
||||
saved = []
|
||||
if not msg.is_multipart():
|
||||
return saved
|
||||
dest_dir.mkdir(parents=True, exist_ok=True)
|
||||
for part in msg.walk():
|
||||
filename = part.get_filename()
|
||||
if not filename:
|
||||
continue
|
||||
filename = decode_mime_header(filename)
|
||||
payload = part.get_payload(decode=True)
|
||||
if payload is None:
|
||||
continue
|
||||
dest = dest_dir / filename
|
||||
# Avoid overwriting — append counter if needed
|
||||
counter = 1
|
||||
while dest.exists():
|
||||
stem, suffix = Path(filename).stem, Path(filename).suffix
|
||||
dest = dest_dir / f"{stem}_{counter}{suffix}"
|
||||
counter += 1
|
||||
dest.write_bytes(payload)
|
||||
saved.append(dest)
|
||||
return saved
|
||||
|
||||
def extract_original_sender(subject: str, body_content: str, from_full: str) -> str:
|
||||
"""If email is a forward, extract original sender from body."""
|
||||
if not re.match(r'^(fwd?|fw)\s*[:\s]', subject, re.IGNORECASE):
|
||||
return from_full
|
||||
match = re.search(
|
||||
r'(?:De la|From):\s*(.+?)(?:\n|$)',
|
||||
body_content, re.IGNORECASE | re.MULTILINE
|
||||
)
|
||||
if match:
|
||||
candidate = match.group(1).strip()
|
||||
# Skip blank or markdown artifacts
|
||||
if candidate and not candidate.startswith('**') and '@' in candidate or len(candidate) > 3:
|
||||
return candidate
|
||||
return from_full
|
||||
|
||||
def extract_sender_email(from_header: str) -> str:
|
||||
"""Extract just the email address from From header"""
|
||||
match = re.search(r'<([^>]+)>', from_header)
|
||||
@@ -204,11 +244,15 @@ def save_email_as_note(eid: str) -> dict:
|
||||
|
||||
KB_PATH.mkdir(parents=True, exist_ok=True)
|
||||
filepath.write_text(content, encoding='utf-8')
|
||||
|
||||
|
||||
# Save attachment files next to the note
|
||||
att_dir = KB_PATH / f"{date_prefix}_{slug}_attachments"
|
||||
attachment_paths = save_email_attachment_files(msg, att_dir)
|
||||
|
||||
# Mark as seen
|
||||
mail.store(eid.encode(), '+FLAGS', '\\Seen')
|
||||
mail.logout()
|
||||
|
||||
|
||||
return {
|
||||
'ok': True,
|
||||
'file': str(filepath),
|
||||
@@ -216,6 +260,7 @@ def save_email_as_note(eid: str) -> dict:
|
||||
'from': sender_email,
|
||||
'from_full': from_addr,
|
||||
'date': date_str,
|
||||
'attachment_paths': [str(p) for p in attachment_paths],
|
||||
}
|
||||
|
||||
def save_unread_emails():
|
||||
|
||||
Reference in New Issue
Block a user