Jak wysłać wiadomość e-mail za pomocą Pythona?

169

Ten kod działa i dobrze wysyła mi 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()

Jeśli jednak spróbuję zawinąć go w taką funkcję:

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()

i nazwij to otrzymuję następujące błędy:

 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

Czy ktoś może mi pomóc zrozumieć, dlaczego?

chmura311
źródło
2
jak nazywasz tę funkcję?
Jochen Ritzel,
Czy opublikowane wcięcie jest takie samo jak w pliku?
gddc
@gddc no Upewniłem się, że wcięcie jest prawidłowe, tak to właśnie
wkleiłem
Wywołuję tę funkcję, importując ją do mojego modułu głównego i przekazując zdefiniowane przeze mnie parametry.
cloud311
4
Chociaż sugestia @ Arrieta dotycząca korzystania z pakietu e-mail jest najlepszym sposobem rozwiązania tego problemu, Twoje podejście może zadziałać. Różnice między twoimi dwiema wersjami są w ciągu: (1) jak wskazuje @NickODell, w wersji funkcji masz wiodące białe spacje. Nagłówki nie powinny mieć początkowej spacji (chyba że są opakowane). (2) chyba, że ​​TEKST zawiera wiodącą pustą linię, utraciłeś separator między nagłówkami a treścią.
Tony Meyer

Odpowiedzi:

191

Polecam korzystać ze standardowych pakietów emaili smtplibwspólnie wysyłać e-maile. Proszę spojrzeć na następujący przykład (odtworzony z dokumentacji Pythona ). Zauważ, że jeśli zastosujesz to podejście, „proste” zadanie jest rzeczywiście proste, a bardziej złożone zadania (takie jak dołączanie obiektów binarnych lub wysyłanie zwykłych / wieloczęściowych wiadomości HTML) są wykonywane bardzo szybko.

# 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()

Aby wysłać wiadomość e-mail do wielu miejsc docelowych, możesz również postępować zgodnie z przykładem w dokumentacji Pythona :

# 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()

Jak widać, nagłówek Tow MIMETextobiekcie musi być ciąg składający się z adresów e-mail oddzielonych przecinkami. Z drugiej strony drugi argument dosendmail funkcji musi być lista ciągów (każdy ciąg to adres e-mail).

Tak więc, jeśli masz trzy adresy e-mail: [email protected], [email protected], i [email protected], można to zrobić w następujący sposób (oczywiste sekcje pominięte):

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

",".join(to)część sprawia, że jeden ciąg z listy, oddzielonych przecinkami.

Z twoich pytań wynika, że ​​nie przeszedłeś przez samouczek Pythona - jest to KONIECZNOŚĆ, jeśli chcesz dostać się gdziekolwiek w Pythonie - dokumentacja jest w większości doskonała dla standardowej biblioteki.

Escualo
źródło
1
Dziękuję, że działa to bardzo ładnie z poziomu funkcji, ale jak mogę wysłać do wielu odbiorców? Ponieważ msg [to] wygląda na klucz słownikowy, próbowałem oddzielić msg [to] średnikiem, ale to nie działa.
cloud311
1
@ cloud311 dokładnie tak, jak masz w kodzie. Chce łańcuch oddzielony przecinkami: ", ".join(["[email protected]", "[email protected]"])
Tim McNamara,
3
Zwróć też uwagę, że nagłówek Do: ma inną semantykę niż adresat w danych koperty. Na przykład możesz użyć adresu „Tony Meyer” <[email protected]> ”jako adresu w nagłówku Do:, ale adresatem w danych koperty musi być tylko„ [email protected] ”. Aby utworzyć „ładny” adres To:, użyj adresu email.utils.formataddr, na przykład email.utils.formataddr („Tony Meyer”, „[email protected]”).
Tony Meyer
1
Mała poprawa: plik powinien być otwierany za pomocą with: with open(textfile, 'rb') as fp:. Można porzucić jawne zamknięcie, ponieważ withblok zamknie plik, nawet jeśli wystąpi w nim błąd.
jpmc26
1
Nie jest to specyficzne dla tej odpowiedzi, ale łącząc się z dowolnym serwerem SMTP, nad którym nie kontrolujesz, powinieneś wziąć pod uwagę możliwość, że jest on nieosiągalny, wolny, odrzuca połączenia lub cokolwiek innego. Jeśli chodzi o kod, otrzymasz wyjątek, ale potem musisz znaleźć sposób, aby ponownie spróbować wysłać wiadomość nieco później. Jeśli rozmawiasz z własnym sendmail / postfix, zajmie się on ponownym wysłaniem za Ciebie.
Ralph Bolton,
66

Cóż, chcesz mieć odpowiedź, która jest aktualna i nowoczesna.

Oto moja odpowiedź:

Kiedy muszę wysyłać pocztę w Pythonie, używam API mailgun, które powoduje wiele problemów związanych z wysyłaniem poczty. Mają cudowną aplikację / interfejs API, która umożliwia bezpłatne wysyłanie 10000 e-maili miesięcznie.

Wysłanie wiadomości e-mail wyglądałoby tak:

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!"})

Możesz także śledzić zdarzenia i wiele więcej, zobacz przewodnik Szybki start .

Mam nadzieję, że okaże się to przydatne!

Dennis Decoene
źródło
3
To jest rzeczywiście dużo bardziej „z tej daty”. Chociaż używa zewnętrznego interfejsu API. Osobiście użyłbym czegoś takiego jak yagmail, aby zachować to wewnętrzne.
PascalVKooten
6
@PascalvKooten Absolutnie zabawne śledzenie ciągłych reklam yagmaila (tak, proszę pana, rozważę to następnym razem;). Uważam jednak za bardzo zagmatwane, że prawie nikt nie przejmuje się kwestią PO, a raczej sugeruje znacznie inne rozwiązania. To tak, jakbym pytał jak
wymienić
Świetnie, że moja misja ma dla niektórych wartość rozrywkową.
PascalVKooten
4
@flaschbier Powodem, dla którego nikogo nie obchodzi kwestia PO, jest zły tytuł. „Jak wysłać wiadomość e-mail za pomocą Pythona?” jest faktycznym powodem, dla którego ludzie szukają odpowiedzi, gdy klikną to pytanie, i oczekiwaliby odpowiedzi, której może udzielić yagmail : ładnej i krótkiej. Proszę bardzo. Więcej reklamy yagmail.
PascalVKooten
1
Wystarczy powiedzieć, że dla klientów Mailgun wysyłanie poczty przez usługi sieciowe jest znacznie bardziej przyjazne dla przepustowości niż przez SMTP (zwłaszcza jeśli używasz załączników).
Ralph Bolton,
44

Chciałbym pomóc Ci w wysyłaniu e-maili, doradzając pakiet yagmail (jestem opiekunem, przepraszam za reklamy, ale czuję, że to naprawdę może pomóc!).

Cały kod byłby dla Ciebie:

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

Zauważ, że podaję wartości domyślne dla wszystkich argumentów, na przykład, jeśli chcesz wysłać do siebie, możesz pominąć TO, jeśli nie chcesz, możesz go również pominąć.

Ponadto celem jest również ułatwienie dołączania kodu HTML lub obrazów (i innych plików).

Tam, gdzie umieścisz zawartość, możesz zrobić coś takiego:

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, jak łatwo jest wysyłać załączniki! To zajęłoby około 20 linii bez yagmaila;)

Ponadto, jeśli skonfigurujesz je raz, nigdy nie będziesz musiał ponownie wprowadzać hasła (i bezpiecznie je przechowywać). W twoim przypadku możesz zrobić coś takiego:

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

co jest o wiele bardziej zwięzłe!

Zapraszam do obejrzenia github lub zainstalowania go bezpośrednio za pomocą pip install yagmail.

PascalVKooten
źródło
4
to powinno być najlepsze rozwiązanie.
Richard de Ree,
1
OMG, to słodki, PROSTY moduł poczty e-mail. Dzięki!
pepoluan
1
Czy mogę używać yagmailinnego niż Gmaila? Próbuję użyć własnego serwera SMTP.
Anirban Nag 'tintinmj'
@dtgq Dziękuję za troskę. Osobiście nie widzę wektora ataku. Jeśli ktoś ma zamiar zmienić plik pod ścieżką, którą chcesz wysłać, nie ma znaczenia, czy masz Attachmentklasę; to wciąż to samo. Jeśli mogą zmienić twój kod, to i tak mogą zrobić wszystko, co chcą (z / bez roota, to jest to samo, co wysyłanie e-maili). Wydaje mi się, że to typowe „jest wygodne / magiczne, więc musi być mniej bezpieczne”. Ciekaw jestem, jakie prawdziwe zagrożenie widzisz?
PascalVKooten
14

Występuje problem z wcięciami. Poniższy kod zadziała:

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
źródło
3
@geotheory SERVERzmienną przekazywaną do funkcji są poświadczenia użytkownika.
Użytkownik
Ręczne składanie razem prawidłowej wiadomości SMTP za pomocą prostej interpolacji ciągów znaków nie jest zalecane, a Twoja odpowiedź zawiera błąd, który doskonale to ilustruje. (Treść musi zaczynać się od pustej linii, aby była to poprawna wiadomość). W przypadku wszystkiego, co dotyczy zestawów znaków innych niż zwykły tekst w 7-bitowym języku US-ASCII z lat 80. tylko w języku angielskim (załączniki, internacjonalizacja i inne wsparcie MIME) naprawdę chcę użyć biblioteki, która obsługuje te rzeczy. emailBiblioteka Pythona robi to dość dobrze (szczególnie od 3.6), chociaż nadal wymaga pewnego zrozumienia tego, co robisz.
tripleee
7

Oto przykład w Pythonie 3.x, znacznie prostszy niż 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.')

wywołaj tę funkcję:

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

poniżej może tylko dla chińskiego użytkownika:

Jeśli używasz 126/163, 网易 邮箱, musisz ustawić „客户 端 授权 密码”, jak poniżej:

wprowadź opis obrazu tutaj

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

Pasować
źródło
4

Podczas wcięcia kodu w funkcji (co jest w porządku), wciąłeś również wiersze nieprzetworzonego ciągu wiadomości. Ale wiodące białe znaki oznaczają zwijanie (konkatenację) wierszy nagłówka, jak opisano w sekcjach 2.2.3 i 3.2.3 RFC 2822 - Internet Message Format :

Każde pole nagłówka jest logicznie pojedynczym wierszem znaków składającym się z nazwy pola, dwukropka i treści pola. Jednak dla wygody i aby poradzić sobie z ograniczeniami 998/78 znaków na wiersz, część treści pola pola nagłówka może zostać podzielona na reprezentację wielu wierszy; nazywa się to „składaniem”.

W formie funkcji sendmailwywołania wszystkie linie rozpoczynają się od spacji, więc są „rozwijane” (łączone) i próbujesz wysłać

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

Inaczej niż sugeruje nasz umysł, smtplibnie zrozumie już nagłówków To:i Subject:, ponieważ te nazwy są rozpoznawane tylko na początku wiersza. Zamiast tego smtplibprzyjmie bardzo długi adres e-mail nadawcy:

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

To nie zadziała, więc przychodzi Twój wyjątek.

Rozwiązanie jest proste: po prostu zachowaj messagesznurek tak, jak był wcześniej. Można to zrobić za pomocą funkcji (jak sugerował Zeeshan) lub od razu w kodzie źródłowym:

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()

Teraz rozwijanie nie następuje i wysyłasz

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

This message was sent with Python's smtplib.

co działa i co zostało zrobione przez twój stary kod.

Zauważ, że zachowałem również pustą linię między nagłówkami i treścią, aby pomieścić sekcję 3.5 specyfikacji RFC (która jest wymagana) i umieściłem dołączenie poza funkcją zgodnie z przewodnikiem stylu Pythona PEP-0008 (który jest opcjonalny).

flaschbier
źródło
3

Prawdopodobnie umieszcza w wiadomości tabulatory. Wydrukuj wiadomość, zanim przekażesz ją do sendMail.

Nick ODell
źródło
1

Upewnij się, że nadawca i odbiorca mają zezwolenie na wysyłanie i odbieranie wiadomości e-mail z nieznanych źródeł (źródeł zewnętrznych) na koncie e-mail.

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)

wprowadź opis obrazu tutaj

Devanand Sharma
źródło
msgnie jest prawidłową wiadomością SMTP i po prostu zniknie w eterze, jeśli Twój serwer pocztowy ją zaakceptuje.
tripleee
0

Pomyślałem, że umieściłem tutaj moje dwa bity, ponieważ właśnie zorientowałem się, jak to działa.

Wygląda na to, że nie masz portu określonego w ustawieniach połączenia SERWERA, co trochę mnie wywarło, gdy próbowałem połączyć się z moim serwerem SMTP, który nie używa domyślnego portu: 25.

Zgodnie z dokumentacją smtplib.SMTP, żądanie / odpowiedź ehlo lub helo powinno być automatycznie obsługiwane, więc nie powinieneś się tym martwić (ale może być coś do potwierdzenia, jeśli wszystko inne zawiedzie).

Inną kwestią, którą należy sobie zadać, jest to, czy zezwoliłeś na połączenia SMTP na samym serwerze SMTP? W przypadku niektórych witryn, takich jak GMAIL i ZOHO, musisz faktycznie wejść i aktywować połączenia IMAP na koncie e-mail. Być może Twój serwer pocztowy nie zezwala na połączenia SMTP, które nie pochodzą z „localhost”? Coś, na co warto zwrócić uwagę.

Ostatnią rzeczą jest to, że możesz spróbować zainicjować połączenie w TLS. Większość serwerów wymaga teraz tego typu uwierzytelniania.

Zobaczysz, że umieściłem dwa pola TO w moim e-mailu. Elementy słownika msg ['DO'] i msg ['FROM'] msg umożliwiają wyświetlanie poprawnych informacji w nagłówkach wiadomości e-mail, które można zobaczyć na końcu wiadomości e-mail w polach Do / Od (Ty może nawet uda się dodać tutaj pole Odpowiedz do. Same pola DO i OD są tym, czego wymaga serwer.Wiem, że słyszałem o niektórych serwerach pocztowych odrzucających wiadomości e-mail, jeśli nie mają odpowiednich nagłówków wiadomości e-mail.

Oto kod, którego użyłem w funkcji, która działa dla mnie, aby wysłać e-mailem zawartość pliku * .txt przy użyciu mojego lokalnego komputera i zdalnego serwera SMTP (ZOHO, jak pokazano):

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
źródło
0

Warto zauważyć, że moduł SMTP obsługuje menedżera kontekstu, więc nie ma potrzeby ręcznego wywoływania quit (), co gwarantuje, że jest wywoływany zawsze, nawet jeśli jest wyjątek.

    with smtplib.SMTP_SSL('smtp.gmail.com', 465) as server:
        server.ehlo()
        server.login(user, password)
        server.sendmail(from, to, body)
Peter C.
źródło
-1

Jeśli chodzi o twój kod, wydaje się, że nie ma w nim nic zasadniczo złego, poza tym, że nie jest jasne, w jaki sposób faktycznie wywołujesz tę funkcję. Wszystko, o czym mogę pomyśleć, to to, że gdy twój serwer nie odpowiada, otrzymasz ten błąd SMTPServerDisconnected. Jeśli spojrzysz na funkcję getreply () w smtplib (fragment poniżej), zobaczysz pomysł.

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.
    """

sprawdź przykład na https://github.com/rreddy80/sendEmails/blob/master/sendEmailAttachments.py, który również używa wywołania funkcji do wysyłania wiadomości e-mail, jeśli to jest to, co próbujesz zrobić (podejście DRY).

rreddy
źródło