feat: Weitergeleitete Bank-Mails erkennen und Uebergangenes zeigen

Wer Bank-Mails in ein Sammelpostfach weiterleitet, hat im From je nach
Methode die eigene Adresse stehen. Geprueft werden deshalb auch Reply-To,
Return-Path, Sender und X-Forwarded-For – bei automatischer Weiterleitung
bleibt der Originalabsender dort erhalten.

Ausserdem meldet der Abruf jetzt, welche Mails er uebergangen hat, mit
Absender und Betreff. Beim Einrichten war das sonst Raten: Man sah nur
"3 uebersprungen" und wusste nicht, was in den Filter gehoert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Guido
2026-08-22 20:23:35 +02:00
co-authored by Claude Opus 5
parent 31f550901a
commit f3a08f2937
5 changed files with 84 additions and 5 deletions
+27 -3
View File
@@ -119,6 +119,20 @@ def _text_der_mail(msg) -> tuple[str, str]:
return betreff, ""
def _absenderfeld(msg) -> str:
"""Alles, was den ursprünglichen Absender verraten kann.
Leitet ein Postfach automatisch weiter, bleibt ``From`` meist der
Originalabsender – verlassen kann man sich darauf aber nicht. Gmail und
andere setzen zusätzlich ``X-Forwarded-For``; bei manueller Weiterleitung
steht die eigene Adresse im ``From`` und die ursprüngliche höchstens im
``Reply-To``. Geprüft wird deshalb alles zusammen.
"""
teile = [str(msg[k] or "") for k in
("From", "Reply-To", "Return-Path", "X-Forwarded-For", "X-Original-From", "Sender")]
return " ".join(t for t in teile if t)
def _passender_absender(absender: list[MailSender], von: str, betreff: str) -> MailSender | None:
"""Erste Zuordnung, deren Filter greifen. Ohne Filter passt sie auf alles."""
von_klein, betreff_klein = von.lower(), betreff.lower()
@@ -151,6 +165,9 @@ def hole_neue(db: Session, quelle: MailSource) -> dict:
verbindung = _verbinden(quelle)
neu = uebersprungen = unklar = 0
# Was durchgefallen ist, mit Absender und Betreff – sonst raet man beim
# Einrichten, was in den Filter gehoert.
ohne_zuordnung: list[dict] = []
try:
code, _ = verbindung.select(f'"{quelle.folder}"')
if code != "OK":
@@ -180,10 +197,15 @@ def hole_neue(db: Session, quelle: MailSource) -> dict:
continue
betreff, text = _text_der_mail(msg)
von = str(msg["From"] or "")
von = _absenderfeld(msg)
zuordnung = _passender_absender(absender, von, betreff)
if zuordnung is None:
uebersprungen += 1
if len(ohne_zuordnung) < 5:
ohne_zuordnung.append({
"from": str(msg["From"] or "")[:120],
"subject": betreff[:120],
})
continue
gesendet = None
@@ -228,7 +250,8 @@ def hole_neue(db: Session, quelle: MailSource) -> dict:
verbindung.logout()
except Exception:
pass
return {"neu": neu, "uebersprungen": uebersprungen, "unklar": unklar}
return {"neu": neu, "uebersprungen": uebersprungen, "unklar": unklar,
"ohne_zuordnung": ohne_zuordnung}
def _nachbereiten(verbindung: imaplib.IMAP4, quelle: MailSource, uid: bytes) -> None:
@@ -253,4 +276,5 @@ def abruf_mit_fehlerbehandlung(db: Session, quelle: MailSource) -> dict:
quelle.last_error = str(e)[:500]
quelle.last_poll_at = datetime.now(timezone.utc)
db.commit()
return {"neu": 0, "uebersprungen": 0, "unklar": 0, "fehler": str(e)}
return {"neu": 0, "uebersprungen": 0, "unklar": 0,
"ohne_zuordnung": [], "fehler": str(e)}