fix(core): support implicit TLS (SMTPS / port 465) for outgoing email - #2510
Merged
wh1te909 merged 1 commit intoJul 23, 2026
Merged
Conversation
send_mail always opened the connection with smtplib.SMTP, which speaks plaintext first and only upgrades via STARTTLS. On port 465 the server expects the TLS handshake immediately, so the connection would hang or fail. Select smtplib.SMTP_SSL when smtp_port == 465 (implicit TLS) and keep smtplib.SMTP + STARTTLS for 587/25. STARTTLS is skipped on the 465 path since the channel is already encrypted.
Member
|
nice one, thanks! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Add support for implicit TLS (SMTPS, port 465) when Tactical sends email through
CoreSettings.send_mail.First of all — thanks for this project, it's been rock solid for us. While wiring up alert emails against an SMTP provider that only exposes port 465 I hit a wall, dug into it, and this is the small fix that made it work.
The problem
send_mailalways opens the connection withsmtplib.SMTP:smtplib.SMTPspeaks plaintext first and only upgrades the channel in-band withSTARTTLS. That's exactly right for ports 587 and 25.Port 465 works differently: it's implicit TLS (a.k.a. SMTPS). The server expects the TLS handshake immediately, the instant the socket opens — before any plaintext SMTP command. So connecting to a 465 endpoint with
smtplib.SMTPsends a plaintextEHLOinto a socket the server is waiting to negotiate TLS on. The result is a hang until the 20s timeout, then a failure — 465 is effectively unusable today, regardless of thesmtp_portvalue the admin sets.Python's stdlib already draws this line clearly:
smtplib.SMTPfor plaintext/STARTTLS, andsmtplib.SMTP_SSLfor implicit TLS.The fix
Pick the connection class from the port — the minimal, config-free change:
smtp_port == 465→ open withsmtplib.SMTP_SSL(...)so the socket is TLS-wrapped from the start. Assl.create_default_context()is passed so the server certificate chain is validated.smtplib.SMTP+STARTTLS, exactly as before.STARTTLSis skipped — the channel is already encrypted, and calling it would raise "TLS already active".No new setting, no migration, no serializer or UI change. The security invariant is preserved and even slightly strengthened: an authenticated
login()never runs over a cleartext channel — it's STARTTLS on 587/25, or an already-encrypted socket on 465.The Gmail relay branch and the no-auth relay branch keep their existing behavior; the no-auth path now also benefits from implicit TLS automatically when the admin points it at a 465 endpoint.
Testing
SMTP_SSLsucceeds (TLS 1.3, cert validated). The same account on 587 keeps working via STARTTLS.python -m py_compileon the module is clean.send_mailunit tests mock the method itself (@patch("core.models.CoreSettings.send_mail")), so they aren't affected by the transport change.Related
Addresses the SSL side of #1621 (SMTP server with STARTTLS or SSL support). STARTTLS was already covered for authenticated hosts; this adds the missing implicit-TLS / 465 path.