CR-011 transport - pin the EHLO name; DNS trouble was stalling every send 5s

smtplib calls getfqdn() on every connect when local_hostname is not given, and
that reverse-DNS lookup blocks ~5s per send whenever DNS is slow or down (found
when the office link dropped today: qa_gate_check's sink saw one mail per ~5s
and its 12s waits timed out). Sends are sequential background tasks, so the
stall compounded across a notification batch - in production a QA transition
with a 3-person group would take 15+ seconds to finish mailing.

socket.gethostname() never touches the network; the EHLO name is now computed
once. Measured against the capture sink: 5.3s -> 0.3s for a two-recipient
batch. Server mail path otherwise untouched.

Item: CR-011 (the send path's transport).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-20 18:05:43 -07:00
parent 8efe624d5d
commit 16afc56c0a

View File

@@ -12,6 +12,7 @@ number and a deep link, not the work-package contents.
"""
import os
import smtplib
import socket
import uuid
import logging
from email.message import EmailMessage
@@ -107,7 +108,13 @@ def send_email(s: dict, to_addr: str, subject: str, body: str) -> None:
port = int(s.get("smtp_port") or 587)
user = s.get("smtp_username") or ""
pw = os.getenv("SMTP_PASSWORD", "")
with smtplib.SMTP(host, port, timeout=15) as srv:
# local_hostname pins the EHLO name. Without it smtplib calls getfqdn() on
# EVERY connect, and that reverse-DNS lookup stalls ~5s per send whenever DNS
# is slow or unreachable - sends are sequential background tasks, so a batch
# of notifications trickled out one per five seconds. gethostname() never
# touches the network. Found 2026-08-20 when the office link dropped.
with smtplib.SMTP(host, port, timeout=15,
local_hostname=(socket.gethostname() or "wp-suite")) as srv:
if s.get("smtp_use_tls", True):
srv.starttls()
if user: