Автоматическая рассылка отчётов в Python: рабочий гайд
Скрипт, который раз в неделю собирает CSV-отчёт и отправляет его на почту с вложением. Разберём отправку через SMTP, планировщик и типичные ошибки с аутентификацией.

В конце будет рабочий скрипт: он формирует Excel-отчёт из данных, прикладывает его к письму и отправляет на список адресов через SMTP. На настройку с нуля уйдёт 30–40 минут, если у вас уже есть Python 3.11+ и доступ к почтовому ящику с включённым SMTP. Автоматический запуск по расписанию покажу отдельно — через cron на Linux и через Планировщик задач на Windows.
Формат материала — инструкция. Мы не разбираем устройство протокола SMTP и не строим отказоустойчивую очередь писем. Задача узкая: отправить отчёт с вложением по расписанию и не сломаться на аутентификации.
Что нужно иметь до начала
Проверьте всё это до того, как писать код. Половина проблем с рассылкой возникает не в коде, а в доступах.
- Python 3.11 или новее. Модули
smtplibиemailвходят в стандартную библиотеку, ставить их не нужно. - Почтовый ящик с доступом по SMTP. У большинства провайдеров SMTP надо включить отдельно в настройках безопасности. Для Gmail и Яндекса обычный пароль от ящика не подойдёт — нужен пароль приложения (об этом ниже).
- Адрес и порт SMTP-сервера. Обычно это порт 465 (SSL) или 587 (STARTTLS). Точные значения смотрите в справке своего провайдера — они разные.
- Пакет
openpyxl, если отчёт будет в формате.xlsx. Устанавливается командойpip install openpyxl. Для простого CSV он не нужен.
Про пароль приложения
Не вставляйте в скрипт обычный пароль от почты. Во-первых, у Gmail, Яндекса и Mail.ru обычная аутентификация по паролю для сторонних программ отключена по умолчанию. Во-вторых, пароль в коде — прямая утечка при попадании файла в репозиторий.
Пароль приложения — это отдельный одноразовый пароль, который почта выдаёт конкретной программе. Создаётся он в настройках безопасности ящика (раздел обычно называется «Пароли приложений» или «App passwords»). Точный путь зависит от провайдера — сверьтесь с его справкой.
Учётные данные в скрипте — это утечка, которая ждёт своего часа. Держите логин и пароль в переменных окружения, а не в теле файла, даже если скрипт лежит на вашем личном ноутбуке.
Шаг 1. Соберём данные в отчёт
Для примера сделаем маленький Excel-файл из списка словарей. В реальном проекте вместо жёстко заданного списка будет выборка из базы или API.
import openpyxl
from datetime import date
def build_report(path):
"""Собирает xlsx-отчёт и сохраняет по указанному пути."""
# В реальности здесь запрос к БД или API
rows = [
{"menedzher": "Иванов", "sdelki": 12, "summa": 340000},
{"menedzher": "Петров", "sdelki": 8, "summa": 210000},
]
wb = openpyxl.Workbook()
ws = wb.active
ws.title = "Отчёт"
ws.append(["Менеджер", "Сделки", "Сумма"])
for r in rows:
ws.append([r["menedzher"], r["sdelki"], r["summa"]])
wb.save(path)
return path
if __name__ == "__main__":
report_name = f"report_{date.today().isoformat()}.xlsx"
build_report(report_name)
print(f"Отчёт готов: {report_name}")
Проверка: после запуска в папке появится файл вида report_2024-06-10.xlsx, а в консоли — строка «Отчёт готов». Откройте файл — в нём должна быть таблица с двумя строками данных.
Шаг 2. Соберём письмо с вложением
Для письма с вложением используется класс EmailMessage из модуля email.message. Он проще старого API с MIMEMultipart и не требует ручной сборки частей.
from email.message import EmailMessage
import mimetypes
def build_email(sender, recipients, subject, body, attachment_path):
"""Формирует письмо с одним вложением."""
msg = EmailMessage()
msg["From"] = sender
msg["To"] = ", ".join(recipients)
msg["Subject"] = subject
msg.set_content(body)
# Определяем тип вложения по расширению
ctype, _ = mimetypes.guess_type(attachment_path)
maintype, subtype = (ctype or "application/octet-stream").split("/", 1)
with open(attachment_path, "rb") as f:
msg.add_attachment(
f.read(),
maintype=maintype,
subtype=subtype,
filename=attachment_path,
)
return msg
Проверка: отдельно это не запустишь — функция возвращает объект письма. Убедиться, что всё собралось, можно после следующего шага, когда письмо реально уйдёт.
Шаг 3. Отправим через SMTP
Логин и пароль читаем из переменных окружения. В коде остаются только заглушки на случай, если переменная не задана.
import os
import smtplib
def send_email(msg, host, port, login, password):
"""Отправляет письмо по SSL (порт 465)."""
with smtplib.SMTP_SSL(host, port) as server:
server.login(login, password)
server.send_message(msg)
if __name__ == "__main__":
SMTP_HOST = "smtp.example.com" # адрес вашего SMTP-сервера
SMTP_PORT = 465 # 465 для SSL, 587 для STARTTLS
LOGIN = os.environ.get("MAIL_LOGIN", "YOUR_LOGIN")
PASSWORD = os.environ.get("MAIL_PASSWORD", "YOUR_APP_PASSWORD")
report = build_report(f"report_{date.today().isoformat()}.xlsx")
letter = build_email(
sender=LOGIN,
recipients=["boss@example.com", "analyst@example.com"],
subject="Еженедельный отчёт",
body="Отчёт за неделю во вложении.",
attachment_path=report,
)
send_email(letter, SMTP_HOST, SMTP_PORT, LOGIN, PASSWORD)
print("Письмо отправлено")
Если ваш сервер работает на порту 587 через STARTTLS, замените SMTP_SSL на связку smtplib.SMTP(host, port) и вызов server.starttls() перед логином. Какой из двух вариантов у вашего провайдера — смотрите в его справке, наугад не подбирайте.
Логин и пароль перед запуском задайте в окружении. На Linux и macOS:
export MAIL_LOGIN="you@example.com"
export MAIL_PASSWORD="YOUR_APP_PASSWORD"
python send_report.py
Проверка: в консоли появится «Письмо отправлено», а на почте получателей — письмо с вложенным xlsx-файлом. Если письма нет — загляните в папку «Спам» и в раздел «Отправленные» вашего ящика.
Шаг 4. Запуск по расписанию
Сам по себе скрипт ничего не рассылает регулярно — его должен кто-то запускать. Планировать запуск изнутри Python (через time.sleep в вечном цикле) — плохая идея: процесс упадёт, и рассылка молча остановится. Отдавайте расписание системному планировщику.
Linux и macOS — cron
Откройте crontab -e и добавьте строку. Пример — каждый понедельник в 9:00:
0 9 * * 1 /usr/bin/python3 /home/user/reports/send_report.py
Указывайте абсолютные пути и к Python, и к скрипту — cron запускается с урезанным окружением и не знает про ваши псевдонимы. Переменные MAIL_LOGIN и MAIL_PASSWORD тоже задайте явно в crontab или в отдельном файле, который скрипт подгружает.
Windows — Планировщик заданий
Создайте задачу в «Планировщике заданий»: в качестве программы укажите путь к python.exe, в аргументах — путь к скрипту. Расписание задаётся в свойствах триггера.
Если не получилось
Пять ошибок, которые встречаются чаще всего, и что за ними стоит.
SMTPAuthenticationError: Username and Password not accepted— вы используете обычный пароль вместо пароля приложения, либо SMTP не включён в настройках ящика. Создайте пароль приложения.ConnectionRefusedErrorили зависание наlogin— неверный порт или тип соединения. Порт 465 — только сSMTP_SSL, порт 587 — сSMTPиstarttls(). Перепутали — соединение не установится.FileNotFoundErrorна этапе вложения — скрипт запущен из другой папки, а путь к отчёту относительный. Стройте путь черезos.path.abspathили запускайте из папки со скриптом.- Письмо ушло, но получатель его не видит — попало в спам. Частая причина: адрес в поле From не совпадает с логином, под которым вы авторизовались на SMTP. Держите их одинаковыми.
- Cron молчит и ничего не шлёт — в cron не заданы переменные окружения с логином и паролем, скрипт падает на
login. Проверьте: перенаправьте вывод в лог, дописав в строку crontab>> /home/user/reports/cron.log 2>&1.
Что делать дальше
Базовая рассылка работает. Три вещи, которые стоит настроить следующими:
- Логирование. Замените
printна модульloggingс записью в файл — иначе при запуске из cron вы не узнаете, отправилось письмо или упало. - Обработка ошибок отправки. Оберните
send_emailвtry/exceptи при сбое шлите короткое уведомление себе — молчаливо упавшая рассылка хуже, чем видимая ошибка. - Секреты вне кода. Если скрипт живёт в репозитории, вынесите доступы в
.env-файл и добавьте его в.gitignore, а читайте через пакетpython-dotenv.
Массовая рассылка на сотни адресов — отдельная тема: там нужны отложенная очередь, лимиты провайдера и корректная отписка. Здесь мы делали внутренний отчёт на десяток адресатов, и для этой задачи связки smtplib с системным планировщиком достаточно.
Агент-продажник: Скрипты B2B-продаж, обработка возражений по BANT и SPIN.
Спросить за 20 ₽Источники: Python docs — email.message.EmailMessage, Python docs — smtplib (SMTP protocol client)
Частые вопросы
А если у меня Python 3.9, скрипт заработает?
Почему письмо уходит, но приходит в спам?
Обязательно ли делать отчёт в Excel? Можно проще?
Что будет, если сервер вернёт ошибку прямо во время отправки?
Как отправить письмо сразу нескольким получателям?
Безопасно ли хранить пароль в переменной окружения?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.