Як надіслати електронний лист із Python?


169

Цей код працює і чудово надсилає мені електронний лист:

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

Хтось може мені допомогти зрозуміти, чому?


2
як ви називаєте функцію?
Jochen Ritzel

Чи відступ, який ви розмістили, такий, як у вашому файлі?
gddc

@gddc ні, я переконався, що правильно відступають, це якраз я вставив його.
cloud311

Я викликаю функцію, імпортуючи її в свій основний модуль і передаючи в неї параметри, які я визначив.
cloud311

4
Хоча пропозиція @ Аррієти використовувати електронний пакет - найкращий спосіб вирішити це, ваш підхід може працювати. Відмінності між вашими двома версіями полягають у рядку: (1), як вказує @NickODell, у вас є провідна пробіл у версії функції. У заголовках не повинно бути провідного місця (якщо вони не завернуті). (2) якщо TEXT не містить пустий рядок, ви втратили роздільник між заголовками та тілом.
Тоні Мейєр

Відповіді:


191

Я рекомендую використовувати стандартні пакети 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 - документація здебільшого відмінна для стандартної бібліотеки.


1
Дякую, що це дуже добре працює в межах функції, але як я можу надіслати декілька одержувачів? Оскільки msg [to] виглядає як ключ до словника, я спробував розділити msg [to] крапкою з комою, але це, здається, не працює.
cloud311

1
@ cloud311 саме так, як це є у вашому коді. Він хоче, щоб рядок був розділений комами: ", ".join(["a@example.com", "b@example.net"])
Тім Макнамара

3
Зауважте також, що заголовок To: має іншу семантику, ніж одержувач конверту. Наприклад, ви можете використовувати "" Тоні Мейєр "<tony.meyer@gmail.com>" як адресу в заголовку Кому: але одержувач конверта повинен бути лише "tony.meyer@gmail.com". Щоб створити "приємну" адресу: використовуйте email.utils.formataddr, як-от email.utils.formataddr ("Тоні Меєр", "tony.meyer@gmail.com").
Тоні Мейєр

1
Невеликі поліпшення: файл повинен бути відкритий з допомогою with: with open(textfile, 'rb') as fp:. Явне закриття може бути відхилено, оскільки withблок закриє файл, навіть якщо в ньому виникає помилка.
jpmc26

1
Не характерно для цієї відповіді, але під час підключення до будь-якого SMTP-сервера, яким ви не керуєте, слід враховувати можливість того, що воно недоступне, повільне, відхиляє з'єднання чи будь-що інше. Виходячи з коду, ви отримаєте виняток, але тоді вам потрібно знайти спосіб повторити спробу надсилання трохи пізніше. Якщо ви розмовляєте зі своїм власним sendmail / postfix, то він подбає про це повторне надсилання для вас.
Ральф Болтон

66

Ну, ви хочете мати відповідь, яка є сучасною та сучасною.

Ось моя відповідь:

Коли мені потрібно відправляти пошту в python, я використовую 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 (так, сер, я буду розглядати це наступного разу, сер;). Але я вважаю дуже заплутаним те, що, здається, майже ніхто не піклується про питання ОП, а пропонує швидше різні рішення. Це як би я запитую, як поміняти лампочки в моєму смарт-2009, і відповідь: Купуйте справжній Mercedes ...
flaschbier

Чудово, що моя місія має для деяких цікаве значення.
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.


4
це має бути найкращим рішенням.
Річард де Рій

1
OMG, це милий, ПРОСТИЙ модуль електронної пошти. Дякую!
pepoluan

1
Чи можу я використовувати yagmailінший, ніж gmail? Я намагаюся використовувати для власного SMTP-сервера.
Anirban Nag 'tintinmj'

@dtgq Дякую за турботу. Особисто я не бачу вектора нападу. Якщо хтось збирається змінити файл під шлях, який ви хочете надіслати, то не має значення, чи є у вас Attachmentклас; це все одно те саме. Якщо вони можуть змінити ваш код, вони можуть робити все, що завгодно (з коренем / без кореня, це те саме, що надсилається електронна пошта Wrt). Мені це здається типовим "це зручно / магічно, тому він повинен бути менш захищеним". Мені цікаво, яку реальну загрозу ви бачите?
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()


3
@geotheory SERVERЗмінна, яка передається у функцію, - це облікові дані користувача.
Користувач

Вручну розміщувати дійсне повідомлення SMTP з простою інтерполяцією рядків не рекомендується, і у вашій відповіді є помилка, яка це ідеально ілюструє. (Тіло потрібно починати з порожнього рядка, щоб це було дійсним повідомленням.) Для всього, що стосується наборів символів, крім звичайного тексту, 1980-х 7-розрядний лише англійською мовою US-ASCII (вкладення, інтернаціоналізація та інша підтримка MIME) дуже хочеться використовувати бібліотеку, яка обробляє цей матеріал. Бібліотека Python emailробить це досить добре (особливо з 3.6), хоча все ще потребує певного розуміння того, що ви робите.
трійка

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


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рядок, як це було раніше. Це можна зробити за допомогою функції (як запропонував Зеешан) або відразу у вихідному коді:

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 (що є необов'язковим).


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-повідомленням і просто видається, що зникне в ефірі, якщо ваш поштовий сервер прийме його.
трійка

0

Думав, я би вклав тут свої два шматочки, оскільки я тільки зрозумів, як це працює.

Здається, що у налаштуваннях підключення до SERVER у вас немає порту, це трохи вплинуло на мене, коли я намагався підключитися до свого SMTP-сервера, який не використовує порт за замовчуванням: 25.

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

Інша справа, щоб запитати себе, чи дозволено вам підключення SMTP на самому вашому SMTP-сервері? Для деяких сайтів, таких як GMAIL та ZOHO, вам потрібно фактично зайти та активувати з'єднання IMAP в обліковому записі електронної пошти. Ваш поштовий сервер може не дозволити з'єднання SMTP, які, можливо, не надходять із "localhost"? Щось заглянути.

Останнє - ви можете спробувати ініціювати з'єднання на TLS. Зараз більшість серверів вимагає такого типу аутентифікації.

Ви побачите, що я застряг два поля TO у своєму електронному листі. Елементи словника msg ['TO'] та msg ['FROM'] дозволяють відображати правильну інформацію в заголовках самого електронного листа, які бачать на кінці отримання повідомлення в полях To / 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()

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 підхід).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.