Les deux modes de connexion transportent le même message vers le même compte de relais, et la différence tient à l’endroit où TLS commence, non à ce qui est envoyé. L’un s’ouvre en clair et se met à niveau dans la connexion ; l’autre est chiffré dès le premier octet. Cette seule différence décide de ce qu’une politique de relais stricte acceptera, de ce qu’un équipement intermédiaire peut observer et du message d’erreur que vous passerez un après-midi à lire. Voici comment je choisis entre eux lorsqu’une application d’un réseau privé remet un message à un service d’envoi interne.

Où les deux modes divergent réellement

La tâche est identique dans les deux cas : s’authentifier comme un compte approuvé, désigner un expéditeur autorisé, lister les destinataires et attendre que le relais assume la responsabilité. Ce qui change, c’est l’ordre des opérations. Le port 587 commence comme une session SMTP en clair, annonce STARTTLS dans sa réponse EHLO et ne se met à niveau que si le client le demande. Le port 465 attend une poignée de main TLS avant qu’aucune commande SMTP ne soit prononcée.

L’envoi et le relais sont des métiers différents, et c’est pourquoi les numéros de port ne sont pas des habitudes interchangeables. RFC 6409 décrit l’envoi de messages comme un service à part entière, et RFC 8314 couvre la variante TLS implicite que la plupart des relais modernes exposent sur 465. Si votre relais documente l’une des deux, cette documentation prime sur toute préférence héritée d’un précédent employeur.

Ce que STARTTLS sur 587 vous apporte

La mise à niveau se produit à l’intérieur de la conversation SMTP, si bien que le relais peut l’annoncer et que le client peut refuser de continuer sans elle. En Python, starttls() lève une exception lorsque le serveur ne propose pas l’extension, ce qui transforme une tentative de rétrogradation en échec visible plutôt qu’en connexion silencieuse en clair. Ce comportement est l’argument principal du mode, et il vaut la peine de s’y fier délibérément, pas par accident.

La fenêtre d’exposition est petite mais réelle : l’échange EHLO initial est lisible, et rien avant la mise à niveau ne transporte d’identifiants. Sur un chemin privé entre un sous-réseau applicatif et un relais interne, cette fenêtre est généralement acceptable. Sur un chemin que vous ne contrôlez pas, c’est la raison pour laquelle certaines équipes insistent sur l’autre mode.

Un dispositif d’interception qui supprime la capacité STARTTLS produit un envoi échoué, pas un repli. C’est le résultat souhaité, et c’est aussi le moyen le plus rapide de découvrir que quelque chose réécrit SMTP sur le chemin.

Ce que change le TLS implicite sur 465

Il n’y a aucune phase en clair. Le client effectue une poignée de main TLS dès l’ouverture de la socket, valide le certificat auprès de son magasin de confiance et n’échange EHLO, AUTH et le message qu’ensuite. Rien ne peut rétrograder la session puisqu’il n’existe aucun état en clair à négocier, ce qui élimine toute une classe d’échecs d’interception.

Le compromis est la diagnosticabilité. Un relais qui répond sur 465 en SMTP clair, ou un répartiteur de charge qui termine TLS avec le mauvais nom de certificat, échoue à la poignée de main et vous donne une erreur SSL plutôt qu’une erreur de messagerie. Quand cela arrive, le problème se situe sous l’application, et aucune construction de message ne le corrigera.

Une fonction d’envoi, deux transports

Le code diffère à peine entre les modes, et c’est pourquoi le choix doit se faire d’après la politique du relais plutôt que d’après la commodité de la bibliothèque. Exportez d’abord les paramètres, puis exécutez un script unique qui sélectionne le transport selon le port configuré.

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() vérifie la chaîne et le nom d’hôte, donc une AC privée doit être installée dans le magasin de confiance plutôt que contournée avec un contexte non vérifié. send_message() renvoie un dictionnaire des destinataires que le relais a refusés ; un dictionnaire vide signifie qu’il les a tous acceptés. Le délai de quinze secondes est un point de départ, pas une valeur réglée pour votre réseau.

Gardez le mot de passe hors de l’image et hors des journaux. Un secret monté à l’exécution et lu dans l’environnement convient ; faire écho de l’environnement dans une ligne de journal ne convient pas, et une trace de débogage de l’échange AUTH non plus.

Choisissez selon la politique du relais et le chemin, pas selon l’habitude

Trois conditions tranchent. Premièrement, ce que le relais annonce réellement : un service qui n’écoute que sur 587 avec STARTTLS n’est pas joignable sur 465, quelle que soit votre préférence pour le TLS implicite. Deuxièmement, si le chemin entre l’application et le relais est assez sûr pour accepter un échange EHLO lisible. Troisièmement, si quelque chose au milieu réécrit ou proxifie SMTP, car cela détermine si le risque de rétrogradation est théorique ou réel.

Quand le relais propose les deux, je prends 465 pour un service qui tourne sans surveillance et 587 pour celui où une personne surveille le premier déploiement, simplement parce que les messages d’échec pointent vers des couches différentes. Cette préférence est la mienne et ce n’est pas une politique.

Quoi que vous choisissiez, consignez le port, le matériel de confiance et le domaine d’expéditeur avec le déploiement. Envoyez ensuite un message de test autorisé et confirmez que le relais l’a accepté, en conservant le résultat de l’envoi et tout rapport de livraison ultérieur comme des enregistrements distincts. Une remise réussie au relais marque la fin de la responsabilité de l’application, non la preuve que le message a atteint une boîte de réception.

Articles similaires