Fix: IMAP Sent-Ordner — echten Namen aus LIST-Antwort extrahieren (INBOX.Sent)

This commit is contained in:
rene 2026-04-30 20:03:23 +02:00
parent 31fae63658
commit 4c6dd07c31

View file

@ -10,6 +10,8 @@ from email.utils import formataddr
from datetime import datetime from datetime import datetime
from typing import List, Optional from typing import List, Optional
import logging
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel from pydantic import BaseModel
@ -17,6 +19,7 @@ from auth import require_admin
from database import db from database import db
router = APIRouter() router = APIRouter()
_log = logging.getLogger(__name__)
_SMTP_HOST = os.getenv("SMTP_HOST", "mail.your-server.de") _SMTP_HOST = os.getenv("SMTP_HOST", "mail.your-server.de")
_SMTP_PORT = int(os.getenv("SMTP_PORT", "587")) _SMTP_PORT = int(os.getenv("SMTP_PORT", "587"))
@ -45,29 +48,40 @@ _SENT_CANDIDATES = ["Sent", "Sent Messages", "Sent Items", "INBOX.Sent", "Gesend
def _imap_save_sent(msg_bytes: bytes, account: str): def _imap_save_sent(msg_bytes: bytes, account: str):
acc = _ACCOUNTS.get(account) or _ACCOUNTS["partner"] acc = _ACCOUNTS.get(account) or _ACCOUNTS["partner"]
if not acc["user"] or not acc["pass"]: if not acc["user"] or not acc["pass"]:
_log.warning("IMAP: Account '%s' nicht konfiguriert, überspringe.", account)
return return
try: try:
ctx = ssl.create_default_context() ctx = ssl.create_default_context()
with imaplib.IMAP4_SSL(_IMAP_HOST, _IMAP_PORT, ssl_context=ctx) as imap: with imaplib.IMAP4_SSL(_IMAP_HOST, _IMAP_PORT, ssl_context=ctx) as imap:
imap.login(acc["user"], acc["pass"]) imap.login(acc["user"], acc["pass"])
# Sent-Ordner finden _, raw_folders = imap.list()
available = [f.decode(errors="replace") for f in (raw_folders or [])]
_log.info("IMAP Ordner (%s): %s", account, available)
# Echten Ordnernamen aus LIST-Antwort extrahieren
# Format: '(\Flags) "." INBOX.Sent' → letztes Token
folder = None folder = None
_, folders = imap.list() for line in available:
available = [f.decode() for f in (folders or [])] name = line.rsplit('"." ', 1)[-1].strip().strip('"')
for candidate in _SENT_CANDIDATES: for candidate in _SENT_CANDIDATES:
if any(candidate.lower() in f.lower() for f in available): if candidate.lower() in name.lower():
folder = candidate folder = name
break
if folder:
break break
if not folder: if not folder:
folder = "Sent" # Fallback: anlegen lassen folder = "INBOX.Sent"
imap.append( _log.info("IMAP: speichere in Ordner '%s' (%s)", folder, account)
typ, data = imap.append(
folder, folder,
r"\Seen", r"\Seen",
imaplib.Time2Internaldate(datetime.now().timestamp()), imaplib.Time2Internaldate(datetime.now().timestamp()),
msg_bytes, msg_bytes,
) )
except Exception: _log.info("IMAP append: %s %s", typ, data)
pass # Nicht blockieren wenn IMAP fehlschlägt except Exception as e:
_log.error("IMAP Sent-Speicherung fehlgeschlagen (%s): %s", account, e)
def _build_message(to: str, subject: str, body: str, account: str) -> MIMEMultipart: def _build_message(to: str, subject: str, body: str, account: str) -> MIMEMultipart: