Envío SMTP con Python: STARTTLS no 587 ou TLS no 465?
STARTTLS no 587 e TLS implícito no 465 transportan ambos un envío de Python, pero só un encaixa co teu relay. Compara o handshake e os modos de fallo.
Ambos modos de conexión levan a mesma mensaxe á mesma conta de relay, e a diferenza está en onde comeza TLS, non en que se envía. Un ábrese en texto claro e actualízase dentro da conexión; o outro está cifrado desde o primeiro byte. Esa única diferenza decide que aceptará unha política de relay estrita, que pode observar un dispositivo intermedio e que mensaxe de erro pasarás unha tarde lendo. Así elixo entre eles cando unha aplicación nunha rede privada entrega unha mensaxe a un servizo de envío interno.
Onde diverxen realmente os dous modos
A tarefa é idéntica en ambos casos: autenticarse como unha conta aprobada, indicar un remitente permitido, listar os destinatarios e esperar a que o relay asuma a responsabilidade. O que cambia é a orde das operacións. O porto 587 comeza como unha sesión SMTP en texto claro, anuncia STARTTLS na súa resposta EHLO e só se actualiza se o cliente o pide. O porto 465 espera un handshake TLS antes de que se pronuncie calquera comando SMTP.
O envío e o relay son traballos distintos, por iso os números de porto non son hábitos intercambiables. RFC 6409 describe o envío de mensaxes como un servizo propio, e RFC 8314 cobre a variante de TLS implícito que a maioría dos relays modernos expoñen no 465. Se o teu relay documenta unha das dúas, esa documentación prevalece sobre calquera preferencia que adquirises nun empregador anterior.
Que che dá STARTTLS no 587
A actualización ocorre dentro da conversa SMTP, así que o relay pode anunciala e o cliente pode negarse a continuar sen ela. En Python, starttls() lanza unha excepción cando o servidor non ofrece a extensión, o que converte un intento de degradación nun fallo visible en lugar dun inicio de sesión silencioso en texto claro. Ese comportamento é o argumento principal do modo, e paga a pena depender del a propósito e non por accidente.
A ventá de exposición é pequena pero real: o intercambio EHLO inicial é lexible, e nada antes da actualización leva credenciais. Nunha ruta privada entre unha subrede de aplicacións e un relay interno, esa ventá adoita ser aceptable. Nunha ruta que non controlas, é a razón pola que algúns equipos insisten no outro modo.
Un dispositivo de intercepción que elimina a capacidade STARTTLS produce un envío fallido, non un retroceso. Ese é o resultado que queres, e tamén é a forma máis rápida de descubrir que algo está a reescribir SMTP na ruta.
Que cambia o TLS implícito no 465
Non hai fase de texto claro en absoluto. O cliente realiza un handshake TLS en canto se abre o socket, valida o certificado contra o seu almacén de confianza e só entón intercambia EHLO, AUTH e a mensaxe. Nada pode degradar a sesión porque non hai un estado de texto claro desde o que negociar, o que elimina toda unha clase de fallos de intercepción.
O prezo é a diagnosticabilidade. Un relay que responde no 465 con SMTP en texto claro, ou un balanceador de carga que termina TLS co nome de certificado equivocado, falla no handshake e dache un erro SSL en lugar dun erro de correo. Cando iso ocorre, o problema está por debaixo da aplicación, e ningunha cantidade de construción da mensaxe o arranxará.
Unha función de envío, dous transportes
O código apenas difire entre os modos, por iso a elección debería facerse desde a política do relay e non desde a comodidade da biblioteca. Exporta primeiro a configuración e logo executa un único script que seleccione o transporte segundo o porto configurado.
export SMTP_HOST=smtp.relay.internal export SMTP_PORT=587 export SMTP_USER=svc-notify export [email protected] export [email protected] export SMTP_PASSWORD="$(cat /run/secrets/smtp_password)"
import os, smtplib, ssl
from email.message import EmailMessage
ctx = ssl.create_default_context()
msg = EmailMessage()
msg["From"] = os.environ["MAIL_FROM"]
msg["To"] = os.environ["MAIL_TEST_TO"]
msg["Subject"] = "relay submission test"
msg.set_content("One authorized test message from the app.")
host = os.environ["SMTP_HOST"]
port = int(os.environ.get("SMTP_PORT", "587"))
if port == 465:
client = smtplib.SMTP_SSL(host, port, context=ctx, timeout=15)
else:
client = smtplib.SMTP(host, port, timeout=15)
client.ehlo()
client.starttls(context=ctx)
client.ehlo()
with client:
client.login(os.environ["SMTP_USER"], os.environ["SMTP_PASSWORD"])
refused = client.send_message(msg)
print("refused:", refused)
create_default_context() verifica a cadea e o nome de host, así que unha CA privada debe instalarse no almacén de confianza en lugar de omitirse cun contexto non verificado. send_message() devolve un dicionario dos destinatarios que o relay rexeitou; un dicionario baleiro significa que os aceptou a todos. O tempo de espera de quince segundos é un punto de partida, non un valor axustado para a túa rede.
Mantén o contrasinal fóra da imaxe e fóra dos rexistros. Un segredo montado en tempo de execución e lido no ambiente está ben; facer eco do ambiente nunha liña de rexistro non o está, nin tampouco un rastro de depuración do intercambio AUTH.
Elixe por política de relay e ruta, non por costume
Tres condicións o deciden. Primeira, que anuncia realmente o relay: un servizo que só escoita no 587 con STARTTLS non é alcanzable no 465 por moito que prefiras o TLS implícito. Segunda, se a ruta entre a aplicación e o relay é o bastante de confianza como para aceptar un intercambio EHLO lexible. Terceira, se algo intermedio reescribe ou fai proxy de SMTP, porque iso decide se o risco de degradación é teórico ou real.
Cando o relay ofrece ambos, tomo o 465 para un servizo que se executa sen supervisión e o 587 para un no que unha persoa observa o primeiro despregamento, só porque as mensaxes de fallo apuntan a capas distintas. Esa preferencia é miña e non é unha política.
Elixas o que elixas, rexistra o porto, o material de confianza e o dominio do remitente xunto co despregamento. Logo envía unha mensaxe de proba autorizada e confirma que o relay a aceptou, gardando o resultado do envío e calquera informe de entrega posterior como rexistros separados. Unha entrega correcta ao relay é o final da responsabilidade da aplicación, non a proba de que a mensaxe chegou a unha caixa de entrada.