Uno script che genera un report o un documento spesso termina con lo stesso gesto manuale: aprire il client di posta, scrivere due righe, allegare il file, inviare. Python include nella libreria standard tutto ciò che serve per fare anche quest'ultimo passo: il modulo smtplib per parlare con il server di posta e il modulo email per costruire messaggi corretti, con allegati e caratteri accentati.
Come funziona l'invio
Un programma di posta, quando inviate un messaggio, si collega al server SMTP del vostro provider, si autentica e consegna il messaggio, che poi il server inoltra ai destinatari. Uno script fa esattamente la stessa cosa. Servono quindi tre informazioni, che si trovano nelle impostazioni del proprio account:
- l'indirizzo del server SMTP, per esempio
smtp.provider.it; - la porta e il tipo di cifratura: di norma 465 con SSL/TLS diretto oppure 587 con STARTTLS;
- nome utente e password.
Costruire il messaggio
Il modulo email.message offre la classe EmailMessage, che gestisce automaticamente codifiche e intestazioni:
from email.message import EmailMessage
from pathlib import Path
msg = EmailMessage()
msg["From"] = "Ufficio amministrazione <amministrazione@esempio.it>"
msg["To"] = "responsabile@esempio.it"
msg["Subject"] = "Report vendite settimanale"
msg.set_content("Buongiorno,\nin allegato il report della settimana.\n\nCordiali saluti")
allegato = Path("vendite_2025-03-10.xlsx")
msg.add_attachment(
allegato.read_bytes(),
maintype="application",
subtype="vnd.openxmlformats-officedocument.spreadsheetml.sheet",
filename=allegato.name,
)
Il tipo dell'allegato può essere ricavato automaticamente con il modulo mimetypes, che a partire dall'estensione restituisce la coppia corretta, per esempio application/pdf per i PDF. Per una versione formattata del testo si può aggiungere anche un'alternativa HTML con msg.add_alternative(html, subtype="html"), lasciando il testo semplice come riserva per i client che non la mostrano.
Inviare in modo cifrato
import smtplib, ssl
contesto = ssl.create_default_context()
with smtplib.SMTP_SSL("smtp.provider.it", 465, context=contesto) as server:
server.login(utente, password)
server.send_message(msg)
Con la porta 587 si usa invece smtplib.SMTP seguito da server.starttls(context=contesto) prima del login. In entrambi i casi la password e il contenuto viaggiano cifrati. Un collegamento senza cifratura non va mai usato, nemmeno per prova.
La password non va nel codice
L'errore più comune è scrivere la password direttamente nello script. Prima o poi lo script viene copiato, condiviso con un collega o salvato in una cartella sincronizzata, e la password lo segue. Le alternative sono diverse:
| Metodo | Come funziona |
|---|---|
| Variabile d'ambiente | os.environ["SMTP_PASSWORD"] letta all'avvio |
| File separato | Un file di configurazione fuori dalla cartella del codice, con permessi ristretti |
| Portachiavi di sistema | La libreria keyring usa il gestore credenziali di Windows o il Portachiavi di macOS |
Nel mio lavoro preferisco il portachiavi di sistema: la password viene salvata una volta con keyring.set_password("smtp", utente, password) e letta dallo script con keyring.get_password, senza mai comparire in chiaro su disco.
Account con verifica in due passaggi
Molti provider, compresi i principali servizi di posta gratuiti, non accettano la password normale dell'account per l'accesso da script quando è attiva la verifica in due passaggi. Bisogna generare dalle impostazioni di sicurezza una password per app, una password specifica e revocabile da usare solo per quello script. Se un giorno lo script viene dismesso o compromesso, si revoca quella sola password senza toccare l'account. Alcuni servizi aziendali richiedono invece metodi di autenticazione più moderni basati su token: in quel caso conviene seguire la documentazione del provider.
Invii a più destinatari
Quando lo stesso script deve inviare messaggi personalizzati a molte persone, per esempio un promemoria a ogni cliente con il proprio estratto, valgono alcune regole:
- un messaggio per destinatario, mai un elenco di indirizzi visibili nel campo «A»;
- una pausa tra un invio e l'altro, con
time.sleep(2), per non superare i limiti del provider ed evitare che l'account venga bloccato come fonte di spam; - un registro degli invii, in un CSV con indirizzo, data e esito, per sapere chi ha già ricevuto il messaggio se lo script si interrompe a metà;
- una modalità di prova che invia tutto a un indirizzo interno prima di partire con i destinatari veri.
for cliente in clienti:
msg = crea_messaggio(cliente)
try:
server.send_message(msg)
esito = "inviato"
except smtplib.SMTPException as e:
esito = f"errore: {e}"
registra(cliente["email"], esito)
time.sleep(2)
Per invii davvero massivi, come newsletter, uno script con la casella aziendale non è lo strumento giusto: servono servizi dedicati che gestiscono disiscrizioni e reputazione del mittente.
In sintesi
email.messagecostruisce messaggi corretti con allegati,smtplibli invia.- Usate sempre una connessione cifrata, sulla porta 465 o 587.
- Tenete la password fuori dal codice, preferibilmente nel portachiavi di sistema.
- Con la verifica in due passaggi usate una password per app revocabile.
- Per più destinatari inviate messaggi singoli, con pause, registro e una prova interna.