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.

Artigos relacionados