guide · SMTP Python

Comment une équipe produit doit-elle implémenter SMTP avec Python en toute sécurité ?

Implémentez SMTP en Python depuis un worker en arrière-plan autorisé, et non directement depuis une requête web. Construisez le message avec EmailMessage, utilisez SMTP_SSL pour le TLS implicite ou effectuez une mise à niveau explicite avec STARTTLS lorsque le contrat actuel du fournisseur l’exige, authentifiez-vous avec un secret côté serveur et appelez send_message avec des timeouts bornés. Persistez la tâche avant de vous connecter, enregistrez les preuves de refus par destinataire, rapprochez les déconnexions ambiguës et distinguez l’acceptation SMTP de la livraison ultérieure et de l’arrivée en boîte de réception.

Autoriser et persister l’envoi avant SMTP

Partez d’un événement applicatif légitime, comme un reçu, une alerte de sécurité, une vérification demandée ou un avis concernant le compte. Authentifiez l’appelant et autorisez le tenant, la catégorie de message, l’identité From visible, le destinataire et la révision du modèle. Écrivez une tâche sortante persistée avec une clé d’événement métier stable avant d’ouvrir toute connexion SMTP. Cette clé doit empêcher deux workers de créer indépendamment le même message logique. Les saisies du navigateur ne doivent pas choisir l’hôte SMTP, le port, le nom d’utilisateur, l’expéditeur de l’enveloppe, des destinataires arbitraires, les en-têtes ni la politique TLS. Gardez ces valeurs dans une configuration serveur relue. Un worker de file d’attente doit réclamer une tâche, revérifier les suppressions et l’autorisation au moment de l’envoi, enregistrer chaque tentative, puis libérer ou finaliser la tâche via des états explicites. La bibliothèque SMTP de Python transporte le message préparé ; elle ne fournit ni l’autorisation par tenant, ni le consentement, ni l’idempotence, ni la politique de suppression.

Construire le message avec EmailMessage

Utilisez email.message.EmailMessage plutôt que de concaténer des en-têtes et des corps bruts. Définissez From, To, Subject, Date et un Message-ID généré selon le modèle approuvé de l’application, puis utilisez set_content pour le texte et add_alternative pour le HTML si nécessaire. Validez les objets adresse, bornez le nombre de destinataires et de pièces jointes, rejetez l’injection de sauts de ligne dans les valeurs et échappez les données du modèle selon le contexte de sortie. Générez le texte et le HTML à partir d’une même révision immuable du modèle. Tenez les secrets et les données personnelles superflues à l’écart des objets, des en-têtes personnalisés, des noms de fichiers, des champs de diagnostic et des logs. Séparez délibérément l’en-tête From visible de l’expéditeur de l’enveloppe SMTP, car l’authentification et le traitement des bounces peuvent dépendre d’identités différentes. Stockez une révision du contenu ou un hash respectueux de la confidentialité pour l’audit, au lieu de conserver les corps complets des messages sans besoin défini.

Choisir explicitement entre TLS implicite et STARTTLS

Python documente SMTP_SSL pour les connexions chiffrées dès le départ et SMTP.starttls pour la mise à niveau d’une connexion établie. Suivez le nom d’hôte, le port, le certificat et le contrat de soumission actuels du fournisseur au lieu de deviner à partir d’une liste de ports générique. Créez un contexte SSL par défaut avec vérification et ne désactivez pas les contrôles de certificat ni de nom d’hôte. Pour STARTTLS, connectez-vous, envoyez EHLO si nécessaire, appelez starttls avec le contexte, puis renvoyez EHLO, car les extensions annoncées peuvent changer après la mise à niveau. N’envoyez jamais d’identifiants ni de contenu de message client sur une connexion en clair. La RFC 8314 recommande la soumission protégée par TLS et déconseille l’accès en clair. Considérez un échec de certificat, un nom d’hôte non concordant, l’absence du STARTTLS requis ou un changement inattendu des capacités comme des échecs bloquants nécessitant une investigation, plutôt que de revenir silencieusement en arrière.

Garder les identifiants SMTP dans un périmètre de secrets restreint

Chargez le nom d’utilisateur et le mot de passe ou le jeton à l’exécution depuis un service de secrets géré côté serveur. Ne placez pas d’identifiants dans le code source, les bundles client, les dumps d’environnement, les URL, les traces d’exception, les outils d’analyse, les notebooks, les captures d’écran, les prompts ou les fixtures versionnées. Limitez chaque identifiant au plus petit environnement et à la plus petite charge de travail que le fournisseur permet, et séparez le développement de la production. Ne vous authentifiez qu’une fois l’état TLS requis établi. Éprouvez la rotation avec des destinataires contrôlés : provisionnez le remplaçant via l’administration approuvée, mettez à jour le worker, confirmez l’authentification et un cycle d’événements complet, puis révoquez l’ancienne valeur. Des échecs d’authentification répétés doivent suspendre la route concernée plutôt que déclencher une boucle de nouvelles tentatives rapides. La méthode login de Python négocie parmi les mécanismes annoncés par le serveur, mais le mécanisme effectif du fournisseur, la politique du compte, les autorisations du jeton et le comportement de rotation exigent des preuves à jour.

Utiliser une fonction d’envoi Python bornée

Gardez l’adaptateur du fournisseur réduit et renvoyez des preuves structurées à la machine à états des tâches. Un flux typique crée un contexte SSL, ouvre SMTP_SSL(host, port, timeout=10) as smtp pour le TLS implicite, appelle smtp.login(username, secret), puis smtp.send_message(message, from_addr=envelope_from, to_addrs=recipients). Pour un fournisseur qui exige une mise à niveau explicite, utilisez SMTP avec un timeout, ehlo, starttls(context=context), ehlo, puis login. Ne présentez pas des noms d’hôte ou des ports d’exemple comme des valeurs par défaut universelles. Passez une liste de destinataires normalisée plutôt que de vous fier à l’analyse d’en-têtes non fiables. Capturez la classe d’exception, le code de réponse SMTP et un texte de diagnostic borné lorsqu’ils sont disponibles, mais masquez les adresses, les identifiants et le contenu des messages. Mesurez séparément les phases de connexion, TLS, authentification, enveloppe, données et fermeture, afin que les échecs opérationnels restent diagnosticables.

Interpréter précisément les résultats de send_message par destinataire

Python documente que sendmail et send_message se terminent normalement lorsque le message est accepté pour au moins un destinataire, et renvoient un dictionnaire des destinataires refusés ; un dictionnaire vide signifie qu’aucun destinataire n’a été refusé à cette étape. Conservez ce résultat par destinataire au lieu de marquer toute la tâche comme délivrée. Si tous les destinataires sont refusés, la bibliothèque lève une exception SMTPRecipientsRefused. D’autres exceptions distinguent le refus de l’expéditeur, le refus de DATA, l’authentification, la connexion, le protocole et les erreurs associées. Traduisez ces preuves exactes en états applicatifs : accepté par le serveur de soumission, refusé définitivement, refusé temporairement ou inconnu. Un retour normal ne prouve que le résultat de la soumission SMTP concernée. Il n’établit ni l’acceptation par le serveur de destination, ni le placement final dans la boîte aux lettres, ni la lecture, ni l’engagement. Les notifications d’état de livraison ultérieures ou les événements du fournisseur doivent être rapprochés séparément.

Ne réessayer que si le risque de doublon est maîtrisé

Classez les échecs avant de planifier une nouvelle tentative. Les échecs définitifs liés à l’adresse, à l’expéditeur, à l’authentification, à la politique ou au contenu nécessitent généralement une correction ou une suppression plutôt qu’une répétition automatique. Les réponses 4xx transitoires peuvent être réessayées avec un backoff exponentiel, de la gigue, un plafond de tentatives, une expiration et un budget par destination. Une réinitialisation de connexion ou un timeout après l’envoi des données du message peut être ambigu : le serveur a pu accepter le message alors que le client n’a pas reçu la réponse finale. Laissez cette tentative à l’état inconnu, examinez l’activité du fournisseur ou les événements ultérieurs au moyen d’une corrélation respectueuse de la confidentialité, et évitez tout renvoi aveugle immédiat. SMTP n’a pas de clé d’idempotence applicative universelle. La clé d’événement métier persistée empêche les tentatives applicatives concurrentes, mais ne peut pas forcer un serveur SMTP distant à dédupliquer deux soumissions acceptées. Faites remonter les résultats ambigus répétés et conservez les preuves exactes ayant servi à la décision.

Gérer les destinataires partiels et les suppressions

Lorsqu’un message a plusieurs destinataires, SMTP peut en accepter certains et en refuser d’autres. Stockez la réponse pour chaque destinataire et ne faites passer à l’état suivant que le sous-ensemble accepté. Ne renvoyez pas toute la liste d’origine simplement parce qu’une adresse a reçu un refus temporaire. Appliquez les suppressions liées aux bounces définitifs, aux plaintes, aux désinscriptions, aux obligations légales, aux tenants et aux administrateurs avant chaque tentative, y compris les nouvelles tentatives. Ne séparez les catégories de messages que selon une politique explicite et documentée ; qualifier un message de transactionnel n’efface ni la protection des destinataires ni les restrictions du fournisseur. Privilégiez les tâches à destinataire unique pour les workflows sensibles lorsque la confidentialité et un état individualisé en justifient le coût. Évitez d’exposer des listes de destinataires via To ou Cc, et n’utilisez jamais le comportement de Bcc comme substitut à l’autorisation. Limitez et masquez le texte de diagnostic, car les réponses SMTP peuvent contenir des adresses de destinataires ou des détails propres au serveur destinataire.

Tester les chemins d’échec avec des systèmes contrôlés

Testez la construction des messages, l’Unicode, les alternatives texte et HTML, les pièces jointes, le rejet d’en-têtes, la normalisation des destinataires, la vérification TLS, l’absence de STARTTLS, les identifiants invalides, le refus de l’expéditeur, le refus d’un ou de tous les destinataires, le refus de DATA, les timeouts avant et après une éventuelle acceptation, les déconnexions, les réponses de limitation de débit, l’expiration des nouvelles tentatives, les workers en double, les changements de suppression et la rotation des secrets. Utilisez un service SMTP de test contrôlé ou un faux serveur local pour des tests unitaires et d’intégration déterministes ; n’acheminez jamais de trafic accidentel des environnements inférieurs vers des adresses de clients. Pour les canaris en production, utilisez des destinataires autorisés et inspectez les en-têtes bruts : From visible, chemin de l’enveloppe, Message-ID, DKIM, SPF, alignement DMARC et preuves du fournisseur. Vérifiez que les logs et les métriques ne laissent fuiter ni identifiants ni corps de message. Bloquez le lancement si le worker peut contourner l’autorisation par tenant, rétrograder TLS, réessayer sans limite, ignorer un refus partiel, ou s’il ne peut pas suspendre la route d’envoi.

Comment SendHQ s’intègre

SendHQ est une API e-mail limitée à l’espace de travail pour les communications produit attendues. Sa documentation couvre l’envoi, les domaines vérifiés, les événements de livraison et les suppressions. Utilisez son API HTTP documentée lorsque vous intégrez SendHQ avec Python.

Questions fréquentes

Python doit-il utiliser SMTP_SSL ou STARTTLS ?

Utilisez le mode exigé par le contrat de soumission actuel du fournisseur. SMTP_SSL chiffre dès l’ouverture de la connexion ; STARTTLS effectue une mise à niveau explicite et exige un TLS vérifié ainsi qu’un nouvel EHLO.

Un retour normal de send_message prouve-t-il la livraison ?

Non. Il signifie qu’au moins un destinataire a été accepté à cette étape de soumission SMTP. L’acceptation par la destination, le placement dans la boîte aux lettres et l’engagement nécessitent des preuves ultérieures et ciblées.

Que signifie le dictionnaire renvoyé par send_message ?

Il associe les destinataires refusés par le serveur SMTP à la réponse correspondante. Un dictionnaire vide signifie qu’aucun n’a été refusé à cette étape, pas que chaque message est arrivé en boîte de réception.

Peut-on réessayer immédiatement après un timeout ?

Pas en toute sécurité s’il s’est produit après une éventuelle soumission. Conservez la tentative comme ambiguë, rapprochez les preuves du fournisseur ou les événements ultérieurs, et ne renvoyez que dans le cadre d’une politique bornée de risque de doublon.

Où stocker le mot de passe SMTP ?

Utilisez un service de secrets géré côté serveur, avec un accès restreint par charge de travail et par environnement, une récupération auditée, une rotation testée et aucune exposition aux clients, aux logs, aux prompts ni aux fixtures.

Faut-il parfois désactiver la vérification des certificats en production ?

Non. Un échec de certificat ou de nom d’hôte révèle une configuration dangereuse ou incorrecte. Arrêtez la route et diagnostiquez le problème au lieu d’affaiblir silencieusement la vérification TLS.

Comment gérer un refus partiel des destinataires ?

Persistez le résultat de chaque destinataire, faites avancer le sous-ensemble accepté et ne réessayez que les refus temporaires admissibles. Ne renvoyez pas le message aux destinataires déjà acceptés avec toute la liste d’origine.

Cette page prouve-t-elle que SendHQ prend en charge SMTP ?

Non. Ce guide couvre SMTP avec Python de manière générale ; utilisez la documentation actuelle de SendHQ pour son API e-mail.

Sources