Как отправить письмо из Python через smtplib: рабочий пример
Стандартная библиотека Python умеет отправлять почту без сторонних пакетов. За 15–20 минут вы отправите первое письмо через smtplib и email — с темой, текстом и вложением.

За 15–20 минут вы отправите письмо из Python — с темой, текстом и, если нужно, вложением. Понадобится только стандартная библиотека: модули smtplib и email входят в поставку Python, ставить ничего не нужно. Примеры проверены на Python 3.11 и 3.12; в этих версиях API стабильно и работает одинаково.
Главная сложность здесь не в коде, а в доступе к SMTP-серверу. Крупные почтовые провайдеры давно не пускают по обычному паролю от аккаунта — нужен либо пароль приложения, либо SMTP-доступ у транзакционного сервиса. Об этом подробно ниже.
Что нужно иметь до начала
- Python 3.8 или новее (проверьте командой
python --version). Код ниже написан под 3.11+. - Адрес SMTP-сервера, порт и логин с паролем. У Gmail это
smtp.gmail.com, порт 465 (SSL) или 587 (STARTTLS). У Яндекса —smtp.yandex.ru, порт 465. Точные параметры своего провайдера ищите в его справке по разделу «SMTP» или «настройка почтовой программы». - Пароль приложения, а не обычный пароль от почты. Gmail с 2022 года не принимает вход по основному паролю для сторонних программ — нужно включить двухфакторную аутентификацию и создать отдельный пароль приложения в настройках Google-аккаунта. У Яндекса и Mail.ru схема похожая.
SMTP (Simple Mail Transfer Protocol) — протокол отправки почты. Клиент подключается к серверу, аутентифицируется и передаёт письмо. Приём почты — это другие протоколы, IMAP и POP3, и в этой статье их не трогаем.
Шаг 1. Простое текстовое письмо
Начнём с минимума: одно текстовое письмо через защищённое соединение SSL (порт 465). Класс EmailMessage из модуля email.message сам соберёт корректные заголовки.
import smtplib
from email.message import EmailMessage
# Параметры подключения
SMTP_HOST = "smtp.gmail.com"
SMTP_PORT = 465
SMTP_USER = "you@gmail.com"
SMTP_PASSWORD = "YOUR_APP_PASSWORD" # пароль приложения, не пароль от почты
# Собираем письмо
message = EmailMessage()
message["Subject"] = "Тест отправки из Python"
message["From"] = SMTP_USER
message["To"] = "friend@example.com"
message.set_content("Привет. Это письмо отправлено скриптом на Python.")
# Отправляем через SSL-соединение
with smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT) as server:
server.login(SMTP_USER, SMTP_PASSWORD)
server.send_message(message)
print("Письмо отправлено")Запустите скрипт. Если всё настроено верно, в консоли появится строка Письмо отправлено, а адресат получит письмо в течение минуты. Проверьте и папку «Спам»: первое письмо от нового отправителя нередко попадает туда.
Почему set_content, а не строка вручную
Метод set_content() сам проставит заголовки Content-Type и кодировку UTF-8. Если собирать письмо руками из строк, кириллица легко превращается в набор вопросительных знаков у получателя. EmailMessage избавляет от этой возни.
Шаг 2. Порт 587 и STARTTLS
Не все серверы держат SSL на 465. Классический вариант — порт 587 с командой STARTTLS: соединение открывается открытым, а потом шифруется. Тогда вместо SMTP_SSL берут обычный SMTP и вызывают starttls().
import smtplib
from email.message import EmailMessage
SMTP_HOST = "smtp.gmail.com"
SMTP_PORT = 587
SMTP_USER = "you@gmail.com"
SMTP_PASSWORD = "YOUR_APP_PASSWORD"
message = EmailMessage()
message["Subject"] = "Письмо через порт 587"
message["From"] = SMTP_USER
message["To"] = "friend@example.com"
message.set_content("Соединение зашифровано через STARTTLS.")
with smtplib.SMTP(SMTP_HOST, SMTP_PORT) as server:
server.starttls() # переводим соединение в шифрованный режим
server.login(SMTP_USER, SMTP_PASSWORD)
server.send_message(message)
print("Отправлено через STARTTLS")Результат тот же. Разница только в порядке рукопожатия. Если сомневаетесь, какой порт использовать, начните с 465 и SMTP_SSL — он проще, там на один вызов меньше.
Шаг 3. Письмо с вложением
Чтобы прикрепить файл, прочитайте его в байтах и добавьте методом add_attachment(). Указывать MIME-тип вручную не обязательно — определим его через модуль mimetypes.
import smtplib
import mimetypes
from email.message import EmailMessage
from pathlib import Path
SMTP_HOST = "smtp.gmail.com"
SMTP_PORT = 465
SMTP_USER = "you@gmail.com"
SMTP_PASSWORD = "YOUR_APP_PASSWORD"
message = EmailMessage()
message["Subject"] = "Отчёт во вложении"
message["From"] = SMTP_USER
message["To"] = "friend@example.com"
message.set_content("Файл отчёта прикреплён к письму.")
# Читаем файл и определяем его тип
file_path = Path("report.pdf")
file_bytes = file_path.read_bytes()
mime_type, _ = mimetypes.guess_type(file_path.name)
maintype, subtype = (mime_type or "application/octet-stream").split("/", 1)
message.add_attachment(
file_bytes,
maintype=maintype,
subtype=subtype,
filename=file_path.name,
)
with smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT) as server:
server.login(SMTP_USER, SMTP_PASSWORD)
server.send_message(message)
print("Письмо с вложением отправлено")Проверка: у получателя рядом с текстом появится файл report.pdf. Крупные вложения провайдер может резать по размеру — у большинства лимит письма 20–25 МБ, но точное значение смотрите в справке провайдера.
Куда прятать пароль
Не оставляйте пароль строкой прямо в коде. Как только скрипт попадёт в репозиторий, пароль утечёт вместе с ним. Держите его в переменной окружения и читайте через os.environ.
import os
SMTP_PASSWORD = os.environ["SMTP_PASSWORD"] # значение задаётся снаружиПароль приложения в коде — та же ошибка, что пароль на стикере под клавиатурой. Разница лишь в том, что репозиторий видят больше людей, чем ваш стол.
Если не получилось
SMTPAuthenticationError: (535, ...)— сервер отклонил логин и пароль. У Gmail и Яндекса это почти всегда значит, что вы подставили обычный пароль вместо пароля приложения. Создайте пароль приложения в настройках аккаунта.smtplib.SMTPServerDisconnected: Connection unexpectedly closed— часто попытка отправить по открытому соединению туда, где сервер ждёт SSL или STARTTLS. Проверьте пару «порт + способ шифрования»: 465 →SMTP_SSL, 587 →SMTP+starttls().ConnectionRefusedErrorили зависание наlogin— порт закрыт. Провайдеры хостинга и некоторые сети блокируют исходящий SMTP. Проверьте доступность порта и уточните у хостера, открыт ли исходящий 465/587.UnicodeEncodeError— вы собираете тело или заголовки вручную. ИспользуйтеEmailMessageиset_content(): они ставят UTF-8 сами.- Письмо ушло, но не дошло — загляните в «Спам». Письма от нового отправителя без настроенных SPF/DKIM фильтруются жёстко.
Что делать дальше
Для разовых задач и внутренних уведомлений smtplib достаточно. Как только писем станет много или потребуется доставляемость до чужих ящиков, переключайтесь на транзакционный сервис (SendGrid, Mailgun, Unisender и подобные): они дают SMTP-доступ или HTTP-API и берут на себя SPF, DKIM и репутацию отправителя. Код при этом почти не меняется — вы просто подставляете их SMTP_HOST и ключ вместо пароля.
Следующий шаг для изучения — HTML-письма: у EmailMessage есть метод add_alternative() с параметром subtype="html", он добавляет HTML-версию рядом с текстовой. Синтаксис и примеры смотрите в разделе документации email.examples.
Мотивационное письмо и эссе: Поступление, стипендии, гранты, обмен
Спросить за 25 ₽Источники: smtplib — SMTP protocol client, документация Python, email.examples — примеры работы с email в Python
Частые вопросы
А если у меня Python 3.7 или старше?
Почему Gmail не принимает мой обычный пароль?
Скрипт зависает на строке login и ничего не происходит. Что не так?
Можно ли отправить письмо сразу нескольким получателям?
Письма уходят, но попадают в спам. Как это исправить?
smtplib блокирует программу на время отправки — можно ли асинхронно?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.