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

Хотя 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: Строка. Если None, Django будет использовать значение параметра DEFAULT_FROM_EMAIL.
  • recipient_list: Список строк, каждая из которых является адресом электронной почты. Каждый член recipient_list будет видеть других получателей в поле «Кому:» электронного сообщения.
  • fail_silently: Булево число. Когда это False, send_mail() вызовет smtplib.SMTPException, если произойдет ошибка. Список возможных исключений см. в документации smtplib, все они являются подклассами SMTPException.
  • auth_user: Необязательное имя пользователя, которое будет использоваться для аутентификации на SMTP-сервере. Если оно не указано, Django будет использовать значение параметра EMAIL_HOST_USER.
  • auth_password: Необязательный пароль для аутентификации на SMTP-сервере. Если он не указан, Django будет использовать значение параметра EMAIL_HOST_PASSWORD.
  • connection: Дополнительный бэкенд электронной почты, который будет использоваться для отправки письма. Если не указано, будет использоваться экземпляр бэкенда по умолчанию. Более подробную информацию см. в документации по Email backends.
  • 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 будут видеть другие адреса в поле «Кому:» почтовых сообщений.

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

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, причем они оба появляются в поле «Кому:»:

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)

Предотвращение инъекции заголовков

Header injection - это эксплойт безопасности, при котором злоумышленник вставляет дополнительные заголовки электронной почты для управления «To:» и «From:» в сообщениях электронной почты, которые генерируют ваши скрипты.

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

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

Вот пример представления, которое берет subject, message и from_email из POST-данных запроса, отправляет их на admin@example.com и перенаправляет на «/contact/thanks/», когда все готово:

from django.core.mail import BadHeaderError, send_mail
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

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

Не все возможности класса EmailMessage доступны через send_mail() и связанные с ним функции-обертки. Если вы хотите использовать расширенные возможности, такие как BCC’ed получателей, вложения файлов или многокомпонентные письма, вам необходимо создать экземпляры EmailMessage напрямую.

Примечание

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

EmailMessage отвечает за создание самого сообщения электронной почты. Затем email backend отвечает за отправку сообщения.

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

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: Список вложений, которые следует поместить в сообщение. Это могут быть либо MIMEBase экземпляры, либо (filename, content, mimetype) тройки.
  • headers: Словарь дополнительных заголовков для размещения в сообщении. Ключи - это имена заголовков, значения - это значения заголовков. Вызывающая сторона должна убедиться, что имена и значения заголовков имеют правильный формат для сообщения электронной почты. Соответствующим атрибутом является extra_headers.
  • cc: Список или кортеж адресов получателей, используемых в заголовке «Cc» при отправке письма.
  • reply_to: Список или кортеж адресов получателей, используемых в заголовке «Reply-To» при отправке электронной почты.

Например:

from django.core.mail import EmailMessage

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 (подкласс класса Python MIMEText) или объект django.core.mail.SafeMIMEMultipart, содержащий сообщение для отправки. Если вам когда-нибудь понадобится расширить класс EmailMessage, вы, вероятно, захотите переопределить этот метод, чтобы поместить нужное вам содержимое в объект MIME.

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

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

    • Вы можете передать ему единственный аргумент, который является экземпляром MIMEBase. Он будет вставлен непосредственно в результирующее сообщение.

    • В качестве альтернативы вы можете передать attach() три аргумента: filename, content и mimetype. filename - это имя вложения файла, которое будет отображаться в письме, content - это данные, которые будут содержаться во вложении, а mimetype - это необязательный MIME тип для вложения. Если вы опустите mimetype, тип содержимого MIME будет определяться по имени файла вложения.

      Например:

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

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

      Для mimetype, начиная с text/, ожидается, что содержимое будет строкой. Бинарные данные будут декодированы с помощью UTF-8, и если это не удастся, MIME-тип будет изменен на application/octet-stream и данные будут присоединены без изменений.

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

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

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

    Для типов MIME, начинающихся с text/, двоичные данные обрабатываются как в attach().

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

Бывает полезно включить в письмо несколько версий содержимого; классический пример - отправить текстовую и 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()

Бэкенды электронной почты

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

Класс email backend имеет следующие методы:

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

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

Функция 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 (который используется по умолчанию), эти бэкенды полезны только во время тестирования и разработки. Если у вас есть особые требования к отправке электронной почты, вы можете write your own email backend.

Бэкэнд 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 (без таймаута).

Бэкэнд консоли

Вместо отправки реальных писем консольный бэкенд просто записывает письма, которые будут отправлены на стандартный вывод. По умолчанию консольный бэкенд пишет в 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

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

Changed in Django 3.1:

Добавлена поддержка pathlib.Path.

Бэкэнд с памятью

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

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

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

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

Бегунок для тестирования Django automatically uses this backend for testing.

Фиктивный бэкэнд

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

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

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

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

Если вам нужно изменить способ отправки писем, вы можете написать свой собственный почтовый бэкенд. Параметр EMAIL_BACKEND в вашем файле настроек - это путь импорта Python для вашего класса бэкенда.

Пользовательские почтовые бэкенды должны иметь подкласс 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, представляющих некоторые периодические электронные письма, которые вы хотите разослать, вы можете отправить эти письма с помощью одного вызова send_messages:

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.

Информацию о модульном тестировании отправки электронной почты в вашем приложении см. в разделе Услуги электронной почты документации по тестированию.

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