Документация / Django

Отправка email в Django через SMTP

В Django почта встроена — модуль django.core.mail, — поэтому отправка письма сводится к тому, чтобы указать SMTP-сервер в settings.py и вызвать send_mail(). Ниже — настройка fmailer и всё, что реально нужно в продакшене: HTML-письма, шаблоны, вложения, массовая рассылка и фоновая отправка через Celery.

Логин и пароль можно получить на странице управления токенами домена.

Параметры подключения

ПараметрЗначение
SMTP-хостsmtp.fmailer.ru
Порт587
ШифрованиеSTARTTLS (соединение переводится в TLS до авторизации)
ЛогинЛогин SMTP-токена вашего домена
ПарольПароль этого токена — показывается один раз, при создании
Каждый режим доступен и на высоком порту: 8465 (SSL/TLS), 8587 (STARTTLS) и 8025 (без шифрования). Многие хостинги и облака блокируют исходящие 25, 465 и 587 — если соединение отваливается по таймауту, используйте соответствующий высокий порт

Настройка settings.py

Добавьте параметры в settings.py. Доступы берём из окружения, чтобы они не попадали в репозиторий:

settings.pypython
# settings.py
import os

EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = "smtp.fmailer.ru"
EMAIL_PORT = 587
EMAIL_USE_TLS = True          # STARTTLS на порту 587
EMAIL_USE_SSL = False         # нельзя включать вместе с EMAIL_USE_TLS
EMAIL_HOST_USER = os.environ["EMAIL_HOST_USER"]
EMAIL_HOST_PASSWORD = os.environ["EMAIL_HOST_PASSWORD"]
EMAIL_TIMEOUT = 10

DEFAULT_FROM_EMAIL = "Магазин <noreply@mail.example.ru>"
SERVER_EMAIL = DEFAULT_FROM_EMAIL   # адрес для служебных писем Django об ошибках

А сам .env или переменные процесса:

.envproperties
EMAIL_HOST_USER=<логин токена>
EMAIL_HOST_PASSWORD=<пароль токена>

Для шифрования с первого байта на порту 465 поменяйте три параметра. EMAIL_USE_TLS и EMAIL_USE_SSL взаимоисключающие — если включить оба, Django упадёт при запуске:

python
# Порт 465 — шифрование сразу, без STARTTLS
EMAIL_PORT = 465
EMAIL_USE_TLS = False
EMAIL_USE_SSL = True
Адрес в поле From: должен быть на подтверждённом домене, иначе к нему не применяются подпись DKIM и запись SPF. Письмо от @yandex.ru или @mail.ru через сторонний релей не проходит DMARC и уходит в спам.

Первое письмо

send_mail() — самый короткий путь: тема, текст, отправитель, получатели:

python
from django.core.mail import send_mail

send_mail(
    subject="Подтверждение заказа №4417",
    message="Заказ принят. Доставка — завтра до 18:00.",
    from_email=None,            # возьмёт DEFAULT_FROM_EMAIL
    recipient_list=["client@example.ru"],
    fail_silently=False,
)

Оставляйте fail_silently=False. С True Django проглатывает все ошибки SMTP, и сломанная конфигурация выглядит точно так же, как рабочая. Проверить настройки, не написав ни одного view:

bash
python manage.py shell -c "from django.core.mail import send_mail; \
send_mail('Тест', 'Работает.', None, ['you@example.ru'], fail_silently=False)"

HTML-письмо

send_mail() отправляет только текст. Для HTML нужен EmailMultiAlternatives — он собирает multipart/alternative из обеих частей. Текстовую часть добавляйте всегда: письмо без неё почтовые фильтры считают признаком спама:

python
from django.core.mail import EmailMultiAlternatives

text = "Заказ принят. Доставка — завтра до 18:00."
html = "<h1>Заказ принят</h1><p>Доставка — завтра до 18:00.</p>"

msg = EmailMultiAlternatives(
    subject="Подтверждение заказа №4417",
    body=text,                  # текстовая часть — она нужна всегда
    to=["client@example.ru"],
)
msg.attach_alternative(html, "text/html")
msg.send()

Письмо из шаблона Django

HTML прямо во view быстро становится нечитаемым. Отрисуйте обычный шаблон и получите из него текстовую версию:

python
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.utils.html import strip_tags

html = render_to_string("email/order_confirmed.html", {"order": order})

msg = EmailMultiAlternatives(
    subject=f"Подтверждение заказа №{order.id}",
    body=strip_tags(html),
    to=[order.customer.email],
)
msg.attach_alternative(html, "text/html")
msg.send()

Несколько получателей

Если передать несколько адресов в recipient_list, они попадут в один заголовок To: и получатели увидят друг друга. Для писем клиентам отправляйте по одному письму на адрес, переиспользуя соединение:

python
from django.core.mail import EmailMultiAlternatives, get_connection

# Одно SMTP-соединение, отдельное письмо каждому получателю.
connection = get_connection()
connection.open()

for user in users:
    EmailMultiAlternatives(
        subject="Еженедельный отчёт",
        body=render_report(user),
        to=[user.email],        # ровно один получатель на письмо
        connection=connection,
    ).send()

connection.close()

Письмо с вложением

python
from django.core.mail import EmailMessage

msg = EmailMessage(
    subject="Счёт на оплату",
    body="Счёт за март во вложении.",
    to=["client@example.ru"],
)

msg.attach_file("/srv/invoices/2026-03.pdf")            # файл с диска
msg.attach("invoice.pdf", pdf_bytes, "application/pdf")  # или байты из памяти

msg.send()

Фоновая отправка через Celery

SMTP-бэкенд Django блокирующий: запрос ждёт соединение, TLS-рукопожатие и ответ сервера. Вынесите отправку в задачу Celery — пользователь получит ответ сразу, а временный сбой SMTP станет повтором вместо ошибки 500:

tasks.pypython
# tasks.py
from celery import shared_task
from django.core.mail import EmailMultiAlternatives


@shared_task(bind=True, max_retries=3, default_retry_delay=60)
def send_order_confirmation(self, order_id: int) -> None:
    order = Order.objects.get(pk=order_id)
    msg = EmailMultiAlternatives(
        subject=f"Подтверждение заказа №{order.id}",
        body=render_confirmation(order),
        to=[order.customer.email],
    )
    try:
        msg.send()
    except Exception as exc:      # временный сбой SMTP — повторим с задержкой
        raise self.retry(exc=exc)
python
# views.py — ответ уходит сразу, письмо отправит воркер
send_order_confirmation.delay(order.id)

Проверка без реальной отправки

python
# settings/dev.py — печатать письма в консоль вместо отправки
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"

В тестах менять ничего не нужно — Django подставляет бэкенд в памяти и складывает письма в django.core.mail.outbox:

python
from django.core import mail
from django.test import TestCase


class OrderEmailTests(TestCase):
    def test_confirmation_is_sent(self):
        place_order(self.customer)
        self.assertEqual(len(mail.outbox), 1)
        self.assertIn("Подтверждение", mail.outbox[0].subject)

Разбор ошибок

ОшибкаПричина и решение
SMTPAuthenticationError Неверный логин или пароль токена. Пароль показывается один раз — при создании.
SMTPServerDisconnected, таймаут соединения Хостинг блокирует почтовые порты. Поставьте EMAIL_PORT = 8587 или 8465.
SMTPNotSupportedError: STARTTLS extension not supportedEMAIL_USE_TLS = True на порту 465. Там нужен EMAIL_USE_SSL.
SMTPSenderRefused (550) Домен отправителя не подтверждён или не покрывается токеном.
Письмо принято, но не дошло Посмотрите журнал отправки в панели — там будет причина отказа.
SMTPRecipientsRefused Адрес получателя некорректен или попал в список подавления после жёсткого отказа.

Частые вопросы

Какой порт использовать в Django — 587 или 465?

587 вместе с EMAIL_USE_TLS = True — это STARTTLS, самый распространённый вариант для SMTP-бэкенда Django. Порт 465 тоже работает: там нужно EMAIL_USE_SSL = True и EMAIL_USE_TLS = False. Включать оба флага одновременно нельзя — Django выбросит ошибку при старте.

Почему Django выдаёт SMTPAuthenticationError?

Неверный логин или пароль. В EMAIL_HOST_USER нужен полный логин SMTP-токена вашего домена, а в EMAIL_HOST_PASSWORD — пароль, который показывается один раз при создании токена. Если пароль утерян, создайте новый токен: восстановить старый нельзя.

Почему письма уходят с локальной машины, но на сервере отваливаются по таймауту?

Большинство хостингов и облачных провайдеров блокируют исходящие подключения на порты 25, 465 и 587. Поменяйте EMAIL_PORT на высокий порт — 8587 для STARTTLS или 8465 для SSL/TLS — и соединение установится.

Можно ли отправлять письма прямо во view?

Не стоит. send_mail блокирует запрос на всё время SMTP-обмена, поэтому медленное соединение превращается в медленную страницу. Отдайте отправку в Celery, Django-Q или django-mailer и сразу возвращайте ответ — заодно получите автоматические повторы при временных сбоях.

Почему письма из Django попадают в спам?

Отправляйте с подтверждённого домена, чтобы работали DKIM и SPF, а DMARC сходился. В DEFAULT_FROM_EMAIL должен стоять адрес на этом домене, а не на Яндексе или Mail.ru. И всегда добавляйте текстовую часть рядом с HTML.

Как проверить отправку, не отправляя писем по-настоящему?

В разработке поставьте EMAIL_BACKEND в django.core.mail.backends.console.EmailBackend — письма будут печататься в консоль. В тестах Django сам подменяет бэкенд на locmem и складывает все сообщения в django.core.mail.outbox.

Что дальше