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

Хотя Python позволяет легко отправлять электронные письма с помощью модуля smtplib, Django предоставляет несколько дополнительных функций. Они позволяют быстро отправлять письма, легко отлаживать оправку при разработке, и предоставляют поддержку окружений, которые не могут использовать SMTP.

Код находится в модуле django.core.mail.

Простой пример

В две строки кода:

from django.core.mail import send_mail

send_mail('Subject here', 'Here is the message.', 'from@example.com',
    ['to@example.com'], fail_silently=False)

Письмо отправлено через SMTP хост и порт, которые указаны в настройках EMAIL_HOST и EMAIL_PORT. Настройки EMAIL_HOST_USER и EMAIL_HOST_PASSWORD, если указаны, используются для авторизации на SMTP сервере, а настройки EMAIL_USE_TLS и EMAIL_USE_SSL указывают использовать ли безопасное соединение.

Примечание

При отправке письма через django.core.mail будет использоваться кодировка из DEFAULT_CHARSET.

send_mail()

send_mail(subject, message, from_email, recipient_list, fail_silently=False, auth_user=None, auth_password=None, connection=None, html_message=None)

Самый простой способ отправить письмо – использовать django.core.mail.send_mail().

Параметры subject, message, from_email и recipient_list являются обязательными.

  • subject: строка.

  • message: строка.

  • from_email: строка.

  • recipient_list: список строк, каждая является email. Каждый получатель из recipient_list будет видеть остальных получателей в поле “To:” письма.

  • fail_silently: булево. При False send_mail вызовет smtplib.SMTPException. Смотрите документацию smtplib, чтобы узнать список всех ошибок, каждый из которых наследуется от SMTPException.

  • auth_user: необязательное имя пользователя, которое используется при авторизации на SMTP сервере. Если не указано, Django будет использовать значение EMAIL_HOST_USER.

  • auth_password: необязательный пароль, который используется при авторизации на SMTP сервере. Если не указано, Django будет использовать значение EMAIL_HOST_PASSWORD.

  • connection: необязательный бэкенд, который будет использоваться для отправки письма. Если не указан, будет использоваться бэкенд по умолчанию. Смотрите раздел о бэкендах для отправки электронной почты.

  • html_message: если html_message указано, письмо будет с multipart/alternative, и будет содержать message с типом text/plain, и html_message с типом text/html.

Возвращает количество успешно отправленных писем (которое будет 0 или 1, т.к. функция отправляет только одно письмо).

send_mass_mail()

send_mass_mail(datatuple, fail_silently=False, auth_user=None, auth_password=None, connection=None)

django.core.mail.send_mass_mail() используется для массовой рассылки электронных писем.

datatuple – кортеж, каждый элемент которого следующего формата:

(subject, message, from_email, recipient_list)

fail_silently, auth_user и auth_password аналогичны параметрам send_mail().

Каждый элемент datatuple соответствует отдельному письму. Как и для send_mail(), получатели из одного списка recipient_list увидят всех остальных в поле “To:” письма.

Например, следующий код отправит два отдельных письма двум отдельным спискам получателей. Однако, будет использоваться одно подключения к почтовому серверу:

message1 = ('Subject here', 'Here is the message', 'from@example.com', ['first@example.com', 'other@example.com'])
message2 = ('Another Subject', 'Here is another message', 'from@example.com', ['second@test.com'])
send_mass_mail((message1, message2), fail_silently=False)

Возвращает количество успешно отправленных писем.

send_mass_mail() или send_mail()

Основная разница между send_mass_mail() и send_mail()send_mail() создает подключение к почтовому серверу при каждом вызове, в то время как send_mass_mail() использует одно подключения для всех писем. Это делает send_mass_mail() более эффективной.

mail_admins()

mail_admins(subject, message, fail_silently=False, connection=None, html_message=None)

django.core.mail.mail_admins() отправляет письма администраторам сайта, которые указаны в настройке ADMINS.

mail_admins() к заголовку письма добавляет префикс из EMAIL_SUBJECT_PREFIX, по умолчанию "[Django] ".

Заголовок “From:” будет содержать значение SERVER_EMAIL.

Этот метод существует для удобства и читаемости.

Если указан html_message, письмо будет с multipart/alternative, и будет содержать message с типом text/plain, и html_message с типом text/html.

mail_managers()

mail_managers(subject, message, fail_silently=False, connection=None, html_message=None)

django.core.mail.mail_managers() аналогичен mail_admins(), но отправляет письма менеджерам сайта, которые указаны в MANAGERS.

Примеры

Отправляет одно письмо john@example.com и jane@example.com, они оба указаны в “To:”:

send_mail('Subject', 'Message.', 'from@example.com',
    ['john@example.com', 'jane@example.com'])

Отправляет письма john@example.com и jane@example.com, они получать отдельные письма:

datatuple = (
    ('Subject', 'Message.', 'from@example.com', ['john@example.com']),
    ('Subject', 'Message.', 'from@example.com', ['jane@example.com']),
)
send_mass_mail(datatuple)

Защищаемся от инъекции заголовков электронного письма

Инъекции заголовков – это атака, во время которой атакующий добавляет данные в заголовки письма “To:” и “From:”, которое создает ваш код.

Функции для отправки писем в Django запрещают указывать перенос строки в значениях заголовков. Если subject, from_email или recipient_list содержит перенос строки (в Unix, Windows или Mac стиле), функция отправки писем (например, send_mail()) вызовет исключение django.core.mail.BadHeaderError (дочерний класс ValueError), и ничего не оправит. Это ваша ответственность проверять все данные перед тем, как передать в функцию.

Если message содержит заголовки в начале, они просто будут выведены в начале содержимого письма.

Вот пример представления, которое получает subject, message и from_email из POST данных запроса, отправляет их на admin@example.com, и перенаправляет по адресу “/contact/thanks/”:

from django.core.mail import send_mail, BadHeaderError
from django.http import HttpResponse, HttpResponseRedirect

def send_email(request):
    subject = request.POST.get('subject', '')
    message = request.POST.get('message', '')
    from_email = request.POST.get('from_email', '')
    if subject and message and from_email:
        try:
            send_mail(subject, message, from_email, ['admin@example.com'])
        except BadHeaderError:
            return HttpResponse('Invalid header found.')
        return HttpResponseRedirect('/contact/thanks/')
    else:
        # In reality we'd use a form class
        # to get proper validation errors.
        return HttpResponse('Make sure all fields are entered and valid.')

Класс EmailMessage

Функции send_mail() и send_mass_mail() на самом деле обертки, которые используют класс EmailMessage.

Не все возможности EmailMessage доступны через send_mail() и остальные функции. Если вы хотите использовать дополнительные возможности, такие как получатели в BCC, отправка файла, или “multi-part” письмо, вам следует использовать непосредственно EmailMessage.

Примечание

Это особенности проектирования. send_mail() и подобные функции ранее были единственным интерфейсом, который предоставлял Django. Однако, список аргументом постепенно рос и рос. По этому было решено использовать более объектно-ориентированный для электронных писем, а функции оставить для обратной совместимости.

EmailMessage отвечает за создание электронного письма. Бэкенд отправки писем отвечает за отправку писем.

Для удобства EmailMessage предоставляет простой метод send() для отправки одного письма. Если вы хотите отправить несколько писем, API бэкенда предоставляет дополнительные методы.

Объекты EmailMessage

class EmailMessage

Класс EmailMessage создает со следующими параметрами (в указанном порядке, если передавать позиционные аргументы). Все параметры не обязательны и их можно установить в любой момент до вызова метода send().

  • subject: Тема письма.

  • body: Содержимое письма. Необходимо указать обычный текст.

  • from_email: Адрес отправителя. Можно использовать как fred@example.com, так и Fred <fred@example.com>. Если не указан, будет использовать значение настройки DEFAULT_FROM_EMAIL.

  • to: Список, или кортеж, получателей.

  • bcc: Список, или кортеж, адресов, которые будут указаны в заголовке “Bcc”.

  • connection: Экземпляр бэкенда для отправки писем. Используйте этот параметр, если хотите использовать одно подключения для нескольких писем. Если не указан, будет создано новое подключение при вызове send().

  • attachments: Список вложений. Могут быть экземпляры email.MIMEBase.MIMEBase, или кортежи (filename, content, mimetype).

  • headers: Словарь дополнительных заголовков. Ключами являются названия заголовков, значения – содержимое заголовка. Вы сами должны убедиться в правильности заголовков. Соответствующий атрибут – extra_headers.

  • cc: Список, или кортеж, получателей, которые будут указаны в заголовке “Cc”.

  • reply_to: Список, или кортеж, получателей, которые будут указаны в заголовке “Reply-To”.

Изменено в Django 1.8:

Был добавлен параметр reply_to.

Например:

email = EmailMessage('Hello', 'Body goes here', 'from@example.com',
            ['to1@example.com', 'to2@example.com'], ['bcc@example.com'],
            reply_to=['another@example.com'], headers={'Message-ID': 'foo'})

Класс содержит следующие методы:

  • send(fail_silently=False) отправляет письмо. Если при создании объекта указали подключение, оно будет использоваться. Иначе будет создан экземпляр бэкенда для отправки почты по умолчанию. Если fail_silently равен True, все ошибки будут проигнорированы. Пустой список получателей не вызывает исключение.

  • message() создает объект django.core.mail.SafeMIMEText (дочерний класс email.MIMEText.MIMEText), или django.core.mail.SafeMIMEMultipart, который содержит письмо для отправки. Если вам понадобиться переопределить класс EmailMessage, скорее всего вы переопределите этот метод, чтобы изменить MIME объект.

  • recipients() возвращает список всех получателей письма из атрибутов to, cc, или bcc. Вам может понадобиться переопределить этот метод, т.к. SMTP серверу необходимо указать список всех получателей. Если вы добавите новый способ указывать получателей, они должны возвращаться этим методом.

  • attach() создает новый вложенный файл и добавляет его к письму. Есть два способа вызвать attach():

    • Можно передать экземпляр email.MIMEBase.MIMEBase. Он будет добавлен непосредственно в письмо.

    • Можно передать три аргумента: filename, content и mimetype. filename – название файла, content – содержимое файла, mimetype – необязательный MIME тип файла. Если вы не укажите mimetype, он будет построен по названию файла.

      Например:

      message.attach('design.png', img_data, 'image/png')
      

      Если вы укажите message/rfc822 в mimetype, можно передать django.core.mail.EmailMessage и email.message.Message.

      Также message/rfc822 вложение не будет base64-кодировано в нарушение RFC 2046#section-5.2.1, что может привести к проблемам с отображением вложений в Evolution и Thunderbird.

  • attach_file() создает новое вложения, используя файл с файловой системы. Вызовите, указав путь к файлу. Опционально можно указать MIME тип вложения. Если вы не укажите mimetype, он будет построен по названию файла. Самый простой пример использования:

    message.attach_file('/images/weather_map.png')
    

Отправка содержимого разного типа

Вам может понадобиться отправлять сообщение в нескольких версиях. Классический пример отправлять HTML и текстовую версию сообщения. Django позволяет делать это, используя класс EmailMultiAlternatives. Это дочерний класс EmailMessage с методом attach_alternative(), которые добавляет дополнительную версию сообщения в письмо. Все остальные методы (включая конструктор) наследуются непосредственно от EmailMessage.

Чтобы отправить и текстовое, и HTML сообщение, используйте следующий код:

from django.core.mail import EmailMultiAlternatives

subject, from_email, to = 'hello', 'from@example.com', 'to@example.com'
text_content = 'This is an important message.'
html_content = '<p>This is an <strong>important</strong> message.</p>'
msg = EmailMultiAlternatives(subject, text_content, from_email, [to])
msg.attach_alternative(html_content, "text/html")
msg.send()

По умолчанию MIME-тип параметра body в EmailMessage равен "text/plain". Лучше не менять это значение, это гарантирует, что получатель сможет прочитать сообщение любым почтовым клиентом. Однако, если вы уверены, что получатель может принимать различные типа сообщений, вы можете использовать атрибут content_subtype класса EmailMessage. Основной тип будет всегда "text", но вы можете поменять подтип. Например:

msg = EmailMessage(subject, html_content, from_email, [to])
msg.content_subtype = "html"  # Main content is now text/html
msg.send()

Бэкенды для отправки электронной почты

Непосредственная отправка электронного письма происходит в бэкенде.

Бэкенд содержит следующие методы:

  • open() создает длительное соединение.

  • close() закрывает текущее соединение.

  • send_messages(email_messages) отправляет список объектов EmailMessage. Если нет открытого соединения, оно будет создано и закрыто после отправки. Если соединение уже открыто, оно останется открытым после отправки.

Может использоваться как контекстный менеджер, автоматически вызывая open() и close() при необходимости:

from django.core import mail

with mail.get_connection() as connection:
    mail.EmailMessage(subject1, body1, from1, [to1],
                      connection=connection).send()
    mail.EmailMessage(subject2, body2, from2, [to2],
                      connection=connection).send()
Добавлено в Django 1.8:

Был добавлен протокол контекстного менеджера.

Получаем экземпляр бэкенда для отправки электронной почты

Функция get_connection() из django.core.mail возвращает экземпляр бэкенда для отправки почты, который вы можете использовать.

get_connection(backend=None, fail_silently=False, *args, **kwargs)

По умолчанию вызов get_connection() вернет экземпляр бэкенда, указанного в EMAIL_BACKEND. Если указать аргумент backend, будет создан указанный бэкенд.

Аргумент fail_silently указывает как обрабатывать ошибки. Если fail_silently равен True, ошибки, которые возникли при оправке почты, будут проигнорированы.

Все остальные аргументы передаются непосредственно в конструктор бэкенда.

Django предоставляет несколько бэкендов. Эти бэкенды, кроме SMTP (который используется по умолчанию), полезны только при разработке или тестировании. Вы можете создать собственный бэкенд.

SMTP бэкенд

class backends.smtp.EmailBackend(host=None, port=None, username=None, password=None, use_tls=None, fail_silently=False, use_ssl=None, timeout=None, ssl_keyfile=None, ssl_certfile=None, **kwargs)

Бэкенд по умолчанию. Электронные письма отправляются через SMTP сервер.

Значение аргумента берется из соответствующей настройки, если аругмент None:

SMTP бэкенд используется в Django по умолчанию. Если вы хотите указать его явно, добавьте в настройки:

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'

Если timeout не указан, будет использоваться результат socket.getdefaulttimeout(), который по умолчанию None (без “таймаута”).

Изменено в Django 1.8:

Были добавлены параметры ssl_keyfile и ssl_certfile, и соответствующие настройки. Была добавлен возможность изменить timeout в настройке EMAIL_TIMEOUT.

Консольный бэкенд

Вместо отправки письма, бэкенд выведет его в стандартный вывод. По умолчанию консольный синдром пишет в stdout. Вы можете использовать объекты-потоки, указав параметр stream при создании соединения.

Чтобы указать этот бэкенд, добавьте следующее в настройки:

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

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

Файловый бэкенд

Этот бэкенд пишет почту в файл. Новый файл создается при создании новой сессии этого бэкенда. Каталог, в котором создаются файлы, берется из настройки EMAIL_FILE_PATH или параметра file_path, который передали при создании соединения get_connection().

Чтобы указать этот бэкенд, добавьте следующее в настройки:

EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend'
EMAIL_FILE_PATH = '/tmp/app-messages' # change this to a proper location

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

Бэкенд в памяти

Бэкенд 'locmem' сохраняет почту в специальном атрибуте модуля django.core.mail. Атрибут outbox создается при первой отправке письма. Это список экземпляров EmailMessage отправленных писем.

Чтобы указать этот бэкенд, добавьте следующее в настройки:

EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'

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

Dummy бэкенд

Этот бэкенд ничего не делает с почтой. Чтобы указать этот бэкенд, добавьте следующее в настройки:

EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend'

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

Создание собственного бэкенда

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

Ваш бэкенд должен наследоваться от BaseEmailBackend, который находится в модуле django.core.mail.backends.base. Бэкенд должен определить метод send_messages(email_messages). Этот метод принимает список объектов EmailMessage и вернуть список успешно отправленных писем. Если ваш бэкенд в каком либо виде определяет постоянное подключение, вам следует определить методы open() и close(). Примеры реализации можно найти в исходном коде smtp.EmailBackend.

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

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

Есть два способа указать бэкенду использовать одно подключение.

Первое, вы можете использовать метод send_messages(). send_messages() принимает список объектов EmailMessage (или наследников), и отправляет их, используя одно подключение.

Например, если у вас есть функция get_notification_email(), которая возвращает список объектов EmailMessage, вы можете отправить их следующим образом:

from django.core import mail
connection = mail.get_connection()   # Use default email connection
messages = get_notification_email()
connection.send_messages(messages)

В этом примере send_messages() откроет соединение, отправит письма, и закроет соединение.

Второй вариант – использовать методы open() и close(), чтобы явно контролировать соединение. send_messages() не будет создавать и закрывать соединение, если оно уже создано. Поэтому, если вы создадите соединение, вы можете контролировать, когда оно будет закрыто. Например:

from django.core import mail
connection = mail.get_connection()

# Manually open the connection
connection.open()

# Construct an email message that uses the connection
email1 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com',
                          ['to1@example.com'], connection=connection)
email1.send() # Send the email

# Construct two more messages
email2 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com',
                          ['to2@example.com'])
email3 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com',
                          ['to3@example.com'])

# Send the two emails in a single call -
connection.send_messages([email2, email3])
# The connection was already open so send_messages() doesn't close it.
# We need to manually close the connection.
connection.close()

Настройка почты при разработке

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

Самый простой способ настроить почту для разработки – использовать бэкенд console. Этот бэкенд перенаправляет всю почту в stdout, позволяя увидеть содержимое писем.

Также можно использовать file. Этот бэкенд сохраняет содержимое каждого SMTP-соединения в файл.

Еще один способ – использовать локальный SMTP-сервер, который принимает письма и выводит их в консоль, но никуда их не оправляет. Python позволяет создать такой сервер одной командой:

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

Эта команда запускает простой SMTP-сервер, который слушает 1025 порт на localhost. Этот сервер выводит заголовки и содержимое полученных писем в консоль. Вам необходимо указать в настройках EMAIL_HOST и EMAIL_PORT. Подробности об этом SMTP-сервер смотрите в документации Python к модулю smtpd.

Информацию об отправке электронной почты при тестировании смотрите в разделе Сервисы для отправки писем.