Как отправить письмо с Python?

170

Этот код работает и отправляет мне электронное письмо просто отлично:

import smtplib
#SERVER = "localhost"

FROM = 'monty@python.com'

TO = ["jon@mycompany.com"] # 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()

Однако, если я попытаюсь обернуть его в такую ​​функцию:

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

и позвонив, я получаю следующие ошибки:

 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

Может кто-нибудь помочь мне понять, почему?

cloud311
источник
2
как вы вызываете функцию?
Йохен Ритцель
Отпечаток, который вы разместили, такой же, как у вас в файле?
GDCD
@gddc Нет, я сделал правильный отступ, именно так я его и вставил.
cloud311
Я вызываю функцию, импортируя ее в мой основной модуль и передавая в нее параметры, которые я определил.
cloud311
4
Хотя предложение @ Arrieta использовать пакет электронной почты является лучшим способом решения этой проблемы, ваш подход может работать. Различия между вашими двумя версиями заключаются в строке: (1) как указывает @NickODell, у вас есть пробел в версии функции. Заголовки не должны иметь начального пробела (если они не обернуты). (2) если TEXT не содержит начальную пустую строку, вы потеряли разделитель между заголовками и телом.
Тони Мейер

Ответы:

192

Я рекомендую вам использовать стандартные пакеты emailи smtplibвместе отправлять электронную почту. Пожалуйста, посмотрите на следующий пример (воспроизведенный из документации Python ). Обратите внимание, что если следовать этому подходу, «простая» задача действительно проста, а более сложные задачи (например, присоединение двоичных объектов или отправка простых / HTML многокомпонентных сообщений) выполняются очень быстро.

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

Для отправки электронной почты нескольким получателям вы также можете следовать примеру в документации по Python :

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

Как вы можете видеть, заголовок Toв MIMETextобъекте должна быть строка , состоящая из адресов электронной почты , разделенных запятыми. С другой стороны, вторым аргументом sendmailфункции должен быть список строк (каждая строка является адресом электронной почты).

Итак, если у вас есть три адреса электронной почты: person1@example.com, person2@example.comи person3@example.com, вы можете сделать следующее (очевидные разделы опущены):

to = ["person1@example.com", "person2@example.com", "person3@example.com"]
msg['To'] = ",".join(to)
s.sendmail(me, to, msg.as_string())

",".join(to)часть составляет одну строку из списка, разделенных запятыми.

Из ваших вопросов я понимаю, что вы не прошли учебник по Python - это ОБЯЗАТЕЛЬНО, если вы хотите получить что-нибудь в Python - документация в основном превосходна для стандартной библиотеки.

Escualo
источник
1
Спасибо, это работает очень хорошо изнутри функции, однако как я могу отправить нескольким получателям? Так как msg [to] выглядит как ключ словаря, я попытался отделить msg [to] точкой с запятой, но это, похоже, не работает.
cloud311
1
@ cloud311 именно так, как у вас есть в вашем коде. Он хочет строку, разделенную запятыми: ", ".join(["a@example.com", "b@example.net"])
Тим Макнамара
3
Также обратите внимание, что заголовок To: имеет другую семантику, чем получатель конверта. Например, вы можете использовать «Tony Meyer» <tony.meyer@gmail.com> »в качестве адреса в заголовке To:, но получатель конверта должен быть только« tony.meyer@gmail.com ». Чтобы создать «хороший» адрес To:, используйте email.utils.formataddr, например, email.utils.formataddr («Тони Мейер», «tony.meyer@gmail.com»).
Тони Мейер
2
Небольшие улучшения: файл должен быть открыт с помощью with: with open(textfile, 'rb') as fp:. Явное закрытие может быть отброшено, так как withблок закроет файл, даже если в нем произойдет ошибка.
jpmc26
1
Не относится к этому ответу, но при подключении к любому SMTP-серверу, который вы не контролируете, следует учитывать возможность того, что он недоступен, медлен, отклоняет соединения или что-то еще. В коде вы получите исключение, но вам нужно будет найти способ повторить попытку отправки чуть позже. Если вы говорите со своим собственным sendmail / postfix, то он позаботится об этой повторной отправке для вас.
Ральф Болтон
67

Ну, вы хотите получить ответ, который был бы современным и современным.

Вот мой ответ:

Когда мне нужно отправить почту в python, я использую mailgun API, который доставляет много головной боли при пересылке почты. У них есть замечательное приложение / API, которое позволяет отправлять 10000 электронных писем в месяц бесплатно.

Отправка электронного письма будет выглядеть так:

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": ["bar@example.com", "YOU@YOUR_DOMAIN_NAME"],
              "subject": "Hello",
              "text": "Testing some Mailgun awesomness!"})

Вы также можете отслеживать события и многое другое, см. Краткое руководство .

Я надеюсь, что вы найдете это полезным!

Деннис Декоен
источник
3
Это действительно намного больше "этой даты". Хотя он использует внешний API. Лично я бы использовал что-то вроде yagmail для хранения его внутри.
PascalVKooten
6
@PascalvKooten Абсолютно забавно следить за вашей постоянной рекламой yagmail (да, сэр, я рассмотрю это в следующий раз, сэр;). Но меня очень смущает то, что почти никто не заботится о проблеме ОП, а скорее предлагает разные решения. Как будто я спрашиваю, как поменять лампочки в моем
смарте
Здорово, что моя миссия имеет некоторую ценность для некоторых.
PascalVKooten
4
@flaschbier Причина, по которой никого не волнует проблема ОП, заключается в том, что название неверно. "Как отправить электронное письмо с Python?" это реальная причина, по которой люди приходят посмотреть, когда нажимают на этот вопрос, и ожидают ответа, который может дать yagmail : хороший и короткий. Вот и ты. Более yagmail реклама.
PascalVKooten
1
Просто чтобы сказать, что для клиентов Mailgun отправка почты через веб-сервисы значительно более благоприятна для полосы пропускания, чем через SMTP (особенно если используются какие-либо вложения).
Ральф Болтон
44

Я хотел бы помочь вам с отправкой электронных писем, посоветовав пакет yagmail (я сопровождающий, извините за рекламу, но я чувствую, что это действительно может помочь!).

Весь код для вас будет:

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

Обратите внимание, что я предоставляю значения по умолчанию для всех аргументов, например, если вы хотите отправить себе, вы можете опустить TO, если вы не хотите тему, вы также можете опустить ее.

Кроме того, цель также состоит в том, чтобы действительно легко прикрепить HTML-код или изображения (и другие файлы).

Где вы размещаете содержимое, вы можете сделать что-то вроде:

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']

Ух, как легко отправлять вложения! Это заняло бы как 20 строк без yagmail;)

Кроме того, если вы настроите его один раз, вам больше никогда не придется вводить пароль (и хранить его безопасно). В вашем случае вы можете сделать что-то вроде:

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

что гораздо лаконичнее!

Я бы пригласил вас взглянуть на github или установить его напрямую pip install yagmail.

PascalVKooten
источник
4
это должно быть лучшим решением.
Ричард де Ри
1
О боже, это приятный, простой модуль электронной почты. Спасибо!
pepoluan
1
Могу ли я использовать yagmailне Gmail? Я пытаюсь использовать для моего собственного сервера SMTP.
Анирбан Наг 'tintinmj'
@dtgq Спасибо за вашу заботу. Лично я не вижу вектор атаки. Если кто-то собирается изменить файл по пути, который вы хотите отправить, то не имеет значения, есть ли у вас Attachmentкласс; это все то же самое. Если они могут изменить ваш код, то они могут делать что угодно в любом случае (с / без root, это то же самое при отправке электронной почты) Это мне кажется типичным «это удобно / волшебно, поэтому должно быть менее безопасно». Мне любопытно, какую реальную угрозу вы видите?
PascalVKooten
14

Есть проблема с отступами. Код ниже будет работать:

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
источник
3
@geotheory SERVERпеременная, которая передается в функцию - это учетные данные пользователя.
Пользователь
Ручное объединение действительного SMTP-сообщения с простой интерполяцией строк не рекомендуется, и в вашем ответе есть ошибка, которая прекрасно это иллюстрирует. (Тело должно начинаться с пустой строки, чтобы это сообщение было действительным.) Для всего, что связано с наборами символов, кроме обычного текста в 7-битном US-ASCII в 1980-х годах только на английском языке (вложения, интернационализация и другая поддержка MIME), вы очень хочу использовать библиотеку, которая обрабатывает эти вещи. Библиотека Python emailделает это достаточно хорошо (особенно начиная с версии 3.6), хотя все еще требует некоторого понимания того, что вы делаете.
tripleee
7

Вот пример на Python 3.x, намного проще, чем 2.x:

import smtplib
from email.message import EmailMessage
def send_mail(to_email, subject, message, server='smtp.example.cn',
              from_email='xx@example.com'):
    # 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.')

вызвать эту функцию:

send_mail(to_email=['12345@qq.com', '12345@126.com'],
          subject='hello', message='Your analysis has done!')

ниже может только для китайского пользователя:

Если вы используете 126/163, 网易 邮箱, вам нужно установить «客户 端 授权 密码», как показано ниже:

введите описание изображения здесь

ссылка: https://stackoverflow.com/a/41470149/2803344 https://docs.python.org/3/library/email.examples.html#email-examples

Belter
источник
4

Делая отступ в своем коде в функции (что нормально), вы также делали отступ строки необработанного сообщения. Но начальный пробел подразумевает свертывание (конкатенацию) строк заголовка, как описано в разделах 2.2.3 и 3.2.3 RFC 2822 - Формат интернет-сообщения :

Каждое поле заголовка логически представляет собой одну строку символов, содержащую имя поля, двоеточие и тело поля. Для удобства, однако, и для того, чтобы справиться с ограничениями в 998/78 символов на строку, часть тела поля поля заголовка может быть разбита на многострочное представление; это называется «складывание».

В функциональной форме вашего sendmailвызова все строки начинаются с пробела и поэтому «развернуты» (объединены), и вы пытаетесь отправить

From: monty@python.com    To: jon@mycompany.com    Subject: Hello!    This message was sent with Python's smtplib.

Кроме наш разум говорит, smtplibне будет понимать To:и Subject:заголовки больше, потому что эти имена распознаются только в начале строки. Вместо этого smtplibпримет очень длинный адрес электронной почты отправителя:

monty@python.com    To: jon@mycompany.com    Subject: Hello!    This message was sent with Python's smtplib.

Это не сработает, и поэтому приходит ваше исключение.

Решение простое: просто сохраните messageстроку, как это было раньше. Это можно сделать с помощью функции (как предложил Zeeshan) или сразу в исходном коде:

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

Теперь разворачивание не происходит и вы отправляете

From: monty@python.com
To: jon@mycompany.com
Subject: Hello!

This message was sent with Python's smtplib.

что работает, и что было сделано вашим старым кодом.

Обратите внимание, что я также сохранял пустую строку между заголовками и телом для размещения раздела 3.5 RFC (который требуется) и помещал включение вне функции в соответствии с руководством по стилю Python PEP-0008 (которое является необязательным).

flaschbier
источник
3

Это, вероятно, вкладки в ваше сообщение. Распечатайте сообщение, прежде чем передать его sendMail.

Ник Оделл
источник
1

Убедитесь, что вы предоставили разрешение отправителю и получателю отправлять электронную почту и получать электронную почту из неизвестных источников (внешних источников) в учетной записи электронной почты.

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)

введите описание изображения здесь

Девананд Шарма
источник
msgне является действительным SMTP-сообщением и просто исчезнет в эфире, если ваш почтовый сервер его примет.
tripleee
0

Думал, что я вставлю свои две части здесь, так как я только что понял, как это работает.

Похоже, что у вас нет порта, указанного в настройках соединения с SERVER, это немного повлияло на меня, когда я пытался подключиться к своему SMTP-серверу, который не использует порт по умолчанию: 25.

В соответствии с документацией smtplib.SMTP ваш запрос / ответ на ehlo или helo должен автоматически обрабатываться, поэтому вам не нужно об этом беспокоиться (но это может быть чем-то, что может подтвердить, если ничего не получится).

Еще один вопрос: разрешены ли SMTP-соединения на самом SMTP-сервере? Для некоторых сайтов, таких как GMAIL и ZOHO, вы должны войти и активировать соединения IMAP в учетной записи электронной почты. Ваш почтовый сервер может не разрешать SMTP-подключения, которые не приходят от 'localhost', возможно? Что-то, чтобы посмотреть.

Последнее, что вы можете попробовать установить соединение по TLS. Большинство серверов сейчас требуют такой тип аутентификации.

Вы увидите, что я забил два поля TO в своем письме. Элементы словаря msg ['TO'] и msg ['FROM'] позволяют отображать правильную информацию в заголовках самого письма, которое видно на принимающей стороне письма в полях «Кому / От» (вы здесь можно даже добавить поле «Ответить». Сами поля «TO» и «FROM» - это то, что требуется серверу. Я знаю, что некоторые почтовые серверы отклоняют электронную почту, если у них нет соответствующих заголовков электронной почты.

Это код, который я использовал в функции, которая работает для отправки по электронной почте содержимого файла * .txt с использованием моего локального компьютера и удаленного SMTP-сервера (ZOHO, как показано):

def emailResults(folder, filename):

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

    # headers
    TO = 'to_user@domain.com'
    msg['To'] = TO
    FROM = 'from_user@domain.com'
    msg['From'] = FROM
    msg['Subject'] = 'email subject |' + filename

    # SMTP
    send = smtplib.SMTP('smtp.zoho.com', 587)
    send.starttls()
    send.login('from_user@domain.com', 'password')
    send.sendmail(FROM, TO, msg.as_string())
    send.quit()
ntk4
источник
0

Стоит отметить, что модуль SMTP поддерживает диспетчер контекста, поэтому нет необходимости вручную вызывать quit (), это гарантирует, что он вызывается всегда, даже если есть исключение.

    with smtplib.SMTP_SSL('smtp.gmail.com', 465) as server:
        server.ehlo()
        server.login(user, password)
        server.sendmail(from, to, body)
Питер С
источник
-1

Что касается вашего кода, то, похоже, в этом нет ничего принципиально неправильного, за исключением того, что неясно, как вы на самом деле вызываете эту функцию. Все, что я могу думать, это то, что когда ваш сервер не отвечает, вы получите эту ошибку SMTPServerDisconnected. Если вы посмотрите функцию getreply () в smtplib (отрывок ниже), вы получите представление.

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

посмотрите пример на https://github.com/rreddy80/sendEmails/blob/master/sendEmailAttachments.py , где также используется вызов функции для отправки электронного письма, если это то, что вы пытаетесь сделать (подход DRY).

rreddy
источник