HOWTO · Python

Senden Sie E-Mails mit Python an mehrere Adressen

Dieser Artikel zeigt, wie Sie mit Python E-Mails an mehrere Adressen senden.

Auf dieser Seite

Dieselbe Nachricht an mehrere Adressen zu senden ist in Python eine Aufgabe für die Standardbibliothek: smtplib verwaltet die SMTP-Sitzung, und EmailMessage aus email.message enthält die Header und den Textkörper. Setzen Sie jede Adresse, die ein Empfänger sehen soll, in den Header To oder Cc, verborgene Adressen in Bcc, und rufen Sie send_message einmal auf — der SMTP-Server kümmert sich um die Zustellung einer Kopie an jedes Postfach.

So funktionieren mehrere E-Mail-Empfänger

Eine E-Mail enthält zwei getrennte Empfängerlisten, und wer sie auseinanderhält, versteht die meisten Überraschungen beim Versand an mehrere Empfänger:

  • Die Umschlag-Empfänger (Envelope Recipients) sind die Adressen, die Ihr Client dem SMTP-Server in RCPT TO-Befehlen übergibt. Die Zustellung wird vollständig über diese Liste gesteuert.
  • Die Header-Empfänger sind die Zeilen To, Cc und Bcc innerhalb der Nachricht. Sie sind Anzeigehinweise für E-Mail-Clients; der Server routet nicht nach ihnen.

To und Cc verhalten sich bei der Zustellung gleich — jeder Empfänger kann beide Listen sehen. Bcc-Empfänger werden wie alle anderen zugestellt, aber send_message entfernt den Bcc-Header vor der Übertragung, sodass niemand je sieht, wer blind kopiert wurde.

Wenn Sie server.send_message(message) aufrufen, liest smtplib die Header To, Cc und Bcc und sendet ein RCPT TO pro Adresse. Sie können auch eine explizite Liste als to_addrs übergeben, die die Header für die Zustellung überschreibt — nützlich, wenn die sichtbare To-Zeile von der tatsächlichen Zustellliste abweichen soll, etwa bei einer verborgenen Verteilerliste.

Jede Adresse erhält eine byte-identische Kopie der Nachricht. Benötigt jede Person anderen Inhalt, erstellen und senden Sie pro Empfänger ein eigenes EmailMessage; der letzte Abschnitt behandelt dieses Muster.

Wählen Sie einen SMTP-Server und eine sichere Authentifizierung

Um einen echten E-Mail-Anbieter zu erreichen, brauchen Sie drei Dinge: den SMTP-Hostnamen und Port des Anbieters, einen Kontonamen und Anmeldedaten. Die Dokumentation des Anbieters ist maßgeblich — die Anforderungen ändern sich, also bestätigen Sie die aktuellen Werte, statt einem kopierten Beispiel zu vertrauen.

Zwei verschlüsselte Verbindungsarten sind üblich:

  • Implizites TLS vom ersten Byte an, typischerweise auf Port 465: verwenden Sie smtplib.SMTP_SSL(host, port).
  • Eine einfache Verbindung, die mit STARTTLS hochgestuft wird, typischerweise auf Port 587: verwenden Sie smtplib.SMTP(host, port) und rufen Sie dann server.starttls() vor server.login(...) auf.

Viele Anbieter akzeptieren das normale Kontopasswort über SMTP nicht mehr. Sie stellen ein App-Passwort oder einen als Passwort verwendeten API-Schlüssel aus oder verlangen OAuth2 — erzeugen Sie, was immer der Anbieter unterstützt. Bewahren Sie die Anmeldedaten außerhalb des Quellcodes auf; Umgebungsvariablen, wie im zweiten Beispiel unten, sind der einfachste Ausgangspunkt. Committen Sie echte Zugangsdaten niemals in ein Repository.

Erstellen Sie ein EmailMessage für mehrere Empfänger

EmailMessage verhält sich wie ein geordnetes Dictionary aus Headern plus einem Textkörper. Setzen Sie zuerst die Header:

  • message["From"] — der Absender. Die meisten Anbieter verlangen, dass er zum authentifizierten Konto oder einem verifizierten Alias passt; eine Abweichung ist ein häufiger Grund für eine Ablehnung.
  • message["To"] — eine kommagetrennte Zeichenkette. Verbinden Sie eine Python-Liste mit ", ".join(recipients), damit das Programm leicht erweiterbar bleibt.
  • message["Cc"] und message["Bcc"] — optional, im selben kommagetrennten Format.
  • message["Subject"] — die Betreffzeile.

message.set_content("...") füllt einen Nur-Text-Körper und setzt Content-Type: text/plain für Sie. Für eine HTML-Version rufen Sie message.add_alternative(html, subtype="html") nach set_content auf, damit Clients, die Text bevorzugen, eine Ausweichvariante behalten. message.add_attachment(...) fügt Dateien wie PDFs oder Bilder hinzu.

Senden Sie die Nachricht mit smtplib

send_message überträgt die erstellte Nachricht einmal und lässt den Server sie an jeden Umschlag-Empfänger verteilen. Als Kontextmanager verwendet, schließt das SMTP-Objekt die Sitzung sauber mit QUIT, wenn der with-Block endet.

Führen Sie dieses vollständige Skript aus. Es startet einen kleinen SMTP-Testserver auf localhost, sendet ein EmailMessage an ein To-Paar plus Cc- und Bcc-Adressen über smtplib und gibt jeden Umschlag-Empfänger aus, den der Server aufgezeichnet hat:

Der Code und die angezeigten Bezeichnungen oder Ausgaben in diesem Abschnitt bleiben auf Englisch.

import smtplib
import socket
import threading
from email.message import EmailMessage

recipients_seen = []

def run_server(sock):
    conn, _ = sock.accept()
    conn.sendall(b"220 localhost ESMTP test server\r\n")
    data_mode = False
    buffer = b""
    while True:
        chunk = conn.recv(1024)
        if not chunk:
            return
        buffer += chunk
        while b"\r\n" in buffer:
            line, buffer = buffer.split(b"\r\n", 1)
            if data_mode:
                if line == b".":
                    data_mode = False
                    conn.sendall(b"250 Message stored\r\n")
                continue
            command = line.decode().split(" ", 1)[0].upper()
            if command == "RCPT":
                address = line.decode().rsplit(":", 1)[-1].strip().strip("<>")
                recipients_seen.append(address)
                conn.sendall(b"250 OK\r\n")
            elif command == "DATA":
                data_mode = True
                conn.sendall(b"354 End data with <CR><LF>.<CR><LF>\r\n")
            elif command == "QUIT":
                conn.sendall(b"221 Bye\r\n")
                return
            else:
                conn.sendall(b"250 OK\r\n")

listener = socket.socket()
listener.bind(("127.0.0.1", 0))
listener.listen(1)
port = listener.getsockname()[1]
thread = threading.Thread(target=run_server, args=(listener,))
thread.start()

message = EmailMessage()
message["Subject"] = "Team update"
message["From"] = "sender@example.com"
message["To"] = "alice@example.com, bob@example.com"
message["Cc"] = "carol@example.com"
message["Bcc"] = "dave@example.com"
message.set_content("Here is this week's team update.")

with smtplib.SMTP("127.0.0.1", port) as server:
    server.send_message(message)

thread.join()
listener.close()
print("Delivered to these envelope recipients:")
for recipient in recipients_seen:
    print(recipient)
Delivered to these envelope recipients:
alice@example.com
bob@example.com
dave@example.com
carol@example.com

Der Testserver hat vier RCPT TO-Adressen protokolliert — eine pro Header-Adresse — darunter dave@example.com, dessen Bcc-Header aus der übertragenen Kopie entfernt wird und die Zustellung trotzdem steuert. Das ist die Trennung von Umschlag und Header in Aktion. Der lokale Testserver spricht unverschlüsseltes SMTP, weshalb dieses Skript smtplib.SMTP ohne Verschlüsselung verwendet.

Um über einen echten Anbieter zu senden, passen Sie dieselbe Nachricht wie folgt an. Das Skript liest die Verbindungsdaten aus Umgebungsvariablen und erinnert Sie daran, sie zu setzen, wenn sie fehlen:

import os
import smtplib
from email.message import EmailMessage

SMTP_HOST = os.environ.get("SMTP_HOST", "")
SMTP_PORT = int(os.environ.get("SMTP_PORT", "465"))
SMTP_USER = os.environ.get("SMTP_USER", "")
SMTP_PASSWORD = os.environ.get("SMTP_PASSWORD", "")

recipients = [
    "alice@example.com",
    "bob@example.com",
    "carol@example.com",
]

message = EmailMessage()
message["Subject"] = "Team update"
message["From"] = SMTP_USER
message["To"] = ", ".join(recipients)
message.set_content("Here is this week's team update.")

if SMTP_HOST and SMTP_USER and SMTP_PASSWORD:
    with smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT) as server:
        server.login(SMTP_USER, SMTP_PASSWORD)
        refused = server.send_message(message)
    print("Accepted for all recipients" if not refused else f"Refused: {refused}")
else:
    print("Set SMTP_HOST, SMTP_PORT, SMTP_USER, and SMTP_PASSWORD to send this message.")
Set SMTP_HOST, SMTP_PORT, SMTP_USER, and SMTP_PASSWORD to send this message.

Mit gesetzten Variablen und einem erreichbaren Anbieter meldet sich dasselbe Skript mit server.login an, sendet über SMTP_SSL und gibt Accepted for all recipients aus — diese Ausgabezeile bleibt unverändert auf Englisch, da sie fest im Beispielcode steht.

Behandeln Sie Zustellfehler und passen Sie das Beispiel an

Fehler treten an wenigen vorhersehbaren Stellen auf:

  • Authentifizierung — server.login löst smtplib.SMTPAuthenticationError aus, wenn die Anmeldedaten falsch sind oder der Anbieter ein App-Passwort bzw. OAuth2 statt des Kontopassworts verlangt.
  • Verbindung — ein falscher Hostname löst socket.gaierror aus; ein falscher Port oder eine blockierte Route zeigt sich als smtplib.SMTPConnectError oder als Timeout. Prüfen Sie, ob der Port zur Verschlüsselungsart passt: SMTP_SSL für 465, SMTP plus starttls() für 587.
  • Empfänger-Ablehnung — wenn der Server jede Adresse ablehnt, löst send_message smtplib.SMTPRecipientsRefused aus. Werden nur einige abgelehnt, sendet es trotzdem an die übrigen und gibt ein Dictionary zurück, das jede abgelehnte Adresse auf ihren SMTP-Statuscode und ihre Meldung abbildet — prüfen Sie diesen Rückgabewert, statt anzunehmen, dass Schweigen Erfolg bedeutet.

Eine SMTP-250-Antwort bedeutet, dass der Server die Nachricht zum Weiterleiten angenommen hat, nicht dass sie in einem Posteingang gelandet ist. Anbieter können sie immer noch filtern, in Quarantäne stellen oder zurückweisen; achten Sie bei echten Versendungen daher auf Bounce-Meldungen im Absender-Postfach.

Um das Beispiel anzupassen, ersetzen Sie die Liste recipients durch Adressen aus einer Datei, einer Datenbank oder einer Umgebungsvariable. Für personalisierte Inhalte halten Sie die Verbindung in einem einzigen with-Block offen und durchlaufen send_message mit einem EmailMessage pro Person — die Wiederverwendung der Sitzung ist deutlich schneller als ein erneuter Verbindungsaufbau. Bei großen Aussendungen hält Bcc die Adressen privat, aber beachten Sie die Ratenlimits des Anbieters; sehr große Listen gehören auf einen dedizierten Versanddienst.