Отправка электронной почты с помощью Python

Оглавление

Вероятно, вы нашли этот учебник, потому что хотите отправлять электронные письма с помощью Python. Возможно, вы хотите получать напоминания по электронной почте из вашего кода, отправлять пользователям письмо с подтверждением при создании учетной записи или отправлять письма членам вашей организации, чтобы напомнить им об уплате взносов. Отправка электронных писем вручную - это трудоемкая и чреватая ошибками задача, но ее легко автоматизировать с помощью Python.

В этом уроке вы узнаете, как:

  • Установите безопасное соединение с помощью SMTP_SSL() и .starttls()

  • Используйте встроенную библиотеку Python smtplib для отправки базовой электронной почты

  • Отправляйте электронные письма с HTML-содержимым и вложениями с помощью пакета email

  • Отправка нескольких персонализированных электронных писем с использованием CSV-файла с контактными данными

  • Используйте пакет Yagmail для отправки электронной почты через учетную запись Gmail, используя всего несколько строк кода

В конце этого руководства вы найдете несколько сервисов транзакционной электронной почты, которые пригодятся вам, когда вы захотите отправить большое количество писем.

Начало работы

Python поставляется со встроенным smtplib модулем для отправки электронной почты с использованием протокола Simple Mail Transfer Protocol (SMTP). smtplib использует протокол RFC 821 для SMTP. В примерах этого руководства для отправки электронной почты используется SMTP-сервер Gmail, но те же принципы применимы и к другим почтовым службам. Хотя большинство поставщиков услуг электронной почты используют те же порты подключения, что и в этом учебнике, вы можете выполнить быстрый поиск Google для подтверждения своего.

Чтобы начать работу с этим учебником, создайте учетную запись Gmail для разработки или создайте отладочный SMTP-сервер, который отбрасывает отправленные вами письма и вместо этого печатает их в командной строке. Оба варианта описаны ниже. Локальный отладочный SMTP-сервер может быть полезен для устранения любых проблем с функциональностью электронной почты и обеспечения того, что ваши функции электронной почты не содержат ошибок, прежде чем отправлять какие-либо письма.

Вариант 1: Настройка учетной записи Gmail для разработки

Если вы решите использовать учетную запись Gmail для отправки писем, я настоятельно рекомендую завести запасную учетную запись для разработки кода. Это связано с тем, что вам придется настроить параметры безопасности аккаунта Gmail, чтобы разрешить доступ к нему из кода Python, а также с тем, что есть вероятность случайного раскрытия ваших регистрационных данных. Кроме того, я обнаружил, что почтовый ящик моего тестового аккаунта быстро заполняется тестовыми письмами, что является достаточной причиной для создания нового аккаунта Gmail для разработки.

Приятной особенностью Gmail является то, что вы можете использовать знак + для добавления любых модификаторов к вашему адресу электронной почты, прямо перед знаком @. Например, почта, отправленная на адреса my+person1@gmail.com и my+person2@gmail.com, будет приходить на адрес my@gmail.com. При тестировании функциональности электронной почты вы можете использовать это для эмуляции нескольких адресов, указывающих на один и тот же почтовый ящик.

Чтобы установить адрес Gmail для тестирования вашего кода, сделайте следующее:

Если вы не хотите снижать настройки безопасности своего аккаунта Gmail, ознакомьтесь с документацией Google о том, как получить учетные данные доступа для вашего скрипта Python, используя механизм авторизации OAuth2.

Вариант 2: Настройка локального SMTP-сервера

Вы можете проверить функциональность электронной почты, запустив локальный отладочный SMTP-сервер, используя модуль smtpd, который поставляется с предустановленным Python. Вместо того чтобы отправлять электронные письма на указанный адрес, он отбрасывает их и печатает их содержимое в консоль. Запуск локального отладочного сервера означает, что нет необходимости заниматься шифрованием сообщений или использовать учетные данные для входа на сервер электронной почты.

Вы можете запустить локальный сервер отладки SMTP, набрав следующее в Командной строке:

$ python -m smtpd -c DebuggingServer -n localhost:1025

В Linux используйте ту же команду, предваряемую sudo.

Все электронные письма, отправленные через этот сервер, будут отброшены и показаны в окне терминала как объект bytes для каждой строки:

---------- MESSAGE FOLLOWS ----------
b'X-Peer: ::1'
b''
b'From: my@address.com'
b'To: your@address.com'
b'Subject: a local test mail'
b''
b'Hello there, here is a test email'
------------ END MESSAGE ------------

До конца руководства я буду считать, что вы используете учетную запись Gmail, но если вы используете локальный отладочный сервер, просто убедитесь, что вы используете localhost в качестве SMTP-сервера и используете порт 1025, а не порт 465 или 587. Кроме этого, вам не нужно будет использовать login() или шифровать связь с помощью SSL/TLS.

Отправка обычного текста по электронной почте

Прежде чем мы перейдем к отправке писем с HTML-содержимым и вложениями, вы научитесь отправлять обычные текстовые письма с помощью Python. Это письма, которые вы можете написать в простом текстовом редакторе. В них нет таких причудливых вещей, как форматирование текста или гиперссылки. Этому вы научитесь чуть позже.

Запуск безопасного SMTP-соединения

При отправке электронной почты через Python необходимо убедиться, что SMTP-соединение зашифровано, чтобы ваше сообщение и учетные данные не были легко доступны другим. SSL (Secure Sockets Layer) и TLS (Transport Layer Security) - это два протокола, которые можно использовать для шифрования SMTP-соединения. Нет необходимости использовать один из них при использовании локального отладочного сервера.

Существует два способа установить безопасное соединение с вашим почтовым сервером:

  • Запустите SMTP-соединение, защищенное с самого начала, используя SMTP_SSL().
  • Запустите незащищенное SMTP-соединение, которое затем может быть зашифровано с помощью .starttls().

В обоих случаях Gmail будет шифровать электронную почту с помощью TLS, поскольку это более безопасный преемник SSL. В соответствии с правилами Python Security considerations, настоятельно рекомендуется использовать create_default_context() из модуля ssl. Это позволит загрузить доверенные сертификаты центра сертификации системы, включить проверку имени хоста и проверку сертификатов, а также попытаться выбрать достаточно безопасные параметры протокола и шифра.

Если вы хотите проверить шифрование письма в почтовом ящике Gmail, перейдите в меню MoreShow original, чтобы увидеть тип шифрования, указанный в заголовке Received.

smtplib - это встроенный в Python модуль для отправки электронных писем на любую машину в Интернете с демоном SMTP или ESMTP listener.

Сначала я покажу вам, как использовать SMTP_SSL(), поскольку в этом случае создается безопасное соединение с самого начала и оно немного лаконичнее, чем SMTP_SSL(). Помните, что Gmail требует подключения к порту 465 при использовании .starttls() и к порту 587 при использовании SMTP_SSL()..starttls()

Вариант 1: Использование SMTP_SSL()

Приведенный ниже пример кода создает защищенное соединение с SMTP-сервером Gmail, используя контекст SMTP_SSL() из smtplib для инициирования TLS-шифрованного соединения. Контекст по умолчанию ssl проверяет имя хоста и его сертификаты и оптимизирует безопасность соединения. Обязательно заполните собственный адрес электронной почты вместо my@gmail.com:

import smtplib, ssl

port = 465  # For SSL
password = input("Type your password and press enter: ")

# Create a secure SSL context
context = ssl.create_default_context()

with smtplib.SMTP_SSL("smtp.gmail.com", port, context=context) as server:
    server.login("my@gmail.com", password)
    # TODO: Send email here

Использование with smtplib.SMTP_SSL() as server: обеспечивает автоматическое закрытие соединения в конце блока кода с отступом. Если port равно нулю или не указано, .SMTP_SSL() будет использоваться стандартный порт для SMTP через SSL (порт 465).

Хранить пароль электронной почты в коде небезопасно, особенно если вы собираетесь поделиться им с другими. Вместо этого используйте input(), чтобы пользователь мог ввести свой пароль при запуске скрипта, как в примере выше. Если вы не хотите, чтобы ваш пароль отображался на экране при вводе, вы можете импортировать модуль getpass и использовать .getpass() вместо него для слепого ввода пароля.

Вариант 2: Использование .starttls()

Вместо того, чтобы использовать .SMTP_SSL() для создания безопасного соединения с самого начала, мы можем создать незащищенное SMTP-соединение и зашифровать его с помощью .starttls().

Для этого создайте экземпляр smtplib.SMTP, который инкапсулирует SMTP-соединение и позволяет получить доступ к его методам. Я рекомендую определить ваш SMTP-сервер и порт в начале вашего скрипта, чтобы легко настроить их.

В приведенном ниже фрагменте кода используется конструкция server = SMTP(), а не формат with SMTP() as server:, который мы использовали в предыдущем примере. Чтобы убедиться, что ваш код не аварийно завершается, когда что-то идет не так, поместите основной код в блок try, а блок except пусть печатает сообщения об ошибках в stdout:

import smtplib, ssl

smtp_server = "smtp.gmail.com"
port = 587  # For starttls
sender_email = "my@gmail.com"
password = input("Type your password and press enter: ")

# Create a secure SSL context
context = ssl.create_default_context()

# Try to log in to server and send email
try:
    server = smtplib.SMTP(smtp_server,port)
    server.ehlo() # Can be omitted
    server.starttls(context=context) # Secure the connection
    server.ehlo() # Can be omitted
    server.login(sender_email, password)
    # TODO: Send email here
except Exception as e:
    # Print any error messages to stdout
    print(e)
finally:
    server.quit() 

Для идентификации себя на сервере, .helo() (SMTP) или .ehlo() (ESMTP) следует вызывать после создания объекта .SMTP(), и снова после .starttls(). Эта функция неявно вызывается .starttls() и .sendmail() при необходимости, поэтому, если вы не хотите проверить расширения SMTP сервиса сервера, нет необходимости использовать .helo() или .ehlo() явно.

Отправка электронной почты в виде обычного текста

После того, как вы инициировали безопасное SMTP-соединение с помощью любого из вышеперечисленных методов, вы можете отправить письмо, используя .sendmail(), который в основном делает то, что написано на жести:

server.sendmail(sender_email, receiver_email, message)

Я рекомендую определять адреса электронной почты и содержание сообщений в верхней части вашего скрипта, после импорта, чтобы вы могли легко их изменить:

sender_email = "my@gmail.com"
receiver_email = "your@gmail.com"
message = """\
Subject: Hi there

This message is sent from Python."""

# Send email here

Строка message начинается с "Subject: Hi there", за которой следуют две новые строки (\n). Это гарантирует, что Hi there будет отображаться как тема письма, а текст после новой строки будет рассматриваться как тело письма.

Приведенный ниже пример кода отправляет письмо с обычным текстом, используя SMTP_SSL():

import smtplib, ssl

port = 465  # For SSL
smtp_server = "smtp.gmail.com"
sender_email = "my@gmail.com"  # Enter your address
receiver_email = "your@gmail.com"  # Enter receiver address
password = input("Type your password and press enter: ")
message = """\
Subject: Hi there

This message is sent from Python."""

context = ssl.create_default_context()
with smtplib.SMTP_SSL(smtp_server, port, context=context) as server:
    server.login(sender_email, password)
    server.sendmail(sender_email, receiver_email, message)

Для сравнения здесь приведен пример кода, который отправляет письмо с обычным текстом через SMTP-соединение, защищенное .starttls(). Строки server.ehlo() можно опустить, поскольку они неявно вызываются .starttls() и .sendmail() при необходимости:

import smtplib, ssl

port = 587  # For starttls
smtp_server = "smtp.gmail.com"
sender_email = "my@gmail.com"
receiver_email = "your@gmail.com"
password = input("Type your password and press enter:")
message = """\
Subject: Hi there

This message is sent from Python."""

context = ssl.create_default_context()
with smtplib.SMTP(smtp_server, port) as server:
    server.ehlo()  # Can be omitted
    server.starttls(context=context)
    server.ehlo()  # Can be omitted
    server.login(sender_email, password)
    server.sendmail(sender_email, receiver_email, message)

Отправка модных писем

Встроенный в Python пакет email позволяет структурировать более причудливые электронные письма, которые затем можно передавать с помощью smtplib, как вы уже делали. Ниже вы узнаете, как использовать пакет email для отправки электронных писем с HTML-содержимым и вложениями.

Включение содержимого HTML

Если вы хотите отформатировать текст в своем электронном письме (жирный, главный и т.д.) или добавить какие-либо изображения, гиперссылки или отзывчивый контент, то HTML оказывается очень кстати. Сегодня наиболее распространенным типом электронной почты является MIME (Multipurpose Internet Mail Extensions) многокомпонентная почта, сочетающая HTML и обычный текст. Сообщения MIME обрабатываются модулем Python email.mime. Подробное описание смотрите в документации .

Поскольку не все почтовые клиенты отображают HTML-содержимое по умолчанию, а некоторые люди предпочитают получать только обычные сообщения по соображениям безопасности, важно включить альтернативу HTML-сообщениям в виде обычного текста. Поскольку почтовый клиент сначала отобразит последнее многокомпонентное вложение, убедитесь, что HTML-сообщение добавлено после обычного текста.

В примере ниже наши объекты MIMEText() будут содержать HTML и обычный текст нашего сообщения, а экземпляр MIMEMultipart("alternative") объединит их в одно сообщение с двумя альтернативными вариантами отображения:

import smtplib, ssl
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

sender_email = "my@gmail.com"
receiver_email = "your@gmail.com"
password = input("Type your password and press enter:")

message = MIMEMultipart("alternative")
message["Subject"] = "multipart test"
message["From"] = sender_email
message["To"] = receiver_email

# Create the plain-text and HTML version of your message
text = """\
Hi,
How are you?
Real Python has many great tutorials:
www.realpython.com"""
html = """\
<html>
  <body>
    <p>Hi,<br>
       How are you?<br>
       <a href="http://www.realpython.com">Real Python</a> 
       has many great tutorials.
    </p>
  </body>
</html>
"""

# Turn these into plain/html MIMEText objects
part1 = MIMEText(text, "plain")
part2 = MIMEText(html, "html")

# Add HTML/plain-text parts to MIMEMultipart message
# The email client will try to render the last part first
message.attach(part1)
message.attach(part2)

# Create secure connection with server and send email
context = ssl.create_default_context()
with smtplib.SMTP_SSL("smtp.gmail.com", 465, context=context) as server:
    server.login(sender_email, password)
    server.sendmail(
        sender_email, receiver_email, message.as_string()
    )

В этом примере вы сначала определяете обычный текст и HTML-сообщение как строковые литералы, а затем храните их как объекты plain/html MIMEText. Затем их можно добавить в таком порядке в сообщение MIMEMultipart("alternative") и отправить через защищенное соединение с почтовым сервером. Не забудьте добавить HTML-сообщение после обычного текста, так как почтовые клиенты будут пытаться сначала отобразить последнюю часть.

Добавление вложений с помощью пакета email

Для того чтобы отправить двоичные файлы на почтовый сервер, предназначенный для работы с текстовыми данными, их необходимо закодировать перед транспортировкой. Чаще всего для этого используется base64, который кодирует двоичные данные в печатаемые символы ASCII.

В примере кода ниже показано, как отправить электронное письмо с файлом PDF в качестве вложения:

import email, smtplib, ssl

from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

subject = "An email with attachment from Python"
body = "This is an email with attachment sent from Python"
sender_email = "my@gmail.com"
receiver_email = "your@gmail.com"
password = input("Type your password and press enter:")

# Create a multipart message and set headers
message = MIMEMultipart()
message["From"] = sender_email
message["To"] = receiver_email
message["Subject"] = subject
message["Bcc"] = receiver_email  # Recommended for mass emails

# Add body to email
message.attach(MIMEText(body, "plain"))

filename = "document.pdf"  # In same directory as script

# Open PDF file in binary mode
with open(filename, "rb") as attachment:
    # Add file as application/octet-stream
    # Email client can usually download this automatically as attachment
    part = MIMEBase("application", "octet-stream")
    part.set_payload(attachment.read())

# Encode file in ASCII characters to send by email    
encoders.encode_base64(part)

# Add header as key/value pair to attachment part
part.add_header(
    "Content-Disposition",
    f"attachment; filename= {filename}",
)

# Add attachment to message and convert message to string
message.attach(part)
text = message.as_string()

# Log in to server using secure context and send email
context = ssl.create_default_context()
with smtplib.SMTP_SSL("smtp.gmail.com", 465, context=context) as server:
    server.login(sender_email, password)
    server.sendmail(sender_email, receiver_email, text)

Сообщение MIMEultipart() принимает параметры в виде пар ключ/значение в стиле RFC5233, которые хранятся в словаре и передаются в .add_header метод базового класса Message.

Ознакомьтесь с документацией для модуля email.mime Python, чтобы узнать больше об использовании классов MIME.

Отправка нескольких персонализированных писем

Представьте, что вы хотите отправить электронные письма членам вашей организации, чтобы напомнить им о необходимости оплатить взносы. Или, может быть, вы хотите отправить студентам вашего класса персонализированные электронные письма с оценками за их недавнее задание. Эти задачи легко решаются в Python.

Создайте файл CSV с соответствующей личной информацией

Для начала отправки нескольких персонализированных электронных писем достаточно создать файл CSV (значения, разделенные запятыми) , содержащий всю необходимую личную информацию. (Следите за тем, чтобы не передавать частную информацию других людей без их согласия). CSV-файл можно представить как простую таблицу, где первая строка часто содержит заголовки столбцов

Ниже приведено содержимое файла contacts_file.csv, который я сохранил в той же папке, что и мой код Python. В нем содержатся имена, адреса и оценки для набора вымышленных людей. Я использовал конструкции my+modifier@gmail.com, чтобы убедиться, что все письма попадают в мой собственный почтовый ящик, который в данном примере имеет вид my@gmail.com:

name,email,grade
Ron Obvious,my+ovious@gmail.com,B+
Killer Rabbit of Caerbannog,my+rabbit@gmail.com,A
Brian Cohen,my+brian@gmail.com,C

При создании CSV-файла обязательно разделяйте значения запятой, без пробелов.

Перекрытие строк для отправки нескольких писем

Приведенный ниже пример кода показывает, как открыть CSV-файл и перебрать строки его содержимого (пропуская строку заголовка). Чтобы убедиться, что код работает правильно, прежде чем отправлять электронные письма всем вашим контактам, я вывел Sending email to ... для каждого контакта, который мы можем позже заменить функциональностью, которая действительно отправляет электронные письма:

import csv

with open("contacts_file.csv") as file:
    reader = csv.reader(file)
    next(reader)  # Skip header row
    for name, email, grade in reader:
        print(f"Sending email to {name}")
        # Send email here

В приведенном выше примере использование with open(filename) as file: позволяет убедиться, что ваш файл закрывается в конце блока кода. csv.reader() позволяет легко читать CSV-файл построчно и извлекать его значения. Строка next(reader) пропускает строку заголовка, поэтому следующая строка for name, email, grade in reader: разделяет последующие строки через каждую запятую и сохраняет полученные значения в строках name, email и grade для текущего контакта.

Если значения в вашем CSV-файле содержат пробелы с одной или обеих сторон, вы можете удалить их с помощью метода .strip().

Персонализированный контент

Вы можете поместить персонализированный контент в сообщение, используя str.format() для заполнения фигурных скобок. Например, "hi {name}, you {result} your assignment".format(name="John", result="passed") даст вам "hi John, you passed your assignment".

Начиная с Python 3.6, форматирование строк может быть выполнено более элегантно с помощью f-строк, но они требуют, чтобы заполнители были определены перед самой f-строкой. Чтобы определить сообщение электронной почты в начале сценария и заполнить заполнители для каждого контакта при циклическом просмотре CSV-файла, используется более старый метод .format().

С учетом этого, вы можете создать общий текст сообщения, с заполнителями, которые могут быть адаптированы к конкретным лицам.

Пример кода

Следующий пример кода позволяет отправлять персонализированные электронные письма нескольким контактам. Он зацикливает CSV-файл с name,email,grade для каждого контакта, как в примере выше.

Общее сообщение определяется в начале сценария, и для каждого контакта в CSV-файле заполняются его {name} и {grade}, а персонализированное письмо рассылается через защищенное соединение с сервером Gmail, как вы видели ранее:

import csv, smtplib, ssl

message = """Subject: Your grade

Hi {name}, your grade is {grade}"""
from_address = "my@gmail.com"
password = input("Type your password and press enter: ")

context = ssl.create_default_context()
with smtplib.SMTP_SSL("smtp.gmail.com", 465, context=context) as server:
    server.login(from_address, password)
    with open("contacts_file.csv") as file:
        reader = csv.reader(file)
        next(reader)  # Skip header row
        for name, email, grade in reader:
            server.sendmail(
                from_address,
                email,
                message.format(name=name,grade=grade),
            )

Yagmail

Существует множество библиотек, предназначенных для упрощения отправки электронных писем, таких как Envelopes, Flanker и Yagmail. Yagmail разработан специально для работы с Gmail и значительно упрощает процесс отправки электронной почты через дружественный API, как вы можете видеть в примере кода ниже:

import yagmail

receiver = "your@gmail.com"
body = "Hello there from Yagmail"
filename = "document.pdf"

yag = yagmail.SMTP("my@gmail.com")
yag.send(
    to=receiver,
    subject="Yagmail test with attachment",
    contents=body, 
    attachments=filename,
)

Этот пример кода отправляет электронное письмо с вложением PDF за долю строк, необходимых для нашего примера с использованием email и smtplib.

При настройке Yagmail вы можете добавить валидации Gmail в связку ключей вашей ОС, как описано в документации. Если этого не сделать, Yagmail при необходимости предложит вам ввести пароль и автоматически сохранит его в связке ключей

Услуги электронной почты

Если вы планируете отправлять большой объем писем, хотите видеть статистику по электронной почте и обеспечить надежную доставку, возможно, стоит обратить внимание на транзакционные почтовые сервисы. Хотя все перечисленные ниже сервисы имеют платные тарифные планы для отправки больших объемов писем, они также поставляются с бесплатными тарифными планами, чтобы вы могли опробовать их. Некоторые из этих бесплатных планов действуют бессрочно и могут быть достаточными для ваших потребностей в электронной почте

Ниже представлен обзор бесплатных тарифных планов некоторых основных служб транзакционной электронной почты. Нажав на название провайдера, вы перейдете в раздел цен на его веб-сайте.

Provider Free plan
Sendgrid 40,000 emails for your first 30 days, then 100/day
Sendinblue 300 emails/day
Mailgun First 10,000 emails free
Mailjet 200 emails/day
Amazon SES 62,000 emails/month

Вы можете воспользоваться поиском Google, чтобы узнать, какой провайдер лучше всего соответствует вашим потребностям, или просто попробовать несколько бесплатных тарифных планов, чтобы понять, с каким API вам больше всего нравится работать.

Пример кода Sendgrid

Вот пример кода для отправки электронной почты с помощью Sendgrid, чтобы дать вам представление о том, как использовать транзакционный почтовый сервис в Python:

import os
import sendgrid
from sendgrid.helpers.mail import Content, Email, Mail

sg = sendgrid.SendGridAPIClient(
    apikey=os.environ.get("SENDGRID_API_KEY")
)
from_email = Email("my@gmail.com")
to_email = Email("your@gmail.com")
subject = "A test email from Sendgrid"
content = Content(
    "text/plain", "Here's a test email sent through Python"
)
mail = Mail(from_email, subject, to_email, content)
response = sg.client.mail.send.post(request_body=mail.get())

# The statements below can be included for debugging purposes
print(response.status_code)
print(response.body)
print(response.headers)

Чтобы запустить этот код, вы должны сначала:

Больше информации о том, как настроить Sendgrid для Mac и Windows, можно найти в README репозитория на Github.

Заключение

Теперь вы можете запустить защищенное SMTP-соединение и отправить несколько персонализированных писем людям из вашего списка контактов!

Вы узнали, как отправлять электронное письмо в формате HTML с альтернативой в виде обычного текста и прикреплять файлы к своим письмам. Пакет Yagmail упрощает все эти задачи при использовании учетной записи Gmail. Если вы планируете отправлять большие объемы электронной почты, стоит обратить внимание на услуги транзакционной электронной почты.

Наслаждайтесь отправкой писем с помощью Python, и помните: никакого спама, пожалуйста!

Вернуться на верх