Skip to content

fix(email): use implicit TLS for port 465 - #46084

Merged
teknium1 merged 2 commits into
mainfrom
hermes/hermes-018096a8
Jun 14, 2026
Merged

fix(email): use implicit TLS for port 465#46084
teknium1 merged 2 commits into
mainfrom
hermes/hermes-018096a8

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Email gateway SMTP connections now use the correct TLS mode for port 465 and a thread-safe IPv4 fallback for broken IPv6 routes.

Cherry-picks #46061 from @liuhao1024, then fixes the fallback path so it uses supported smtplib extension points instead of unsupported sock= kwargs. Also adds security-focused regressions: certificate/TLS verification errors are not retried through fallback, and the IPv4 path does not monkeypatch process-global socket resolution.

Changes

  • gateway/platforms/email.py: add _connect_smtp() with SMTP_SSL for port 465 and SMTP + STARTTLS elsewhere.
  • gateway/platforms/email.py: add IPv4-only SMTP subclasses that override _get_socket() instead of passing unsupported sock= kwargs.
  • gateway/platforms/email.py: close partially-open SMTP sessions if STARTTLS fails.
  • tests/gateway/test_email.py: cover 465/587 protocol selection, IPv4 fallback, no TLS-error retry, and no global resolver mutation.

Security notes

  • TLS verification still uses ssl.create_default_context().
  • Certificate / TLS verification failures are treated as security failures and are not retried via IPv4.
  • The IPv4 fallback is per-connection and thread-safe; it does not patch socket.getaddrinfo globally.

Validation

Check Result
scripts/run_tests.sh tests/gateway/test_email.py 68 passed
python -m py_compile gateway/platforms/email.py tests/gateway/test_email.py pass
git diff --check pass
live Python signature smoke verified smtplib.SMTP/SMTP_SSL have no sock= kwarg; fallback uses subclasses

Infographic

SMTP TLS Pathfinder

Original PR: #46061
Related duplicate: #46019
Fixes: #46018

liuhao1024 and others added 2 commits June 14, 2026 03:09
Port 465 expects implicit TLS (SMTP_SSL) from the first byte. The email
adapter always used SMTP() + starttls(), which is correct for port 587
but hangs/fails on port 465 providers (e.g., Swiss ISPs).

Additionally, when the SMTP host has AAAA DNS records but IPv6 is
unreachable, socket.create_connection() tries IPv6 first and hangs
until timeout. Add an IPv4 fallback via AF_INET socket.

Extract _connect_smtp() helper to consolidate the 4 duplicate SMTP
connection sites into a single method with correct protocol selection
and IPv6 fallback logic.
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-018096a8 vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 10893 on HEAD, 10893 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 5725 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@liuhao1024

Copy link
Copy Markdown
Contributor

Verification: clean review — email adapter implicit TLS + IPv4 fallback

Reviewed the full diff. Two changes in this PR, both well-implemented:

1. Port 465 implicit TLS (SMTP_SSL)

  • _connect_smtp() correctly selects smtplib.SMTP_SSL for port 465 and smtplib.SMTP + STARTTLS for all other ports. This fixes a real bug where port 465 connections silently failed because STARTTLS was used on an already-TLS port.
  • All 4 send paths (connect test, _send_email, _send_email_with_attachments, _send_email_with_attachment) now use the unified _connect_smtp() helper. No path left behind.

2. IPv4 fallback for unreachable IPv6

  • _create_ipv4_connection() uses socket.getaddrinfo with AF_INET — does NOT mutate process-global socket.getaddrinfo (confirmed by test test_ipv4_connection_does_not_mutate_global_resolver).
  • The retry catches (socket.timeout, TimeoutError, ConnectionError, OSError) but re-raises ssl.SSLError — correct, since TLS verification failures are security errors, not connectivity issues.
  • _IPv4SMTP / _IPv4SMTP_SSL override _get_socket() to use the IPv4-only connection. Clean subclass pattern.

Resource management: smtp.quit() is now in a try/finally block in the connect() test path (was previously not guarded). _send_email and variants already had finally: smtp.close().

No findings. Tests cover all 4 connection variants (587/465 × default/IPv4-fallback) plus the TLS-error-doesn't-retry guard.

@teknium1
teknium1 merged commit cf7d593 into main Jun 14, 2026
28 checks passed
@teknium1
teknium1 deleted the hermes/hermes-018096a8 branch June 14, 2026 11:16
@alt-glitch alt-glitch added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery platform/email Email (IMAP/SMTP) adapter P2 Medium — degraded but workaround exists labels Jun 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists platform/email Email (IMAP/SMTP) adapter type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: Email adapter fails on port 465 (SMTP_SSL) and hangs on IPv6

3 participants