Wie sende ich eine E-Mail mit Python?

169

Dieser Code funktioniert und sendet mir eine E-Mail:

import smtplib
#SERVER = "localhost"

FROM = '[email protected]'

TO = ["[email protected]"] # must be a list

SUBJECT = "Hello!"

TEXT = "This message was sent with Python's smtplib."

# Prepare actual message

message = """\
From: %s
To: %s
Subject: %s

%s
""" % (FROM, ", ".join(TO), SUBJECT, TEXT)

# Send the mail

server = smtplib.SMTP('myserver')
server.sendmail(FROM, TO, message)
server.quit()

Wenn ich jedoch versuche, es in eine Funktion wie diese zu verpacken:

def sendMail(FROM,TO,SUBJECT,TEXT,SERVER):
    import smtplib
    """this is some test documentation in the function"""
    message = """\
        From: %s
        To: %s
        Subject: %s
        %s
        """ % (FROM, ", ".join(TO), SUBJECT, TEXT)
    # Send the mail
    server = smtplib.SMTP(SERVER)
    server.sendmail(FROM, TO, message)
    server.quit()

und nenne es ich bekomme folgende Fehler:

 Traceback (most recent call last):
  File "C:/Python31/mailtest1.py", line 8, in <module>
    sendmail.sendMail(sender,recipients,subject,body,server)
  File "C:/Python31\sendmail.py", line 13, in sendMail
    server.sendmail(FROM, TO, message)
  File "C:\Python31\lib\smtplib.py", line 720, in sendmail
    self.rset()
  File "C:\Python31\lib\smtplib.py", line 444, in rset
    return self.docmd("rset")
  File "C:\Python31\lib\smtplib.py", line 368, in docmd
    return self.getreply()
  File "C:\Python31\lib\smtplib.py", line 345, in getreply
    raise SMTPServerDisconnected("Connection unexpectedly closed")
smtplib.SMTPServerDisconnected: Connection unexpectedly closed

Kann mir jemand helfen zu verstehen warum?

cloud311
quelle
2
Wie ruft man die Funktion auf?
Jochen Ritzel
Ist der Einzug, den Sie gepostet haben, der gleiche wie in Ihrer Datei?
GDDC
@gddc nein, ich habe darauf geachtet, richtig einzurücken, das ist genau die Art, wie ich es eingefügt habe.
cloud311
Ich rufe die Funktion auf, indem ich sie in mein Hauptmodul importiere und die von mir definierten Parameter darin übergebe.
Cloud311
4
Obwohl @ Arrietas Vorschlag, das E-Mail-Paket zu verwenden, der beste Weg ist, dies zu lösen, kann Ihr Ansatz funktionieren. Die Unterschiede zwischen Ihren beiden Versionen liegen in der Zeichenfolge: (1) Wie @NickODell hervorhebt, haben Sie in der Funktionsversion führende Leerzeichen. Header sollten kein führendes Leerzeichen haben (es sei denn, sie sind umbrochen). (2) Wenn TEXT keine führende Leerzeile enthält, haben Sie das Trennzeichen zwischen Überschriften und Text verloren.
Tony Meyer

Antworten:

191

Ich empfehle, dass Sie die Standardpakete verwenden emailund smtplibzusammen E-Mails senden. Bitte schauen Sie sich das folgende Beispiel an (reproduziert aus der Python-Dokumentation ). Beachten Sie, dass bei diesem Ansatz die "einfache" Aufgabe in der Tat einfach ist und die komplexeren Aufgaben (wie das Anhängen von Binärobjekten oder das Senden von mehrteiligen einfachen / HTML-Nachrichten) sehr schnell erledigt werden.

# Import smtplib for the actual sending function
import smtplib

# Import the email modules we'll need
from email.mime.text import MIMEText

# Open a plain text file for reading.  For this example, assume that
# the text file contains only ASCII characters.
with open(textfile, 'rb') as fp:
    # Create a text/plain message
    msg = MIMEText(fp.read())

# me == the sender's email address
# you == the recipient's email address
msg['Subject'] = 'The contents of %s' % textfile
msg['From'] = me
msg['To'] = you

# Send the message via our own SMTP server, but don't include the
# envelope header.
s = smtplib.SMTP('localhost')
s.sendmail(me, [you], msg.as_string())
s.quit()

Um E-Mails an mehrere Ziele zu senden, können Sie auch dem Beispiel in der Python-Dokumentation folgen :

# Import smtplib for the actual sending function
import smtplib

# Here are the email package modules we'll need
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart

# Create the container (outer) email message.
msg = MIMEMultipart()
msg['Subject'] = 'Our family reunion'
# me == the sender's email address
# family = the list of all recipients' email addresses
msg['From'] = me
msg['To'] = ', '.join(family)
msg.preamble = 'Our family reunion'

# Assume we know that the image files are all in PNG format
for file in pngfiles:
    # Open the files in binary mode.  Let the MIMEImage class automatically
    # guess the specific image type.
    with open(file, 'rb') as fp:
        img = MIMEImage(fp.read())
    msg.attach(img)

# Send the email via our own SMTP server.
s = smtplib.SMTP('localhost')
s.sendmail(me, family, msg.as_string())
s.quit()

Wie Sie sehen können, muss der Header Toim MIMETextObjekt eine Zeichenfolge sein, die aus durch Kommas getrennten E-Mail-Adressen besteht. Andererseits muss das zweite Argument für die sendmailFunktion eine Liste von Zeichenfolgen sein (jede Zeichenfolge ist eine E-Mail-Adresse).

Also, wenn Sie drei E - Mail - Adressen haben: [email protected], [email protected], und [email protected]können Sie tun , folgt als (offensichtliche Abschnitte weggelassen):

to = ["[email protected]", "[email protected]", "[email protected]"]
msg['To'] = ",".join(to)
s.sendmail(me, to, msg.as_string())

Der ",".join(to)Teil macht eine einzelne Zeichenfolge aus der Liste, die durch Kommas getrennt ist.

Aus Ihren Fragen geht hervor, dass Sie das Python-Tutorial noch nicht durchlaufen haben - es ist ein MUSS, wenn Sie irgendwo in Python hinkommen möchten -, dass die Dokumentation für die Standardbibliothek größtenteils hervorragend ist.

Escualo
quelle
1
Vielen Dank, dass dies innerhalb einer Funktion sehr gut funktioniert. Wie kann ich jedoch an mehrere Empfänger senden? Da msg [to] wie ein Wörterbuchschlüssel aussieht, habe ich versucht, msg [to] durch ein Semikolon zu trennen, aber das scheint nicht zu funktionieren.
Cloud311
1
@ cloud311 genau so, wie Sie es in Ihrem Code haben. Es will eine durch Kommas getrennte Zeichenfolge: ", ".join(["[email protected]", "[email protected]"])
Tim McNamara
3
Beachten Sie auch, dass der To: -Header eine andere Semantik als der Umschlagempfänger hat. Beispielsweise können Sie "Tony Meyer" <[email protected]> "als Adresse im Header" An: "verwenden, der Empfänger des Umschlags darf jedoch nur" [email protected] "sein. Verwenden Sie email.utils.formataddr wie email.utils.formataddr ("Tony Meyer", "[email protected]"), um eine "nette" An: Adresse zu erstellen.
Tony Meyer
1
Kleine Verbesserung: Die Datei sollte geöffnet werden mit with: with open(textfile, 'rb') as fp:. Das explizite Schließen kann gelöscht werden, da der withBlock die Datei schließt, selbst wenn ein Fehler darin auftritt.
jpmc26
1
Nicht spezifisch für diese Antwort, aber wenn Sie eine Verbindung zu einem SMTP-Server herstellen, den Sie nicht steuern, sollten Sie die Möglichkeit in Betracht ziehen, dass dieser nicht erreichbar, langsam ist, Verbindungen ablehnt oder was auch immer. In Bezug auf den Code erhalten Sie eine Ausnahme, aber Sie müssen dann einen Weg finden, um das Senden etwas später erneut zu versuchen. Wenn Sie mit Ihrem eigenen sendmail / postfix sprechen, wird das erneute Senden für Sie erledigt.
Ralph Bolton
66

Nun, Sie möchten eine Antwort haben, die aktuell und modern ist.

Hier ist meine Antwort:

Wenn ich in Python mailen muss , verwende ich die Mailgun- API, die viele Probleme beim Aussenden von Mails verursacht. Sie haben eine wunderbare App / API, mit der Sie 10.000 E-Mails pro Monat kostenlos senden können.

Das Senden einer E-Mail würde folgendermaßen aussehen:

def send_simple_message():
    return requests.post(
        "https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages",
        auth=("api", "YOUR_API_KEY"),
        data={"from": "Excited User <mailgun@YOUR_DOMAIN_NAME>",
              "to": ["[email protected]", "YOU@YOUR_DOMAIN_NAME"],
              "subject": "Hello",
              "text": "Testing some Mailgun awesomness!"})

Sie können auch Ereignisse und vieles mehr verfolgen, siehe die Kurzanleitung .

Ich hoffe, Sie finden das nützlich!

Dennis Decoene
quelle
3
Dies ist in der Tat viel mehr "von diesem Datum". Es wird jedoch eine externe API verwendet. Ich persönlich würde so etwas wie yagmail verwenden, um es intern zu halten.
PascalVKooten
6
@PascalvKooten Absolut amüsant, Ihrer ständigen Werbung für yagmail zu folgen (ja, Sir, ich werde es beim nächsten Mal in Betracht ziehen, Sir;). Aber ich finde es sehr verwirrend, dass sich fast niemand für das OP-Problem zu interessieren scheint, sondern viel andere Lösungen vorschlägt. Es ist, als würde ich fragen, wie man die Glühbirnen in meinem 2009 smart wechselt, und die Antwort lautet: Kaufen Sie einen echten Mercedes ...
flaschbier
Schön, dass meine Mission für manche einen gewissen Vergnügungswert hat.
PascalVKooten
4
@flaschbier Der Grund, warum sich niemand um das OP-Problem kümmert, ist, dass der Titel falsch ist. "Wie sende ich eine E-Mail mit Python?" ist der eigentliche Grund, warum Leute kommen, um zu schauen, wenn sie auf diese Frage klicken, und sie würden eine Antwort erwarten, die yagmail liefern kann: nett und kurz. Los geht's. Mehr yagmail Werbung.
PascalVKooten
1
Um nur zu sagen, dass für Mailgun-Kunden das Senden von E-Mails über Webservices wesentlich bandbreitenfreundlicher ist als über SMTP (insbesondere bei Verwendung von Anhängen).
Ralph Bolton
44

Ich möchte Ihnen beim Versenden von E-Mails helfen, indem ich das yagmail-Paket berate (ich bin der Betreuer, entschuldige die Werbung, aber ich denke, es kann wirklich helfen!).

Der gesamte Code für Sie wäre:

import yagmail
yag = yagmail.SMTP(FROM, 'pass')
yag.send(TO, SUBJECT, TEXT)

Beachten Sie, dass ich für alle Argumente Standardeinstellungen TOgebe. Wenn Sie beispielsweise an sich selbst senden möchten, können Sie diese weglassen . Wenn Sie keinen Betreff möchten, können Sie ihn auch weglassen.

Darüber hinaus ist es das Ziel, das Anhängen von HTML-Code oder Bildern (und anderen Dateien) zu vereinfachen.

Wo Sie Inhalte ablegen, können Sie Folgendes tun:

contents = ['Body text, and here is an embedded image:', 'http://somedomain/image.png',
            'You can also find an audio file attached.', '/local/path/song.mp3']

Wow, wie einfach ist es, Anhänge zu versenden! Dies würde wie 20 Zeilen ohne yagmail dauern;)

Wenn Sie es einmal einrichten, müssen Sie das Kennwort nie wieder eingeben (und sicher aufbewahren). In Ihrem Fall können Sie Folgendes tun:

import yagmail
yagmail.SMTP().send(contents = contents)

das ist viel prägnanter!

Ich möchte Sie einladen, sich den Github anzusehen oder ihn direkt mit zu installieren pip install yagmail.

PascalVKooten
quelle
4
Dies sollte die Top-Lösung sein.
Richard de Ree
1
OMG, das ist ein süßes, EINFACHES E-Mail-Modul. Vielen Dank!
Pepoluan
1
Kann ich yagmailandere als Google Mail verwenden? Ich versuche, für meinen eigenen SMTP-Server zu verwenden.
Anirban Nag 'tintinmj'
@dtgq Danke für deine Sorge. Persönlich sehe ich den Angriffsvektor nicht. Wenn jemand die Datei unter dem Pfad ändern möchte, den Sie senden möchten, spielt es keine Rolle, ob Sie eine AttachmentKlasse haben. es ist immer noch dasselbe. Wenn sie Ihren Code ändern können, können sie trotzdem alles tun, was sie wollen (mit / ohne root, das ist das gleiche per E-Mail). Dies scheint mir das typische "es ist bequem / magisch, also muss es weniger sicher sein". Ich bin gespannt, welche echte Bedrohung Sie sehen.
PascalVKooten
14

Es liegt ein Einrückungsproblem vor. Der folgende Code funktioniert:

import textwrap

def sendMail(FROM,TO,SUBJECT,TEXT,SERVER):
    import smtplib
    """this is some test documentation in the function"""
    message = textwrap.dedent("""\
        From: %s
        To: %s
        Subject: %s
        %s
        """ % (FROM, ", ".join(TO), SUBJECT, TEXT))
    # Send the mail
    server = smtplib.SMTP(SERVER)
    server.sendmail(FROM, TO, message)
    server.quit()

Zeeshan
quelle
3
@geotheory Die SERVERVariable, die an die Funktion übergeben wird, sind die Benutzeranmeldeinformationen.
Benutzer
Das manuelle Zusammensetzen einer gültigen SMTP-Nachricht mit einfacher Zeichenfolgeninterpolation wird nicht empfohlen, und Ihre Antwort weist einen Fehler auf, der dies perfekt veranschaulicht. (Der Text muss mit einer Leerzeile beginnen, damit dies eine gültige Nachricht ist.) Für alles, was andere Zeichensätze als Nur-Text in den 1980er Jahren betrifft, nur 7-Bit-US-ASCII (nur Anhänge, Internationalisierung und andere MIME-Unterstützung) Ich möchte wirklich eine Bibliothek verwenden, die dieses Zeug handhabt. Die Python- emailBibliothek macht dies ziemlich gut (insbesondere seit 3.6), obwohl es immer noch ein gewisses Verständnis dafür erfordert, was Sie tun.
Tripleee
7

Hier ist ein Beispiel für Python 3.x, viel einfacher als 2.x:

import smtplib
from email.message import EmailMessage
def send_mail(to_email, subject, message, server='smtp.example.cn',
              from_email='[email protected]'):
    # import smtplib
    msg = EmailMessage()
    msg['Subject'] = subject
    msg['From'] = from_email
    msg['To'] = ', '.join(to_email)
    msg.set_content(message)
    print(msg)
    server = smtplib.SMTP(server)
    server.set_debuglevel(1)
    server.login(from_email, 'password')  # user & password
    server.send_message(msg)
    server.quit()
    print('successfully sent the mail.')

Rufen Sie diese Funktion auf:

send_mail(to_email=['[email protected]', '[email protected]'],
          subject='hello', message='Your analysis has done!')

unten darf nur für chinesische Benutzer:

Wenn Sie 126/163, 网易 邮箱 verwenden, müssen Sie "客户 端 授权 密码" wie folgt einstellen:

Geben Sie hier die Bildbeschreibung ein

Ref: https://stackoverflow.com/a/41470149/2803344 https://docs.python.org/3/library/email.examples.html#email-examples

Belter
quelle
4

Während Sie Ihren Code in die Funktion einrücken (was in Ordnung ist), haben Sie auch die Zeilen der rohen Nachrichtenzeichenfolge eingerückt. Führender Leerraum impliziert jedoch das Falten (Verketten) der Kopfzeilen, wie in den Abschnitten 2.2.3 und 3.2.3 von RFC 2822 - Internet Message Format beschrieben :

Jedes Kopfzeilenfeld ist logischerweise eine einzelne Zeichenzeile, die den Feldnamen, den Doppelpunkt und den Feldkörper umfasst. Der Einfachheit halber und um die 998/78-Zeichenbeschränkungen pro Zeile zu bewältigen, kann der Feldkörperteil eines Kopffelds in eine Darstellung mit mehreren Zeilen aufgeteilt werden. Dies nennt man "Falten".

In der Funktionsform Ihres sendmailAnrufs beginnen alle Leitungen mit Leerzeichen und werden daher "entfaltet" (verkettet), und Sie versuchen zu senden

From: [email protected]    To: [email protected]    Subject: Hello!    This message was sent with Python's smtplib.

Anders als unser Verstand vermuten lässt, smtplibwerden die To:und Subject:Überschriften nicht mehr verstanden, da diese Namen nur am Anfang einer Zeile erkannt werden. Stattdessen smtplibwird eine sehr lange Absender-E-Mail-Adresse angenommen:

[email protected]    To: [email protected]    Subject: Hello!    This message was sent with Python's smtplib.

Dies wird nicht funktionieren und so kommt Ihre Ausnahme.

Die Lösung ist einfach: Bewahren Sie den messageString einfach so auf, wie er vorher war. Dies kann durch eine Funktion (wie von Zeeshan vorgeschlagen) oder sofort im Quellcode erfolgen:

import smtplib

def sendMail(FROM,TO,SUBJECT,TEXT,SERVER):
    """this is some test documentation in the function"""
    message = """\
From: %s
To: %s
Subject: %s

%s
""" % (FROM, ", ".join(TO), SUBJECT, TEXT)
    # Send the mail
    server = smtplib.SMTP(SERVER)
    server.sendmail(FROM, TO, message)
    server.quit()

Jetzt findet die Entfaltung nicht mehr statt und Sie senden

From: [email protected]
To: [email protected]
Subject: Hello!

This message was sent with Python's smtplib.

Welches ist, was funktioniert und was von Ihrem alten Code getan wurde.

Beachten Sie, dass ich auch die leere Zeile zwischen Headern und Body beibehalten habe, um Abschnitt 3.5 des RFC aufzunehmen (der erforderlich ist), und das Include außerhalb der Funktion gemäß dem Python -Styleguide PEP-0008 (optional) platziert habe.

flaschbier
quelle
3

Wahrscheinlich werden Tabs in Ihre Nachricht eingefügt. Drucken Sie die Nachricht aus, bevor Sie sie an sendMail übergeben.

Nick ODell
quelle
1

Stellen Sie sicher, dass Sie sowohl dem Absender als auch dem Empfänger die Berechtigung erteilt haben, E-Mails von unbekannten Quellen (externen Quellen) im E-Mail-Konto zu senden und zu empfangen.

import smtplib

#Ports 465 and 587 are intended for email client to email server communication - sending email
server = smtplib.SMTP('smtp.gmail.com', 587)

#starttls() is a way to take an existing insecure connection and upgrade it to a secure connection using SSL/TLS.
server.starttls()

#Next, log in to the server
server.login("#email", "#password")

msg = "Hello! This Message was sent by the help of Python"

#Send the mail
server.sendmail("#Sender", "#Reciever", msg)

Geben Sie hier die Bildbeschreibung ein

Devanand Sharma
quelle
msgist keine gültige SMTP-Nachricht und scheint einfach im Äther zu verschwinden, wenn Ihr Mailserver dies akzeptiert.
Tripleee
0

Ich dachte, ich würde meine zwei Teile hier einfügen, da ich gerade herausgefunden habe, wie das funktioniert.

Es scheint, dass Sie den in Ihren SERVER-Verbindungseinstellungen angegebenen Port nicht haben. Dies hat mich ein wenig beeinträchtigt, als ich versuchte, eine Verbindung zu meinem SMTP-Server herzustellen, der nicht den Standardport verwendet: 25.

Gemäß den smtplib.SMTP-Dokumenten sollte Ihre Ehlo- oder Helo-Anfrage / Antwort automatisch bearbeitet werden, sodass Sie sich darüber keine Sorgen machen müssen (dies kann jedoch eine Bestätigung sein, wenn alles andere fehlschlägt).

Eine andere Frage ist, ob Sie SMTP-Verbindungen auf Ihrem SMTP-Server selbst zugelassen haben. Bei einigen Websites wie GMAIL und ZOHO müssen Sie die IMAP-Verbindungen innerhalb des E-Mail-Kontos aktivieren. Ihr Mailserver lässt möglicherweise keine SMTP-Verbindungen zu, die möglicherweise nicht von 'localhost' stammen. Etwas zu untersuchen.

Als letztes möchten Sie möglicherweise versuchen, die Verbindung über TLS herzustellen. Die meisten Server erfordern jetzt diese Art der Authentifizierung.

Sie werden sehen, dass ich zwei TO-Felder in meine E-Mail gestaut habe. Mit den msg-Wörterbuchelementen msg ['TO'] und msg ['FROM'] werden die richtigen Informationen in den Kopfzeilen der E-Mail selbst angezeigt, die am empfangenden Ende der E-Mail in den Feldern An / Von (Sie) angezeigt werden Möglicherweise können Sie hier sogar ein Antwortfeld hinzufügen. Die Felder TO und FROM selbst sind die Anforderungen des Servers. Ich weiß, dass einige E-Mail-Server E-Mails ablehnen, wenn sie nicht über die richtigen E-Mail-Header verfügen.

Dies ist der Code, den ich in einer Funktion verwendet habe, mit der ich den Inhalt einer * .txt-Datei mithilfe meines lokalen Computers und eines Remote-SMTP-Servers (ZOHO wie gezeigt) per E-Mail versenden kann:

def emailResults(folder, filename):

    # body of the message
    doc = folder + filename + '.txt'
    with open(doc, 'r') as readText:
        msg = MIMEText(readText.read())

    # headers
    TO = '[email protected]'
    msg['To'] = TO
    FROM = '[email protected]'
    msg['From'] = FROM
    msg['Subject'] = 'email subject |' + filename

    # SMTP
    send = smtplib.SMTP('smtp.zoho.com', 587)
    send.starttls()
    send.login('[email protected]', 'password')
    send.sendmail(FROM, TO, msg.as_string())
    send.quit()
ntk4
quelle
0

Es ist erwähnenswert, dass das SMTP-Modul den Kontextmanager unterstützt, sodass das manuelle Aufrufen von quit () nicht erforderlich ist. Dies garantiert, dass es immer aufgerufen wird, auch wenn es eine Ausnahme gibt.

    with smtplib.SMTP_SSL('smtp.gmail.com', 465) as server:
        server.ehlo()
        server.login(user, password)
        server.sendmail(from, to, body)
Peter C.
quelle
-1

Was Ihren Code betrifft, scheint nichts grundlegend Falsches daran zu sein, außer dass es unklar ist, wie Sie diese Funktion tatsächlich aufrufen. Ich kann mir nur vorstellen, dass Sie diesen SMTPServerDisconnected-Fehler erhalten, wenn Ihr Server nicht antwortet. Wenn Sie die Funktion getreply () in smtplib nachschlagen (Auszug unten), erhalten Sie eine Idee.

def getreply(self):
    """Get a reply from the server.

    Returns a tuple consisting of:

      - server response code (e.g. '250', or such, if all goes well)
        Note: returns -1 if it can't read response code.

      - server response string corresponding to response code (multiline
        responses are converted to a single, multiline string).

    Raises SMTPServerDisconnected if end-of-file is reached.
    """

Überprüfen Sie ein Beispiel unter https://github.com/rreddy80/sendEmails/blob/master/sendEmailAttachments.py , das auch einen Funktionsaufruf zum Senden einer E-Mail verwendet, wenn Sie dies versuchen (DRY-Ansatz).

rreddy
quelle