⟳ Automazione

Inviare email automatiche con Python: allegati e credenziali

Come inviare email automatiche con Python e smtplib: allegati, connessione cifrata, password per app, credenziali fuori dal codice e invii graduali.

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.message costruisce messaggi corretti con allegati, smtplib li 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.
MM
Marco Marzaduri

Marco Marzaduri si occupa di cybersecurity, informatica forense e sviluppo web. Laureato in informatica, unisce il rigore dell'analisi tecnica all'attenzione per le persone che usano la tecnologia.

Leggi anche