From d32746b00f06a849479110798d4e569c5f8829e2 Mon Sep 17 00:00:00 2001 From: claude-dev Date: Fri, 10 Jul 2026 09:37:19 +0000 Subject: [PATCH] fix(email): SMTP-Versand mit 3 Versuchen und 20s-Timeout Die IONOS-Farm (smtp.ionos.de, 10 Backends im DNS-Round-Robin) hatte am 10.07. teils tote/lahme Backends - ein einzelner Sendeversuch schlug dann fehl (Timed out waiting for server ready message). Jeder Retry oeffnet eine neue Verbindung und trifft per Round-Robin ein anderes Backend. Co-Authored-By: Claude Fable 5 --- src/email_utils/sender.py | 48 +++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/src/email_utils/sender.py b/src/email_utils/sender.py index c00abfc..a1ebcbd 100644 --- a/src/email_utils/sender.py +++ b/src/email_utils/sender.py @@ -1,4 +1,5 @@ """Async E-Mail-Versand via SMTP.""" +import asyncio import logging from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart @@ -17,6 +18,12 @@ from config import ( logger = logging.getLogger("osint.email") +# Die IONOS-SMTP-Farm (smtp.ionos.de) besteht aus ~10 Backends im DNS-Round-Robin; +# einzelne Backends koennen tot/ueberlastet sein (beobachtet 2026-07-10). Jeder +# Versuch oeffnet eine neue Verbindung und wuerfelt damit ein neues Backend. +MAIL_VERSUCHE = 3 +MAIL_TIMEOUT_S = 20 + async def send_email(to_email: str, subject: str, html_body: str) -> bool: """Sendet eine HTML-E-Mail. @@ -38,17 +45,30 @@ async def send_email(to_email: str, subject: str, html_body: str) -> bool: msg.attach(MIMEText(text_content, "plain", "utf-8")) msg.attach(MIMEText(html_body, "html", "utf-8")) - try: - await aiosmtplib.send( - msg, - hostname=SMTP_HOST, - port=SMTP_PORT, - username=SMTP_USER if SMTP_USER else None, - password=SMTP_PASSWORD if SMTP_PASSWORD else None, - start_tls=SMTP_USE_TLS, - ) - logger.info(f"E-Mail gesendet an {to_email}: {subject}") - return True - except Exception as e: - logger.error(f"E-Mail-Versand fehlgeschlagen an {to_email}: {e}") - return False + letzter_fehler = None + for versuch in range(1, MAIL_VERSUCHE + 1): + try: + await aiosmtplib.send( + msg, + hostname=SMTP_HOST, + port=SMTP_PORT, + username=SMTP_USER if SMTP_USER else None, + password=SMTP_PASSWORD if SMTP_PASSWORD else None, + start_tls=SMTP_USE_TLS, + timeout=MAIL_TIMEOUT_S, + ) + logger.info(f"E-Mail gesendet an {to_email}: {subject}") + return True + except Exception as e: + letzter_fehler = e + logger.warning( + f"E-Mail-Versand Versuch {versuch}/{MAIL_VERSUCHE} an {to_email} fehlgeschlagen: {e}" + ) + if versuch < MAIL_VERSUCHE: + await asyncio.sleep(1.5) + + logger.error( + f"E-Mail-Versand endgueltig fehlgeschlagen an {to_email} " + f"nach {MAIL_VERSUCHE} Versuchen: {letzter_fehler}" + ) + return False