Обновлено 2026-10-08

Как отправить письмо из Python через smtplib

Чтобы отправить письмо из Python, соберите email.message.EmailMessage, откройте smtplib.SMTP("smtp.bulko.io", 587), вызовите starttls() с ssl.create_default_context(), авторизуйтесь SMTP-логином и паролем и вызовите send_message(). Для порта 465 используйте smtplib.SMTP_SSL без вызова starttls().

Серверsmtp.bulko.io
Порт587 · 465 · 2525
ШифрованиеSTARTTLS · SSL
Логин и парольиз кабинета

Что указать в Python

ПолеЗначение
hostsmtp.bulko.io
port587 с SMTP + starttls() (2525 при блокировке); 465 с SMTP_SSL
TLSserver.starttls(context=ssl.create_default_context()) на 587/2525
login useros.environ["SMTP_USERNAME"] (вида smtp_xxxxxxxxxxxxxxxx)
login passwordos.environ["SMTP_PASSWORD"]
msg["From"]Адрес на вашем домене, например Your App <[email protected]>
timeout30 секунд

Настройка по шагам

  1. Купите SMTP-пакет на странице Bulko SMTP Relay и скопируйте логин и пароль в разделе Dashboard -> SMTP Access.
  2. Задайте переменные окружения SMTP_USERNAME и SMTP_PASSWORD там, где запускается скрипт: в шелле, юните systemd, контейнере или секретах CI.
  3. Соберите письмо через email.message.EmailMessage: заголовки From, To и Subject, текст через set_content() и при необходимости HTML через add_alternative(..., subtype="html"). Это современная замена связке MIMEText/MIMEMultipart.
  4. Для порта 587 откройте smtplib.SMTP(HOST, 587, timeout=30) и до login() вызовите starttls(context=ssl.create_default_context()). Контекст по умолчанию проверяет сертификат и имя сервера.
  5. Для порта 465 используйте smtplib.SMTP_SSL(HOST, 465, context=context) и не вызывайте starttls(): соединение уже зашифровано.
  6. Вызовите login(USER, PASSWORD) и send_message(msg). Метод берёт отправителя и получателей из заголовков (To, Cc и Bcc) и не передаёт сам заголовок Bcc.
  7. В From указывайте адрес на своём домене. Для выровненных SPF, DKIM и DMARC подтвердите домен в разделе Sending Domains личного кабинета Bulko.

С Python 3.12 старые аргументы keyfile и certfile у SMTP_SSL и starttls() удалены, поэтому всегда передавайте ssl.SSLContext через context=. Если исходящий 587 закрыт, используйте порт 2525 с тем же кодом SMTP + starttls(). Подробнее в статье о SMTP-портах.

send_test.py

# send_test.py  (Python 3.8+, standard library only)
import os
import smtplib
import ssl
from email.message import EmailMessage

HOST = "smtp.bulko.io"
USER = os.environ["SMTP_USERNAME"]
PASSWORD = os.environ["SMTP_PASSWORD"]

msg = EmailMessage()
msg["From"] = "Your App <[email protected]>"
msg["To"] = "[email protected]"
msg["Subject"] = "Python SMTP test"
msg.set_content("It works.")
msg.add_alternative("<p>It works.</p>", subtype="html")

context = ssl.create_default_context()

# Порт 587 (или 2525): обычное соединение, затем STARTTLS
with smtplib.SMTP(HOST, 587, timeout=30) as server:
    server.starttls(context=context)
    server.login(USER, PASSWORD)
    server.send_message(msg)

# Порт 465: TLS с первого байта
# with smtplib.SMTP_SSL(HOST, 465, context=context, timeout=30) as server:
#     server.login(USER, PASSWORD)
#     server.send_message(msg)

print("Accepted by the relay")

Проверка

Запустите SMTP_USERNAME=smtp_xxx SMTP_PASSWORD=yourpass python3 send_test.py, указав в заголовке To реальный адрес. Если скрипт напечатал Accepted by the relay без трейсбека, релей принял письмо. Проверьте ящик через минуту-две.

Чтобы увидеть весь SMTP-диалог, добавьте server.set_debuglevel(1) сразу после открытия соединения. Исключения вроде SMTPAuthenticationError или SMTPDataError содержат код ответа сервера, по которому удобно искать причину в таблице ниже.

Ошибки и решения

ПроблемаРешение
smtplib.SMTPAuthenticationError: (535, ...)Неверный логин или пароль, либо переменные окружения пустые. Скопируйте их заново из Dashboard -> SMTP Access. Подробнее: https://bulko.io/ru/smtp-errors/535/
ssl.SSLError: [SSL: WRONG_VERSION_NUMBER]SMTP_SSL использован на порту 587. На 587 нужен smtplib.SMTP со starttls(), SMTP_SSL только для 465.
SMTPServerDisconnected или зависание на 465На порту 465 использован обычный smtplib.SMTP, а сервер ждёт TLS сразу. Для 465 нужен SMTP_SSL.
SMTPNotSupportedError: SMTP AUTH extension not supportedlogin() вызван до starttls() на порту 587. Сначала starttls(), потом login().
TimeoutError или ConnectionRefusedErrorХостинг закрывает исходящие SMTP-порты. Попробуйте порт 2525 с SMTP + starttls() и не убирайте timeout.
smtplib.SMTPDataError: (554, b'5.7.1 ...')Письмо отклонил ИИ-фильтр контента (фишинг, вредоносное ПО, имитация бренда, мошенничество). Правила: https://bulko.io/ru/acceptable-use/

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

Что выбрать: SMTP со starttls() или SMTP_SSL?

smtplib.SMTP со starttls() для портов 587 и 2525, smtplib.SMTP_SSL для порта 465. Оба варианта шифруют сессию, разница только в том, когда начинается TLS.

Нужно ли вызывать ehlo() вручную?

Обычно нет. starttls() и login() сами отправляют EHLO, когда нужно. Лишний вызов ehlo() ничего не сломает.

Можно ли читать ответы через этот сервер?

Нет. Bulko SMTP работает только на отправку, IMAP нет. Ответы приходят в ящик From или Reply-To, и читать их можно через imaplib у вашего почтового провайдера.

Сколько писем в день может отправить скрипт?

По умолчанию 2 000 писем в сутки на один SMTP-аккаунт. Для пачки писем используйте одно соединение, а за повышением лимита обращайтесь на [email protected].

Сколько это стоит?

Разовые пакеты: $5 за 5 000 писем, $15 за 20 000, $30 за 50 000, $60 за 150 000. Неиспользованные письма не сгорают, бесплатного пробного периода для SMTP нет.

Получить SMTP-доступ для Python

Разовая оплата картой или криптой, без подписки. Неиспользованные письма не сгорают. Лимит 2 000 писем в сутки на один доступ.

SMTP Relay 5K: $5 · 5000SMTP Relay 20K: $15 · 20000SMTP Relay 50K: $30 · 50000SMTP Relay 150K: $60 · 150000
Тарифы SMTP-релея

Что можно отправлять: правила использования.