Email: add explicit SMTP security mode

This commit is contained in:
mechramc
2026-06-01 23:15:06 -05:00
committed by GitHub
parent ccc0b9ab0c
commit 9d0a18a5b5
6 changed files with 195 additions and 40 deletions
+25 -25
View File
@@ -15,7 +15,6 @@ and `email_pollers.py` (the background loops):
import os
import imaplib
import smtplib
import ssl
import email as email_mod
import email.header
import email.utils
@@ -39,41 +38,37 @@ from src.secret_storage import decrypt as _decrypt
logger = logging.getLogger(__name__)
def _send_smtp_message(cfg: dict, from_addr: str, recipients: list[str], message: str | bytes, timeout: int = 30) -> None:
"""Send through SMTP using the conventional TLS mode for the configured port.
def _smtp_security_mode(cfg: dict) -> str:
raw = str(cfg.get("smtp_security") or "").strip().lower()
if raw in {"ssl", "starttls", "none"}:
return raw
port = int(cfg.get("smtp_port") or 465)
if port == 587:
return "starttls"
return "ssl"
Account settings only store host/port today. Port 465 is implicit TLS
(SMTP_SSL); port 587 is plain SMTP upgraded with STARTTLS. Using SSL
directly against 587 raises the classic "[SSL: WRONG_VERSION_NUMBER]"
error even when credentials are correct.
"""
def _send_smtp_message(cfg: dict, from_addr: str, recipients: list[str], message: str | bytes, timeout: int = 30) -> None:
"""Send through SMTP using the configured transport security mode."""
host = cfg["smtp_host"]
port = int(cfg.get("smtp_port") or 465)
user = cfg.get("smtp_user") or ""
password = cfg.get("smtp_password") or ""
def _send_starttls(starttls_port: int = 587) -> None:
with smtplib.SMTP(host, starttls_port, timeout=timeout) as smtp:
smtp.starttls()
if user and password:
smtp.login(user, password)
smtp.sendmail(from_addr, recipients, message)
security = _smtp_security_mode(cfg)
if port == 587:
_send_starttls(587)
return
try:
if security == "ssl":
with smtplib.SMTP_SSL(host, port, timeout=timeout) as smtp:
if user and password:
smtp.login(user, password)
smtp.sendmail(from_addr, recipients, message)
return
except (TimeoutError, ssl.SSLError) as e:
if port == 465:
logger.warning("SMTP implicit TLS on %s:465 failed (%s); retrying STARTTLS on 587", host, e)
_send_starttls(587)
return
raise
with smtplib.SMTP(host, port, timeout=timeout) as smtp:
if security == "starttls":
smtp.starttls()
if user and password:
smtp.login(user, password)
smtp.sendmail(from_addr, recipients, message)
def _strip_think(text: str) -> str:
@@ -543,6 +538,7 @@ def _get_email_config(account_id: str | None = None, owner: str = "") -> dict:
"account_name": row.name,
"smtp_host": row.smtp_host or "",
"smtp_port": int(row.smtp_port or 465),
"smtp_security": _smtp_security_mode({"smtp_security": getattr(row, "smtp_security", ""), "smtp_port": row.smtp_port}),
"smtp_user": row.smtp_user or "",
"smtp_password": _decrypt(row.smtp_password or ""),
"imap_host": row.imap_host or "",
@@ -569,6 +565,10 @@ def _get_email_config(account_id: str | None = None, owner: str = "") -> dict:
"account_name": "legacy",
"smtp_host": settings.get("smtp_host", os.environ.get("SMTP_HOST", "")),
"smtp_port": int(settings.get("smtp_port", os.environ.get("SMTP_PORT", "465")) or 465),
"smtp_security": _smtp_security_mode({
"smtp_security": settings.get("smtp_security", os.environ.get("SMTP_SECURITY", "")),
"smtp_port": settings.get("smtp_port", os.environ.get("SMTP_PORT", "465")),
}),
"smtp_user": settings.get("smtp_user", os.environ.get("SMTP_USER", "")),
"smtp_password": settings.get("smtp_password", os.environ.get("SMTP_PASSWORD", "")),
"imap_host": settings.get("imap_host", os.environ.get("IMAP_HOST", "")),