Обновлено 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().
Что указать в Python
| Поле | Значение |
|---|---|
| host | smtp.bulko.io |
| port | 587 с SMTP + starttls() (2525 при блокировке); 465 с SMTP_SSL |
| TLS | server.starttls(context=ssl.create_default_context()) на 587/2525 |
| login user | os.environ["SMTP_USERNAME"] (вида smtp_xxxxxxxxxxxxxxxx) |
| login password | os.environ["SMTP_PASSWORD"] |
| msg["From"] | Адрес на вашем домене, например Your App <[email protected]> |
| timeout | 30 секунд |
Настройка по шагам
- Купите SMTP-пакет на странице Bulko SMTP Relay и скопируйте логин и пароль в разделе Dashboard -> SMTP Access.
- Задайте переменные окружения
SMTP_USERNAMEиSMTP_PASSWORDтам, где запускается скрипт: в шелле, юните systemd, контейнере или секретах CI. - Соберите письмо через
email.message.EmailMessage: заголовкиFrom,ToиSubject, текст черезset_content()и при необходимости HTML черезadd_alternative(..., subtype="html"). Это современная замена связкеMIMEText/MIMEMultipart. - Для порта 587 откройте
smtplib.SMTP(HOST, 587, timeout=30)и доlogin()вызовитеstarttls(context=ssl.create_default_context()). Контекст по умолчанию проверяет сертификат и имя сервера. - Для порта 465 используйте
smtplib.SMTP_SSL(HOST, 465, context=context)и не вызывайтеstarttls(): соединение уже зашифровано. - Вызовите
login(USER, PASSWORD)иsend_message(msg). Метод берёт отправителя и получателей из заголовков (To, Cc и Bcc) и не передаёт сам заголовок Bcc. - В
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 supported | login() вызван до 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 писем в сутки на один доступ.
Что можно отправлять: правила использования.