guide · smtp python3
Comment une équipe produit doit-elle implémenter SMTP en Python 3 en toute sécurité ?
Implémentez SMTP en Python 3 derrière un worker serveur autorisé, et non dans du code côté navigateur ou contrôlé par l’utilisateur. Construisez les messages avec EmailMessage, gardez les destinataires d’enveloppe séparés des en-têtes visibles, créez un contexte SSL vérifié, fixez des timeouts de connexion finis et utilisez soit SMTP_SSL pour un TLS dès l’ouverture de la connexion, soit SMTP.starttls() suivi d’EHLO pour une mise à niveau explicite. Chargez les identifiants depuis un gestionnaire de secrets, appelez send_message(), examinez les destinataires refusés et enregistrez le résultat exact de la tentative. Ne réessayez que les échecs transitoires, avec un backoff borné, et ne considérez jamais l’acceptation SMTP comme une preuve d’arrivée en boîte de réception.
Définissez une opération e-mail autorisée unique
Partez d’un événement produit approuvé, comme la vérification d’un compte, un reçu, une alerte demandée ou une notification de sécurité. Enregistrez une tâche sortante durable avant d’ouvrir une connexion SMTP. Cette tâche doit contenir une clé d’événement métier stable, le tenant, la catégorie de message, la révision du modèle, l’expéditeur et les destinataires d’enveloppe approuvés, l’identité From visible, la base de consentement ou de nécessité et le résultat actuel de la vérification de suppression. Le navigateur, l’application mobile, le modèle et les saisies utilisateur ne doivent pas pouvoir choisir les hôtes SMTP, les identifiants, les expéditeurs d’enveloppe, des en-têtes arbitraires ou des destinataires non restreints. Autorisez l’appelant et le tenant, validez les adresses, bornez le nombre de destinataires et de pièces jointes et empêchez l’injection de sauts de ligne. Prenez en charge une tâche une seule fois et conservez un historique des tentatives en ajout seul. smtplib de Python est un client de protocole : il ne fournit ni idempotence métier, ni isolation des tenants, ni consentement, ni suppression, ni file d’attente durable. Ces contrôles relèvent de l’application qui l’entoure.
Construisez des messages structurés avec EmailMessage
Utilisez email.message.EmailMessage plutôt que de concaténer des chaînes d’en-têtes et de MIME. Renseignez From, To, Subject et un en-tête de corrélation applicatif stable à partir de valeurs validées, puis appelez set_content pour le texte brut, add_alternative pour une partie HTML si nécessaire, et add_attachment uniquement pour les types et tailles de fichiers explicitement pris en charge. Générez le texte et le HTML à partir de la même révision de modèle approuvée. Échappez les valeurs non fiables selon leur contexte de sortie et évitez d’afficher du HTML utilisateur brut. Ne placez pas de secrets, de jetons d’accès, de données personnelles superflues ni de clés de base de données internes dans les en-têtes, les objets, les champs de suivi ou les noms de pièces jointes. Le paquet email sérialise selon sa politique et peut générer des délimiteurs MIME lors de l’aplatissement : signez ou hachez donc la représentation sérialisée finale si des contrôles d’intégrité ultérieurs dépendent des octets exacts. Gardez l’enveloppe SMTP séparée : les en-têtes To et Cc visibles s’adressent aux lecteurs, tandis que la liste des destinataires de transport détermine les commandes RCPT TO.
Choisissez délibérément entre TLS implicite et STARTTLS
Utilisez SMTP_SSL lorsque le serveur exige TLS dès le début de la connexion. N’utilisez SMTP pour une connexion en clair que lorsque le workflow documenté du serveur impose une mise à niveau STARTTLS immédiate. La documentation de smtplib de Python indique que starttls place les commandes SMTP suivantes à l’intérieur de TLS et que le client doit ensuite rappeler ehlo. Ne vous authentifiez jamais avant la mise à niveau TLS requise. Créez le contexte avec ssl.create_default_context pour que la validation des certificats et la vérification du nom d’hôte utilisent des valeurs par défaut sûres côté client, et transmettez le nom d’hôte attendu du serveur via la connexion normale de la bibliothèque. Considérez l’absence de prise en charge de STARTTLS, l’échec de certificat, un nom d’hôte non concordant ou l’échec de négociation TLS comme un arrêt net lorsque le chiffrement est exigé. Ne désactivez pas la vérification et ne la remplacez pas par un contexte non vérifié pour faire fonctionner la production. Le TLS de saut protège la connexion SMTP, pas le contenu stocké des messages, le traitement chez le fournisseur, le stockage chez le destinataire ni la boîte aux lettres finale.
Gardez les identifiants côté serveur et à portée limitée
Chargez le nom d’utilisateur, le mot de passe ou le jeton SMTP à l’exécution depuis un service de gestion de secrets. Ne le placez jamais dans le contrôle de version, les couches Docker, une configuration commitée dans Git, les URL, les arguments de ligne de commande, la sortie de débogage, les outils d’analyse, les rapports d’exception, les snapshots de test, les notebooks, les tickets ou les prompts. Préférez un identifiant limité à un environnement, un domaine d’expéditeur ou une charge autorisée plutôt qu’un secret d’administration à l’échelle du compte. Séparez la production du développement et de la CI. Faites de la rotation une routine : provisionnez un remplaçant, mettez à jour le worker, effectuez un test de livraison contrôlé, confirmez les preuves d’authentification et de résultat, puis révoquez l’ancien identifiant. Limitez l’accès au secret au processus d’envoi et auditez les lectures administratives. La méthode login de Python essaie les mécanismes d’authentification annoncés par le serveur ; l’application doit néanmoins décider si le serveur, la sécurité de la connexion, le compte et le mécanisme sont acceptables. Des échecs d’authentification répétés doivent mettre la cohorte en pause et déclencher une investigation plutôt que des tentatives rapides de mot de passe.
Utilisez des timeouts explicites et une durée de vie de connexion bornée
Passez un timeout fini à SMTP ou SMTP_SSL pour que la connexion et les opérations bloquantes ne puissent pas occuper un worker indéfiniment. Appliquez une échéance globale à la tâche et une politique d’annulation, car un timeout de socket ne suffit pas à contrôler l’âge dans la file. Ne partagez pas un objet SMTP entre des tâches concurrentes, sauf si l’accès est sérialisé et que la sûreté de son état est démontrée. Une conception simple ouvre une connexion pour un lot borné, salue le serveur, établit TLS si nécessaire, s’authentifie, soumet un petit nombre de messages, appelle quit et abandonne la connexion après une erreur ou une limite d’âge. La réutilisation peut réduire la surcharge, mais elle accroît l’ambiguïté après une déconnexion du serveur, un timeout ou un état partiel. Bornez le nombre de messages par connexion et reconnectez-vous délibérément. Surveillez la latence de connexion, la négociation TLS, l’authentification, la latence des commandes, les déconnexions du serveur et l’âge des tâches, sans journaliser les identifiants ni le contenu des messages. Le serveur SMTP peut imposer des limites qui évoluent indépendamment de Python.
Envoyez un message et conservez les résultats par destinataire
SMTP.sendmail utilise from_addr et to_addrs pour l’enveloppe de transport et ne réécrit pas les en-têtes du message. SMTP.send_message sérialise un EmailMessage et en déduit des valeurs par défaut, sauf si des valeurs d’enveloppe explicites sont fournies. Dans le code de production, transmettez explicitement l’expéditeur d’enveloppe et la liste de destinataires approuvés, pour que la gestion du Bcc et l’autorisation du tenant restent sans ambiguïté. Python indique que sendmail se termine normalement lorsqu’au moins un destinataire a été accepté et renvoie un dictionnaire contenant chaque destinataire refusé. L’absence d’exception n’équivaut donc pas à un succès pour tous les destinataires. Stockez séparément les ensembles de destinataires acceptés et refusés, avec le code de statut et un diagnostic assaini. Ne réessayez pas les destinataires acceptés lorsque seuls certains ont été refusés. Traitez chaque destinataire comme un résultat autorisé indépendamment, tout en conservant la tentative de message commune. Une exception ultérieure à l’étape DATA diffère d’un refus RCPT et nécessite sa propre classification.
Classez les exceptions par étape et par caractère définitif
Gérez explicitement les exceptions de smtplib et conservez leurs codes SMTP et les messages assainis du serveur. SMTPConnectError et un timeout peuvent être transitoires, mais ils peuvent aussi révéler un mauvais hôte, un mauvais port, un pare-feu ou une panne. Un SMTPNotSupportedError après STARTTLS ou SMTPUTF8 doit arrêter une configuration qui exige cette fonctionnalité. SMTPAuthenticationError exige d’examiner les identifiants, le compte, le mécanisme et TLS, pas de réessayer à l’aveugle. SMTPSenderRefused et SMTPRecipientsRefused exigent des décisions au niveau de l’identité ou du destinataire. SMTPDataError décrit une réponse DATA inattendue et peut, selon son statut étendu, refléter un problème de contenu, de politique, de quota ou un comportement temporaire du destinataire. Classez les réponses 4xx comme candidates à une nouvelle tentative bornée et les 5xx comme définitives pour cette tentative, tout en respectant la documentation propre au fournisseur. Utilisez un backoff exponentiel, du jitter, des plafonds de tentatives et d’âge dans la file, ainsi qu’un état de lettre morte. Ne réessayez jamais après une suppression, une plainte, une désinscription, une autorisation révoquée ou une preuve de destinataire invalide.
Rapprochez les résultats de soumission ambigus
Un timeout réseau ou une déconnexion survenant après que le client a transmis les données du message, mais avant qu’il ait observé la réponse finale du serveur, est ambigu. Le serveur a pu accepter la prise en charge même si Python a levé une exception. Ne créez pas immédiatement un nouvel envoi logique. Marquez la tentative comme inconnue, conservez ses identifiants d’événement et de trace stables, et interrogez les logs du fournisseur ou les événements de livraison ultérieurs lorsqu’ils sont disponibles. Si le service SMTP n’offre ni idempotence ni corrélation consultable, définissez une décision produit fondée sur la catégorie de message, l’ancienneté, le préjudice d’un doublon et l’expérience utilisateur. Les alertes de sécurité et les e-mails de réinitialisation de mot de passe ne présentent pas les mêmes risques de doublon que les reçus ou les avis financiers. Conservez dans le registre la tentative d’origine et tout lien de nouvelle tentative. Ne prétendez jamais à une livraison exactement une fois, car SMTP ne l’assure pas de bout en bout. Testez cette branche avec un serveur de test contrôlé qui coupe la connexion à chaque étape du protocole, y compris avant et après l’acceptation de DATA.
Distinguez l’acceptation SMTP de la livraison et de l’engagement
Un appel send_message réussi signifie qu’au moins un destinataire a été accepté à l’étape SMTP observée, selon la sémantique documentée de Python. Il ne prouve pas que chaque destinataire a été accepté, que le serveur de destination a ensuite conservé le message, que le message est arrivé dans un dossier de boîte de réception ni qu’une personne l’a lu. Modélisez comme des preuves distinctes la soumission au fournisseur, l’acceptation par le serveur destinataire, l’échec temporaire ou définitif, le bounce ultérieur, la plainte, la désinscription, le placement en boîte aux lettres et l’engagement. Ingérez les événements authentifiés du fournisseur lorsqu’ils sont disponibles, dédupliquez-les et conservez l’heure de survenue séparément de l’heure de traitement. Appliquez les bounces définitifs, les plaintes et les désinscriptions juste avant les envois suivants. Les ouvertures et les clics ne prouvent pas le transport et peuvent être affectés par les technologies de protection de la vie privée. Conservez des métriques agrégées minimisées pour la vie privée par cohorte compatible avec l’isolation entre tenants, révision de modèle, domaine d’expéditeur, classe de statut et période. Déclenchez des alertes sur les pics de refus, les résultats inconnus, l’âge de la file, les échecs TLS, les échecs d’authentification et une diffusion inhabituelle vers de nombreux destinataires.
Testez en local sans envoyer de vrais e-mails clients
Testez unitairement la construction des messages, le rejet de l’injection d’en-têtes, l’autorisation des destinataires, la suppression de Bcc, les alternatives texte brut et HTML, la gestion d’Unicode, les limites des pièces jointes et les vérifications de suppression. Utilisez un serveur de test SMTP local contrôlé ou une fixture de protocole pour simuler les échecs de salutation, l’absence de STARTTLS, l’échec de certificat, les erreurs d’authentification, l’acceptation partielle de RCPT, les réponses DATA 4xx et 5xx, les déconnexions et les réponses retardées. N’utilisez pas de services de débogage non authentifiés et dépréciés pour des secrets semblables à ceux de production ou du contenu client. Les tests d’intégration doivent utiliser des comptes dédiés et des destinataires contrôlés, avec des quotas et un nettoyage explicites. Vérifiez le message brut reçu, les résultats d’authentification, les en-têtes visibles, le comportement de réponse et la corrélation des événements. Exécutez une recherche de secrets dans les fixtures et les logs.
Comment SendHQ s’intègre
SendHQ documente une API e-mail limitée à l’espace de travail pour l’envoi depuis un domaine vérifié, les événements de livraison et les suppressions. Ce guide couvre le client SMTP de la bibliothèque standard de Python ; utilisez la documentation de SendHQ pour connaître ses méthodes d’intégration et son contrat d’API actuels.
Questions fréquentes
Faut-il placer les identifiants SMTP Python dans le code client ?
Non. Gardez-les dans un gestionnaire de secrets côté serveur, avec une portée étroite par environnement et par charge, des accès audités, une rotation régulière et aucune journalisation.
Quand Python doit-il utiliser SMTP_SSL ?
Utilisez SMTP_SSL lorsque TLS est exigé dès l’ouverture de la connexion. N’utilisez SMTP avec starttls que pour un workflow documenté de mise à niveau explicite qui échoue de manière sûre.
Faut-il rappeler EHLO après starttls ?
Oui. La documentation de smtplib de Python indique d’appeler à nouveau ehlo après starttls afin que les capacités soient redécouvertes à l’intérieur de la connexion protégée.
send_message signifie-t-il que tous les destinataires ont été acceptés ?
Non. Python peut se terminer normalement lorsqu’au moins un destinataire a été accepté et renvoie séparément les destinataires refusés. Enregistrez et traitez indépendamment le résultat de chaque destinataire.
Que faire après un SMTPAuthenticationError ?
Mettez en pause la configuration concernée et examinez TLS, le serveur, le compte, le secret et les mécanismes annoncés. Réessayer les identifiants à l’aveugle peut aggraver les verrouillages ou les signaux de compromission.
Faut-il réessayer chaque SMTPDataError ?
Non. Conservez le statut exact et le diagnostic, puis distinguez les conditions temporaires 4xx des échecs définitifs 5xx liés à la politique, au contenu, au quota ou à la configuration.
L’acceptation SMTP détermine-t-elle l’arrivée en boîte de réception ?
Non. C’est une preuve de transport au périmètre limité. Les relais ultérieurs, le filtrage du destinataire, les bounces, les règles de boîte aux lettres, le dossier de destination et l’engagement humain restent des résultats distincts.
Ce guide couvre-t-il l’intégration spécifique à SendHQ ?
Non. Il couvre le client SMTP de la bibliothèque standard de Python. Consultez la documentation de SendHQ pour connaître ses méthodes d’intégration et son contrat d’API actuels.
Sources
- Documentation de smtplib pour Python 3 — Python Software Foundation
- Documentation d’EmailMessage pour Python 3 — Python Software Foundation
- Documentation de ssl pour Python 3 — Python Software Foundation
- RFC 5321 : Simple Mail Transfer Protocol — RFC Editor
- RFC 3207 : SMTP Service Extension for Secure SMTP over TLS — RFC Editor
- RFC 4954 : SMTP Service Extension for Authentication — RFC Editor