Envio SMTP com Python: STARTTLS na 587 ou TLS na 465?
STARTTLS na 587 e TLS implícito na 465 transportam ambos um envio de Python, mas só um corresponde ao seu relay. Compare o handshake e os modos de falha.
Os dois modos de conexão transportam a mesma mensagem para a mesma conta de relay, e a diferença está em onde o TLS começa, não no que é enviado. Um abre em texto claro e é atualizado dentro da conexão; o outro é cifrado desde o primeiro byte. Essa única diferença decide o que uma política de relay estrita aceitará, o que um dispositivo intermediário pode observar e qual mensagem de erro você passará uma tarde lendo. É assim que escolho entre eles quando uma aplicação numa rede privada entrega uma mensagem a um serviço de envio interno.
Onde os dois modos realmente divergem
A tarefa é idêntica nos dois casos: autenticar-se como uma conta aprovada, indicar um remetente permitido, listar os destinatários e esperar que o relay assuma a responsabilidade. O que muda é a ordem das operações. A porta 587 começa como uma sessão SMTP em texto claro, anuncia STARTTLS na resposta EHLO e só é atualizada se o cliente o pedir. A porta 465 espera um handshake TLS antes de qualquer comando SMTP ser pronunciado.
Envio e relay são trabalhos diferentes, por isso os números de porta não são hábitos intercambiáveis. RFC 6409 descreve o envio de mensagens como um serviço próprio, e a RFC 8314 cobre a variante de TLS implícito que a maioria dos relays modernos expõe na 465. Se o seu relay documenta uma das duas, essa documentação prevalece sobre qualquer preferência que tenha trazido de um empregador anterior.
O que o STARTTLS na 587 lhe dá
A atualização acontece dentro da conversa SMTP, portanto o relay pode anunciá-la e o cliente pode recusar-se a continuar sem ela. Em Python, starttls() lança uma exceção quando o servidor não oferece a extensão, o que transforma uma tentativa de degradação numa falha visível em vez de um login silencioso em texto claro. Esse comportamento é o argumento principal do modo, e vale a pena depender dele deliberadamente e não por acaso.
A janela de exposição é pequena mas real: a troca EHLO inicial é legível, e nada antes da atualização transporta credenciais. Num caminho privado entre uma sub-rede de aplicação e um relay interno, essa janela é normalmente aceitável. Num caminho que não controla, é a razão pela qual algumas equipas insistem no outro modo.
Um dispositivo de interceção que remove a capacidade STARTTLS produz um envio falhado, não um retrocesso. Esse é o resultado que quer, e é também a forma mais rápida de descobrir que algo está a reescrever SMTP no caminho.
O que o TLS implícito na 465 muda
Não há fase de texto claro nenhuma. O cliente realiza um handshake TLS assim que o socket abre, valida o certificado contra o seu armazém de confiança e só então troca EHLO, AUTH e a mensagem. Nada pode degradar a sessão porque não existe um estado de texto claro a partir do qual negociar, e isso elimina toda uma classe de falhas de interceção.
O preço é a diagnosticabilidade. Um relay que responde na 465 em SMTP de texto claro, ou um balanceador de carga que termina o TLS com o nome de certificado errado, falha no handshake e dá-lhe um erro SSL em vez de um erro de correio. Quando isso acontece, o problema está abaixo da aplicação, e nenhuma construção da mensagem o corrige.
Uma função de envio, dois transportes
O código quase não difere entre os modos, por isso a escolha deve ser feita a partir da política do relay e não da conveniência da biblioteca. Exporte primeiro as definições e execute depois um único script que selecione o transporte a partir da porta configurada.
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 cadeia e o nome do anfitrião, pelo que uma AC privada tem de ser instalada no armazém de confiança em vez de contornada com um contexto não verificado. send_message() devolve um dicionário dos destinatários que o relay recusou; um dicionário vazio significa que os aceitou a todos. O tempo limite de quinze segundos é um ponto de partida, não um valor afinado para a sua rede.
Mantenha a palavra-passe fora da imagem e fora dos registos. Um segredo montado em tempo de execução e lido no ambiente está bem; fazer eco do ambiente numa linha de registo não está, nem um rasto de depuração da troca AUTH.
Escolha pela política do relay e pelo caminho, não pelo hábito
Três condições decidem. Primeira, o que o relay anuncia realmente: um serviço que só escuta na 587 com STARTTLS não é alcançável na 465, por muito que prefira o TLS implícito. Segunda, se o caminho entre a aplicação e o relay é suficientemente fidedigno para aceitar uma troca EHLO legível. Terceira, se algo pelo meio reescreve ou faz proxy de SMTP, porque isso decide se o risco de degradação é teórico ou real.
Quando o relay oferece ambos, escolho a 465 para um serviço que corre sem supervisão e a 587 para um em que uma pessoa acompanha a primeira implementação, apenas porque as mensagens de falha apontam para camadas diferentes. Essa preferência é minha e não é uma política.
Escolha o que escolher, registe a porta, o material de confiança e o domínio do remetente juntamente com a implementação. Depois envie uma mensagem de teste autorizada e confirme que o relay a aceitou, guardando o resultado do envio e qualquer relatório de entrega posterior como registos separados. Uma entrega bem-sucedida ao relay é o fim da responsabilidade da aplicação, não a prova de que a mensagem chegou a uma caixa de entrada.