guida · smtp python3
Come dovrebbe implementare SMTP con Python 3 in sicurezza un team di prodotto?
Implementa SMTP con Python 3 dietro un worker server autorizzato, non nel browser né in codice controllato dagli utenti. Costruisci i messaggi con EmailMessage, tieni i destinatari della busta separati dalle intestazioni visibili, crea un contesto SSL verificato, imposta timeout di connessione finiti e usa SMTP_SSL per il TLS dall'inizio della connessione oppure SMTP.starttls() seguito da EHLO per un upgrade esplicito. Carica le credenziali da un secret manager, chiama send_message(), esamina i destinatari rifiutati e salva l'esito esatto del tentativo. Ritenta solo gli errori temporanei con backoff limitato e non considerare mai l'accettazione SMTP come prova dell'arrivo in inbox.
Definisci un'unica operazione email autorizzata
Parti da un evento di prodotto approvato, come la verifica dell'account, una ricevuta, un avviso richiesto o una notifica di sicurezza. Salva un job di invio persistente prima di aprire una connessione SMTP. Il job dovrebbe contenere una chiave stabile dell'evento di business, il tenant, la classe di messaggio, la revisione del template, il mittente e i destinatari di busta approvati, l'identità From visibile, la base di consenso o di necessità e l'esito attuale della soppressione. Browser, app mobili, template e input degli utenti non devono poter scegliere host SMTP, credenziali, mittenti di busta, intestazioni arbitrarie o destinatari senza restrizioni. Autorizza chiamante e tenant, convalida gli indirizzi, limita il numero di destinatari e allegati e impedisci l'injection di caratteri di nuova riga. Prendi in carico un job una sola volta e mantieni una cronologia dei tentativi di sola aggiunta. smtplib di Python è un client di protocollo: non offre idempotenza di business, isolamento dei tenant, consenso, soppressione né una coda persistente. Questi controlli spettano all'applicazione che lo circonda.
Costruisci messaggi strutturati con EmailMessage
Usa email.message.EmailMessage invece di concatenare stringhe di intestazioni e MIME. Imposta From, To, Subject e un'intestazione di correlazione applicativa stabile a partire da valori convalidati, poi chiama set_content per il testo semplice, add_alternative per una parte HTML quando serve e add_attachment solo per tipi e dimensioni di file esplicitamente supportati. Genera sia il testo sia l'HTML dalla stessa revisione approvata del template. Esegui l'escape dei valori non attendibili in base al contesto di output ed evita di renderizzare HTML raw fornito dagli utenti. Non inserire segreti, token di accesso, dati personali non necessari o chiavi interne del database in intestazioni, oggetti, campi di tracciamento o nomi degli allegati. Il pacchetto email serializza secondo la propria policy e può generare i boundary MIME durante l'appiattimento, quindi firma o calcola l'hash della rappresentazione serializzata finale se controlli di integrità successivi dipendono dai byte esatti. Tieni separata la busta SMTP: le intestazioni To e Cc visibili comunicano con chi legge, mentre l'elenco dei destinatari di trasporto controlla i comandi RCPT TO.
Scegli consapevolmente tra TLS implicito e STARTTLS
Usa SMTP_SSL quando il server richiede TLS fin dall'inizio della connessione. Usa SMTP per una connessione in chiaro solo quando il flusso documentato del server richiede un upgrade STARTTLS immediato. La documentazione di smtplib di Python spiega che starttls porta i comandi SMTP successivi all'interno di TLS e che il client dovrebbe poi chiamare di nuovo ehlo. Non autenticarti mai prima dell'upgrade TLS richiesto. Crea il contesto con ssl.create_default_context, così che la convalida dei certificati e il controllo dell'hostname usino impostazioni client sicure, e passa l'hostname previsto del server tramite la normale connessione della libreria. Considera l'assenza di supporto STARTTLS, un errore del certificato, un hostname non corrispondente o un errore di negoziazione TLS come un blocco definitivo quando la crittografia è obbligatoria. Non disattivare la verifica e non sostituirla con un contesto non verificato per far funzionare la produzione. Il TLS a livello di hop protegge la connessione SMTP, non il contenuto archiviato del messaggio, l'elaborazione del provider, l'archiviazione presso il destinatario o la casella finale.
Tieni le credenziali lato server e con ambito limitato
Carica nome utente, password o token SMTP in fase di esecuzione da un servizio gestito di segreti. Non inserirli mai nel controllo di versione, nei layer Docker, in configurazioni committate su Git, negli URL, negli argomenti della riga di comando, nell'output di debug, negli analytics, nei report di eccezione, negli snapshot dei test, nei notebook, nei ticket o nei prompt. Preferisci una credenziale limitata a un ambiente, a un dominio mittente o a un carico di lavoro consentito rispetto a un segreto amministrativo valido per tutto l'account. Separa la produzione dallo sviluppo e dalla CI. Rendi la rotazione un'abitudine: crea una credenziale sostitutiva, aggiorna il worker, esegui un test di consegna controllato, conferma le evidenze di autenticazione e di esito, poi revoca la vecchia credenziale. Limita l'accesso ai segreti al processo di invio e sottoponi ad audit le letture amministrative. Il metodo login di Python prova i meccanismi di autenticazione annunciati dal server; l'applicazione deve comunque decidere se server, sicurezza della connessione, account e meccanismo sono accettabili. Errori di autenticazione ripetuti dovrebbero mettere in pausa la coorte e avviare un'indagine, non generare rapidi tentativi con la password.
Usa timeout espliciti e una durata della connessione limitata
Passa un timeout finito a SMTP o SMTP_SSL, così che la connessione e le operazioni bloccanti non possano occupare un worker all'infinito. Applica una scadenza esterna del job e una policy di annullamento, perché un singolo timeout del socket non è un controllo completo sull'età in coda. Non condividere un oggetto SMTP tra task concorrenti, a meno che l'accesso non sia serializzato e il suo stato si sia dimostrato sicuro. Un design semplice apre una connessione per un batch limitato, saluta il server, stabilisce TLS se richiesto, si autentica, invia un piccolo numero di messaggi, chiama quit e scarta la connessione dopo errori o al raggiungimento dei limiti di età. Il riutilizzo può ridurre l'overhead, ma aumenta l'ambiguità dopo disconnessioni del server, timeout o stati parziali. Limita i messaggi per connessione e riconnettiti in modo deliberato. Monitora latenza di connessione, negoziazione TLS, autenticazione, latenza dei comandi, disconnessioni del server ed età dei job senza registrare credenziali o contenuti dei messaggi. Il server SMTP può imporre limiti che cambiano indipendentemente da Python.
Invia un messaggio e conserva gli esiti per ciascun destinatario
SMTP.sendmail usa from_addr e to_addrs per la busta di trasporto e non riscrive le intestazioni del messaggio. SMTP.send_message serializza un EmailMessage e ricava i valori predefiniti, a meno che non vengano forniti valori di busta espliciti. Nel codice di produzione, passa esplicitamente il mittente di busta e l'elenco dei destinatari approvati, così che la gestione del Bcc e l'autorizzazione del tenant restino inequivocabili. Python documenta che sendmail termina normalmente quando almeno un destinatario è stato accettato e restituisce un dizionario con una voce per ogni destinatario rifiutato. L'assenza di eccezioni quindi non equivale al successo per tutti i destinatari. Memorizza separatamente l'insieme dei destinatari accettati e quello dei rifiutati, con codice di stato e diagnostica ripulita da dati sensibili. Non ritentare i destinatari accettati quando solo alcuni sono stati rifiutati. Tratta ogni destinatario come un esito autorizzato in modo indipendente, pur mantenendo il tentativo di messaggio condiviso. Un'eccezione successiva nella fase DATA è diversa da un rifiuto RCPT e richiede una classificazione propria.
Classifica le eccezioni per fase e permanenza
Gestisci esplicitamente le eccezioni di smtplib e conserva i relativi codici SMTP e i messaggi del server ripuliti da dati sensibili. SMTPConnectError e i timeout possono essere temporanei, ma possono anche rivelare host, porta, firewall errati o un'interruzione. Un SMTPNotSupportedError dopo STARTTLS o SMTPUTF8 dovrebbe bloccare una configurazione che richiede quella funzionalità. SMTPAuthenticationError richiede di indagare su credenziali, account, meccanismo e TLS, non nuovi tentativi alla cieca. SMTPSenderRefused e SMTPRecipientsRefused richiedono decisioni a livello di identità o di destinatario. SMTPDataError descrive una risposta DATA inattesa e può rappresentare un problema di contenuto, policy, quota o un comportamento temporaneo del destinatario, a seconda del codice di stato esteso. Classifica le risposte 4xx come candidate a un numero limitato di nuovi tentativi e le 5xx come permanenti per quel tentativo, rispettando la documentazione specifica del provider. Usa backoff esponenziale, jitter, limiti sul numero di tentativi e sull'età in coda e uno stato di dead letter. Non ritentare mai dopo evidenze di soppressione, segnalazione di spam, disiscrizione, autorizzazione revocata o destinatario non valido.
Riconcilia gli esiti di invio ambigui
Un timeout di rete o una disconnessione avvenuti dopo che il client ha trasmesso i dati del messaggio, ma prima che abbia ricevuto la risposta finale del server, sono ambigui. Il server potrebbe essersi assunto la responsabilità del messaggio anche se Python ha sollevato un'eccezione. Non creare subito un nuovo invio logico. Segna il tentativo come sconosciuto, mantieni i suoi identificatori stabili di evento e di traccia e interroga i log del provider o gli eventi di consegna successivi, se disponibili. Se il servizio SMTP non offre idempotenza né una correlazione consultabile, definisci una decisione di prodotto basata su classe di messaggio, età, danno da duplicato ed esperienza utente. Gli avvisi di sicurezza e i messaggi di reimpostazione della password hanno rischi di duplicazione diversi da ricevute o comunicazioni finanziarie. Conserva nel registro il tentativo originale e l'eventuale collegamento al nuovo tentativo. Non affermare mai una consegna exactly-once, perché SMTP non la garantisce end-to-end. Testa questo ramo con una fixture di server controllata che interrompe la connessione in ogni fase del protocollo, anche prima e dopo l'accettazione del DATA.
Separa l'accettazione SMTP dalla consegna e dall'engagement
Una chiamata send_message riuscita significa che almeno un destinatario è stato accettato nella fase SMTP osservata, secondo la semantica documentata da Python. Non dimostra che ogni destinatario sia stato accettato, che il server di destinazione abbia poi conservato il messaggio, che il messaggio sia arrivato nella cartella inbox o che una persona l'abbia letto. Modella come evidenze separate l'invio al provider, l'accettazione da parte del server destinatario, l'errore temporaneo o permanente, il bounce successivo, la segnalazione di spam, la disiscrizione, il posizionamento nella casella e l'engagement. Acquisisci gli eventi autenticati del provider quando disponibili, deduplicali e conserva l'ora in cui si sono verificati separatamente da quella di elaborazione. Applica immediatamente le soppressioni dovute a bounce permanenti, segnalazioni di spam e disiscrizioni prima degli invii successivi. Aperture e clic non sono prove di trasporto e possono essere influenzati dalle tecnologie per la privacy. Mantieni metriche aggregate minimizzate per la privacy per coorte sicura rispetto ai tenant, revisione del template, dominio mittente, classe di stato e periodo. Imposta avvisi su picchi di rifiuti, esiti sconosciuti, età in coda, errori TLS, errori di autenticazione e una distribuzione anomala dei destinatari.
Testa in locale senza inviare posta reale ai clienti
Esegui unit test per la costruzione dei messaggi, il rifiuto dell'iniezione di intestazioni, l'autorizzazione dei destinatari, la rimozione di Bcc, le alternative in testo semplice e HTML, la gestione Unicode, i limiti degli allegati e i controlli di soppressione. Usa un server di test SMTP locale controllato o una fixture di protocollo per simulare errori di saluto, STARTTLS mancante, fallimento del certificato, errori di autenticazione, accettazione RCPT parziale, risposte DATA 4xx e 5xx, disconnessioni e risposte ritardate. Non usare servizi di debug non autenticati e deprecati per segreti simili a quelli di produzione o contenuti dei clienti. I test di integrazione devono usare account dedicati e destinatari controllati, con quote e pulizia esplicite. Verifica il messaggio raw ricevuto, i risultati dell'autenticazione, le intestazioni visibili, il comportamento delle risposte e la correlazione degli eventi. Esegui la scansione dei segreti su fixture e log.
Come si inserisce SendHQ
SendHQ documenta un'API email con ambito limitato al workspace per l'invio da domini verificati, gli eventi di consegna e le soppressioni. Questa guida tratta il client SMTP della libreria standard di Python; usa la documentazione di SendHQ per i metodi di integrazione correnti e il contratto API.
Domande frequenti
Le credenziali SMTP di Python vanno inserite nel codice client?
No. Tienile in un secret manager lato server con ambito ristretto per ambiente e carico di lavoro, accessi sottoposti ad audit, rotazione regolare e nessuna registrazione nei log.
Quando conviene usare SMTP_SSL in Python?
Usa SMTP_SSL quando TLS è richiesto fin dall'inizio della connessione. Usa SMTP con starttls solo per un flusso di upgrade esplicito documentato che blocchi la connessione in caso di errore.
Bisogna chiamare di nuovo EHLO dopo starttls?
Sì. La documentazione di smtplib di Python indica di chiamare di nuovo ehlo dopo starttls, così che le capacità del server vengano rilevate di nuovo all'interno della connessione protetta.
send_message significa che ogni destinatario è stato accettato?
No. Python può terminare normalmente quando almeno un destinatario è stato accettato e restituisce separatamente i destinatari rifiutati. Salva e gestisci gli esiti dei destinatari in modo indipendente.
Cosa deve succedere dopo un SMTPAuthenticationError?
Metti in pausa la configurazione interessata ed esamina TLS, server, account, segreto e meccanismi annunciati. Nuovi tentativi alla cieca con le credenziali possono aggravare blocchi dell'account o segnali di compromissione.
Ogni SMTPDataError va ritentato?
No. Conserva lo stato e la diagnostica esatti, poi distingui le condizioni temporanee 4xx dagli errori permanenti 5xx di policy, contenuto, quota o configurazione.
L'accettazione SMTP determina l'arrivo in inbox?
No. È un'evidenza di trasporto con un ambito preciso. Relay successivi, filtri del destinatario, bounce, regole della casella, cartella di destinazione ed engagement umano restano esiti separati.
Questa guida copre un'integrazione specifica di SendHQ?
No. Copre il client SMTP della libreria standard di Python. Consulta la documentazione di SendHQ per i metodi di integrazione correnti e il contratto API.
Fonti
- Documentazione di smtplib per Python 3 — Python Software Foundation
- Documentazione di EmailMessage per Python 3 — Python Software Foundation
- Documentazione di ssl per Python 3 — Python Software Foundation
- RFC 5321: Simple Mail Transfer Protocol (SMTP) — RFC Editor
- RFC 3207: estensione del servizio SMTP per SMTP sicuro su TLS — RFC Editor
- RFC 4954: SMTP Service Extension for Authentication — RFC Editor