# Мастер-класс: автоматизация отправки профессиональных email через Python

## Метаданные

- **Спикер:** Corey Schafer
- **Канал:** Corey Schafer
- **Тема:** Практическое руководство по созданию и отправке автоматизированных писем, включая вложения, HTML-верстки и работу с серверами, для Python-разработчиков среднего уровня. Освоение навыка займет около 32 минут.
- **Длительность:** 31:56
- **YouTube:** https://www.youtube.com/watch?v=JRCJ6RtE3xU
- **Источник:** https://ekstraktznaniy.ru/workbook/1275

## Ключевые тезисы

1. **Подготовьте среду безопасности для доступа к почтовому серверу** — Настройте пароль приложения в настройках безопасности вашего почтового провайдера, например, Gmail. Это позволит скрипту аутентифицироваться без использования основного пароля, повышая безопасность вашего аккаунта.
2. **Используйте переменные окружения для хранения конфиденциальных данных** — Никогда не хардкодьте логины и пароли в коде. Используйте модуль os и системные переменные окружения, чтобы исключить возможность случайной утечки учетных данных при публикации кода в репозитории.
3. **Установите соединение с почтовым сервером через SMTP_SSL** — Применяйте класс SMTP_SSL для создания защищенного соединения с самого начала. Это упрощает код, избавляя от необходимости вручную вызывать команды STARTTLS и HELO.
4. **Формируйте структуру письма с помощью класса EmailMessage** — Используйте объект EmailMessage вместо ручного форматирования строк. Это обеспечивает чистоту кода и позволяет корректно добавлять заголовки, вложения и альтернативные форматы контента.
5. **Протестируйте отправку через локальный отладочный сервер** — Запустите локальный сервер командой 'python -m smtpd -c DebuggingServer -n localhost:1025'. Это позволит отлаживать отправку писем, не переполняя реальный почтовый ящик тестовыми сообщениями.
6. **Добавляйте вложения в письма любого типа** — Считывайте файлы в байтовом режиме (rb) и используйте метод add_attachment. Обязательно указывайте корректные типы MIME для отображения вложений в почтовых клиентах получателей.
7. **Автоматизируйте отправку нескольких файлов через цикл** — Создайте список путей к файлам и итерируйте его, вызывая метод add_attachment внутри цикла. Это значительно масштабирует возможности рассылки документов или медиа-контента.
8. **Обогащайте письма версткой с использованием HTML** — Добавляйте альтернативный контент через метод add_alternative с указанием подтипа 'html'. Это обеспечит стильное отображение для современных клиентов с сохранением текстового варианта для совместимости.

## Практические задания

### Задание 1: Настройка безопасного окружения

### Задание 2: Развертывание локального тестового сервера

### Задание 3: Создание шаблона письма с вложениями

### Задание 4: Интеграция HTML и Plain Text

## Ключевые цитаты

> «Никогда не добавляйте конфиденциальную информацию напрямую в скрипт. Используйте конфигурационные файлы или переменные окружения.»

> «Использование контекстного менеджера обеспечивает автоматическое закрытие соединения с почтовым сервером, что освобождает от ручного управления ресурсами.»

> «Класс EmailMessage — это более организованный и удобный способ формирования сообщений, который позволяет легко добавлять вложения и HTML-контент.»

> «Если вы часто тестируете код, используйте локальный отладочный сервер, чтобы избежать спама в собственном почтовом ящике.»

## Полный текст экстракта

# Мастер-класс: автоматизация отправки профессиональных email через Python

> 🎤 **Corey Schafer** — Кори Шефер — эксперт по программированию на Python с многолетним опытом создания образовательного контента для разработчиков любого уровня.


## ⚡ Зачем читать это руководство?
* **Безопасность прежде всего**: Вы научитесь хранить учетные данные вне кода, используя переменные окружения, что защитит ваш аккаунт от компрометации при публикации проектов на GitHub.
* **Профессиональный стандарт**: Переход от простых строк к классу `EmailMessage` позволит вам создавать сложные письма с вложениями и HTML-версткой, которые выглядят как результат работы CRM-системы.
* **Экономия времени на тестировании**: Вы освоите использование локального отладочного сервера `DebuggingServer`, что позволит мгновенно видеть результат работы скрипта без отправки реальных писем и засорения почтового ящика.

## 🗺 Карта навыков
| Уровень | Навык | Инструмент |
| :--- | :--- | :--- |
| Базовый | Подключение к SMTP | `smtplib.SMTP_SSL` |
| Средний | Структурирование письма | `email.message.EmailMessage` |
| Продвинутый | Работа с вложениями | `mimetypes` / `imghdr` |
| Экспертный | HTML-верстка | `add_alternative` |

## 1. Подготовка среды и безопасная аутентификация
Для автоматизации почтовой рассылки важно первым делом настроить среду так, чтобы ваш скрипт мог безопасно общаться с сервером. В видео Corey Schafer подчеркивает, что использование основного пароля от Gmail в коде — это критическая ошибка. Вместо этого необходимо настроить «пароли приложений» (App Passwords) в настройках безопасности Google, особенно если у вас включена двухфакторная аутентификация. Это создает специальный ключ доступа для вашего приложения, который можно легко отозвать в любой момент.

Следующим этапом является исключение «хардкодинга» данных. Даже если вы не выкладываете код публично, привычка хранить логины и пароли в переменных окружения — это золотой стандарт разработки. В Python мы используем модуль `os` для извлечения этих данных. Например, `email_user = os.environ.get('EMAIL_USER')` позволяет скрипту получить доступ к данным, не раскрывая их в исходном коде. Если вы работаете на Linux или macOS, вы можете добавить эти переменные в `.bash_profile` или `.zshrc`, а на Windows — через системные настройки переменных среды.

Corey Schafer отмечает в видео: 
> "I think it's a good habit to never add your sensitive information directly to your script but instead you should use configuration files that don't get committed to source control or use environment variables."

Это утверждение подчеркивает, что безопасность — это не просто дополнительная опция, а фундаментальная часть профессионального кода. Работая с почтовыми протоколами, вы также должны понимать разницу между `SMTP` и `SMTP_SSL`. Использование `SMTP_SSL` на порту 465 позволяет установить защищенное соединение сразу, избегая необходимости вручную вызывать команды `ehlo()` и `starttls()`, что значительно упрощает код и делает его более стабильным.

✅ **Сделайте сейчас**: Перейдите в настройки безопасности вашего почтового ящика и создайте пароль приложения. Установите две системные переменные окружения на вашем компьютере: `EMAIL_USER` (ваш email) и `EMAIL_PASS` (пароль приложения). Проверьте доступность переменных, выполнив `os.environ.get('EMAIL_USER')` в консоли Python.

## 2. Использование локального отладочного сервера для тестирования
Частая проблема при разработке почтовых ботов — бесконечные тестовые письма, которые захламляют ваш личный ящик или ящики ваших коллег. Решением этой проблемы является локальный отладочный сервер, который позволяет перехватывать исходящие SMTP-запросы прямо на вашем компьютере. Corey Schafer демонстрирует это на примере встроенного в Python модуля `smtpd`.

Чтобы запустить такой сервер, вам достаточно открыть терминал и выполнить команду: `python -m smtpd -c DebuggingServer -n localhost:1025`. После этого ваш компьютер начнет «слушать» порт 1025. Любое письмо, отправленное скриптом, вместо ухода в интернет будет просто напечатано в консоли. Это идеальный способ протестировать форматирование заголовков, корректность вложений и структуру HTML-тела письма без риска отправить что-то недоделанное реальному получателю.

В своем руководстве Corey Schafer объясняет:
> "If you're doing a lot of testing and don't actually want to send yourself an email each time, then you can use a local debug server to test this."

Этот подход критически важен, когда вы работаете с библиотекой `EmailMessage`. В отличие от старого способа конкатенации строк, `EmailMessage` позволяет удобно задавать заголовки (`msg['Subject']`, `msg['From']`, `msg['To']`) и тело письма. При использовании отладочного сервера вы увидите сырой вывод MIME-структуры письма в консоли, что поможет вам понять, как именно почтовые клиенты будут интерпретировать ваш контент. Это особенно полезно при добавлении вложений, так как вы сможете увидеть, правильно ли определены типы MIME и заголовки `Content-Disposition`. Такая практика экономит часы отладки, позволяя фокусироваться на логике, а не на борьбе с ограничениями почтовых провайдеров, которые могут блокировать подозрительную активность при частых отправках тестовых сообщений.

✅ **Сделайте сейчас**: Откройте отдельное окно терминала, запустите `python -m smtpd -c DebuggingServer -n localhost:1025` и измените настройки вашего SMTP-соединения в коде, указав `localhost` и порт `1025`. Отправьте тестовое письмо и изучите структуру MIME-сообщения, которая появится в терминале.

---

## 3. Профессиональная работа с вложениями: от изображений до документов

Когда ваша задача выходит за рамки простого текстового уведомления, наступает этап работы с вложениями. Corey Schafer демонстрирует, что библиотека `email.message.EmailMessage` справляется с этой задачей элегантно, если понимать принцип работы MIME-типов. Ключ к успеху здесь — использование библиотек для автоматического определения типа контента. В видео используется модуль `imghdr`, который позволяет программно узнать, является ли ваш файл JPEG-изображением или другим форматом, просто проанализировав сигнатуру файла. Это избавляет от необходимости жестко прописывать MIME-типы для каждого файла вручную.

Процесс добавления файла в письмо выглядит так: мы открываем файл в режиме чтения байтов (`'rb'`), считываем его содержимое, а затем вызываем метод `add_attachment`. Здесь важно правильно указать `main_type` и `sub_type`. Например, для изображений это будут `'image'` и `'jpeg'`, а для произвольных бинарных файлов, таких как PDF, используется пара `'application'` и `'octet-stream'`. Последний вариант — это своего рода «универсальный ключ», который говорит почтовому клиенту: «это бинарные данные, просто сохрани их как файл». В видео Corey Schafer показывает, как удобно организовать этот процесс через цикл `for`, перебирая список файлов из каталога проекта. Это позволяет отправлять пакеты документов, например, резюме или отчеты, одним нажатием кнопки, что делает ваш Python-скрипт полноценным инструментом для автоматизации документооборота.

Corey Schafer подчеркивает важность правильной обработки данных:
> "If you're attaching some other kind of file then you just need to change the main type, the subtype for each one. By setting the application as the main type and octet stream as the subtype that's what the Python documentation calls a generic bag of bits type."

Этот «generic bag of bits» — важный концепт для любого разработчика. Понимая, как почтовые сервера интерпретируют вложения, вы сможете избежать проблем с отображением файлов у получателей. Иногда полезно добавлять имя файла через параметр `filename`, чтобы пользователь не получал письмо с вложением вроде `unnamed_attachment`, а видел понятное название, например, `report_Q4.pdf`. Вся эта структура автоматически упаковывается библиотекой `email.message` в корректный MIME-пакет, обеспечивая совместимость с Outlook, Gmail, Apple Mail и другими клиентами.

✅ **Сделайте сейчас**: Создайте список из трех файлов разных форматов (например, `.jpg`, `.pdf`, `.txt`) в папке вашего проекта. Напишите цикл, который проходит по этому списку, определяет тип файла (используя `imghdr` для картинок и устанавливая `application/octet-stream` для остальных) и прикрепляет их к объекту `EmailMessage`. Отправьте письмо на свой тестовый сервер и убедитесь в консоли, что заголовки `Content-Disposition` для каждого файла сформированы верно.

## 4. Верстка профессиональных HTML-писем с фоллбэком

В современной разработке email-рассылок простого текста бывает недостаточно. HTML-письма позволяют добавлять брендированные элементы, заголовки с разным размером шрифта, выделения цветом и ссылки, что значительно повышает конверсию и читаемость. Однако, как отмечает Corey Schafer, нельзя забывать об обратной совместимости. Некоторые почтовые клиенты или настройки безопасности пользователей могут блокировать HTML, поэтому «золотой стандарт» — это отправка письма, содержащего как HTML-версию, так и обычный текстовый вариант в качестве альтернативы (фоллбэк).

Для реализации такой стратегии в Python используется метод `add_alternative`. Сначала вы задаете базовый контент с помощью `set_content()` (это будет текстовая версия письма), а затем добавляете HTML-версию через `add_alternative(html_content, subtype='html')`. Метод `add_alternative` автоматически связывает эти части в MIME-контейнер `multipart/alternative`. Это означает, что почтовый клиент получателя сам выберет, какой вариант отобразить: если клиент поддерживает HTML, он покажет верстку; если нет — пользователь увидит чистый текст. Это демонстрирует высокий уровень инженерной культуры, где удобство конечного пользователя ставится выше простоты написания кода.

В видео спикер акцентирует внимание на чистоте реализации:
> "In this email that I'm gonna send I'm gonna have HTML and plain text as an alternative because some people turn off HTML emails so you want to have that text as a fallback."

При верстке HTML-писем важно помнить о простых ограничениях: сложные CSS-фреймворки (вроде Bootstrap) здесь работают плохо. Лучше использовать инлайновые стили (style="...") непосредственно внутри тегов, чтобы верстка отображалась корректно во всех почтовых клиентах. Corey Schafer предлагает использовать простую структуру с `h1` тегами и встроенными стилями цвета, что является безопасной базой для любого автоматизированного письма. Используя этот подход, вы можете создавать автоматические отчеты, приветственные письма или новостные дайджесты, которые выглядят профессионально и не попадают в спам из-за некорректной MIME-структуры. Помните, что `add_alternative` — это мощный инструмент, который делает ваши скрипты не просто «отправщиками данных», а полноценными генераторами контента.

✅ **Сделайте сейчас**: Возьмите ваш текущий скрипт и замените обычное тело письма на конструкцию с `set_content` (простой текст) и `add_alternative` (HTML-строка с тегами `<h1>`, `<p>` и встроенным цветом `style="color: blue;"`). Отправьте письмо себе и откройте его в разных браузерах или почтовых приложениях, чтобы увидеть, как работает механизм альтернативного контента в реальности.

---

## 5. Рассылка на несколько адресов: управление списком получателей

В реальных сценариях автоматизации вам редко приходится отправлять письма одному человеку. Чаще всего это рассылки: уведомления клиентам, отчеты для менеджеров или ежедневные сводки для команды разработки. Corey Schafer обращает внимание на то, что поле `To` в объекте `EmailMessage` — это не просто строка, а поле, которое должно соответствовать стандартам SMTP-протокола. В Python-библиотеке `email.message` вы можете передать в это поле как одну строку, так и список адресов, объединенных в строку через запятую. Это важнейший нюанс, который часто вызывает ошибки у новичков, пытающихся передать обычный Python-список (`list`) напрямую.

Существует два основных способа решения этой задачи. Первый — ручное создание строки с адресами, разделенными запятыми, с использованием метода `.join()`. Например: `msg['To'] = ', '.join(contacts)`. Это самый чистый и надежный способ. Второй способ — использование заголовка `Bcc` (скрытая копия). Если ваша задача — разослать отчет группе людей так, чтобы они не видели адреса друг друга, вы должны использовать именно поле `Bcc`. В коде это выглядит так же просто: `msg['Bcc'] = ', '.join(contacts)`. Понимание разницы между `To`, `Cc` и `Bcc` делает ваш инструмент профессиональным, предотвращая нарушение конфиденциальности данных пользователей.

В своем видео Corey Schafer делится опытом:
> "When I was making this video I tested this with my email and the email of a friend of mine and it worked well it did send emails to both of us but this isn't how they have it in the Python documentation. In the documentation they have it where they are passing in a string of comma separated values."

Этот тезис подчеркивает важность следования официальной документации Python (`email.utils`). Использование `', '.join()` гарантирует, что заголовки письма будут сформированы корректно для любого почтового агента (MTA). Если вы передадите список напрямую, библиотека может выбросить исключение или сформировать некорректный заголовок, который почтовый сервер получателя воспримет как спам или просто не сможет обработать. Кроме того, при работе с большими списками рассылки (сотни и тысячи адресов) стоит помнить про лимиты SMTP-серверов. Не пытайтесь отправить письмо сразу тысяче человек в одном поле `To` — почтовые провайдеры (Gmail, Outlook) заблокируют такую активность как подозрительную. Для массовых рассылок лучше использовать цикл, который отправляет письма по отдельности, либо использовать специализированные API (типа SendGrid или Mailgun), но для внутренних нужд команды цикл `for` с индивидуальной отправкой — это «золотая середина».

✅ **Сделайте сейчас**: Создайте список `contacts` из 3–5 тестовых email-адресов. Напишите код, который объединяет эти адреса в строку через запятую и присваивает её полю `msg['To']`. Отправьте тестовое сообщение на ваш локальный отладочный сервер и изучите в консоли поле `To`. Убедитесь, что все адреса отображаются корректно и разделены запятыми, как того требует стандарт RFC 5322.

## 6. Безопасность учетных данных: почему пароли в коде — это табу

Последняя, но самая критичная тема — это безопасность. Corey Schafer категоричен в своем подходе: «Никогда не добавляйте чувствительную информацию прямо в скрипт». Это правило, которое отличает любителя от профессионала. Если вы загрузите скрипт с открытым паролем на GitHub, боты мгновенно украдут его и начнут использовать ваш аккаунт для рассылки спама, что приведет к моментальной блокировке вашего почтового адреса. Вместо этого необходимо использовать системные переменные окружения (`environment variables`) или `.env`-файлы, которые никогда не попадают в систему контроля версий (Git).

Работа с `os.environ` позволяет вам хранить конфигурацию отдельно от логики. В операционных системах Linux или macOS вы можете добавить экспорт переменных в файл `.bashrc` или `.zshrc`, а в Windows — использовать встроенный менеджер переменных среды. Когда ваш Python-скрипт запускается, он «видит» эти переменные так, будто они были прописаны в коде, но физически их нет в файле `.py`. Это позволяет вам безопасно делиться кодом, выкладывать его на GitHub или передавать коллегам, не опасаясь утечки ключей доступа. Более того, при использовании `os.environ.get('EMAIL_PASS')` вы получаете возможность настроить поведение скрипта при отсутствии переменной, например, выведя понятное сообщение об ошибке, а не просто упав с `KeyError`.

Corey Schafer акцентирует внимание на культуре разработки:
> "I think it's a good habit to never add your sensitive information directly to your script but instead you should use configuration files that don't get committed to source control or use environment variables."

Этот совет актуален не только для почтовых ботов, но и для работы с базами данных, API-ключами и облачными сервисами. Если вы планируете развивать свои навыки в Python, освоение модуля `os` и библиотек вроде `python-dotenv` станет фундаментом вашей карьеры. Профессиональный разработчик всегда думает о «чистоте» репозитория. Помните: если вы однажды случайно закоммитили пароль в репозиторий, считается, что он уже скомпрометирован. Даже если вы удалите его в следующем коммите, он навсегда останется в истории Git. Поэтому создание переменных окружения — это не прихоть, а способ защитить себя от необходимости менять пароли каждый раз, когда вы хотите сделать пуш в репозиторий.

✅ **Сделайте сейчас**: Если вы еще этого не сделали, удалите все жестко прописанные пароли из вашего файла. Установите переменные `EMAIL_USER` и `EMAIL_PASS` в настройках вашей ОС. В коде используйте `os.environ.get('EMAIL_USER')` для загрузки этих данных. После этого добавьте ваш `.env` файл (если вы его используете) в `.gitignore`, чтобы убедиться, что он никогда не попадет в ваш удаленный репозиторий. Проверьте работоспособность, запустив скрипт и убедившись, что он корректно подтягивает данные из окружения.

---

## 7. Работа с вложениями: расширение возможностей скрипта

В предыдущих разделах мы научились отправлять текст и HTML, но автоматизация бизнес-процессов часто требует обмена документами. Corey Schafer демонстрирует, что добавление вложений в Python — это не просто прикрепление файла, а процесс превращения двоичных данных (бинарных файлов) в структурированную часть MIME-сообщения. Метод `add_attachment` требует, чтобы вы считали файл в режиме `rb` (read bytes), что критически важно для корректной передачи изображений, PDF или архивов. Попытка прочитать файл в текстовом режиме приведет к повреждению данных и невозможности их открытия на стороне получателя.

Основная трудность для начинающих — определение типа контента. Если вы отправляете изображение, вам нужно указать `maintype='image'` и `subtype='jpeg'` (или другой формат). Для универсальных файлов, когда формат не определен заранее, используется «мешок битов» (generic bag of bits) — `maintype='application'` и `subtype='octet-stream'`. Библиотека `imghdr` (ныне замещаемая `python-magic` в современных проектах, но классическая в контексте видео) позволяет автоматически определять тип изображения по заголовкам файла, что исключает ошибки при обработке пользовательских данных.

Corey Schafer подчеркивает важность итеративного подхода:
> "When you're attaching multiple images, you can simply create a loop while making these attachments. By looping through a list of file paths, you can open, read, and attach each file sequentially without bloating your code."

Этот подход позволяет превратить ваш скрипт в мощный инструмент для генерации отчетов. Представьте, что ваш скрипт ежедневно собирает PDF-отчеты из базы данных, сохраняет их в папку и автоматически рассылает команде. Использование списка путей к файлам делает систему масштабируемой: вы можете добавлять или удалять файлы в списке, не меняя логику отправки. Важно помнить о размере сообщений: большинство SMTP-серверов имеют жесткие ограничения (обычно 20–25 МБ на письмо). Если ваш список файлов превышает этот лимит, скрипт выдаст ошибку `SMTPServerDisconnected`. В таких случаях профессионалы используют внешние хранилища (S3, облачные диски) и прикрепляют в письмо только ссылку на скачивание.

✅ **Сделайте сейчас**: Создайте список `attachments = ['report.pdf', 'data.csv', 'screenshot.png']`. Напишите цикл `for`, который проверяет существование каждого файла, считывает его данные и вызывает `msg.add_attachment`. Для CSV и PDF используйте `application/octet-stream`. Убедитесь, что после завершения цикла все вложения корректно отображаются в тестовом почтовом клиенте.

## 8. Искусство отладки: использование локальных серверов

Самая распространенная ошибка новичка — «спамить» на собственный ящик или ящик руководителя во время настройки логики скрипта. Corey Schafer предлагает элегантное решение — локальный отладочный сервер SMTP. Это встроенный в Python инструмент, который имитирует поведение почтового сервера, но вместо отправки письма в интернет, он просто выводит его содержимое в вашу консоль. Это позволяет проверять заголовки, MIME-структуру и содержимое вложений мгновенно, не дожидаясь ответа от серверов Google или Outlook.

Для запуска достаточно одной команды в терминале: `python -m smtpd -c DebuggingServer -n localhost:1025`. Флаг `-n` отключает DNS-запросы, а `-c` задает тип сервера. Когда скрипт соединяется с таким «сервером», он «думает», что письмо ушло, а вы видите весь «сырой» текст MIME-сообщения прямо в IDE или командной строке. Это лучший способ учиться тому, как устроен протокол email изнутри: вы увидите, как формируются границы (boundaries) сообщения, как кодируются вложения в Base64 и как прописываются заголовки.

Спикер акцентирует внимание на этом этапе разработки:
> "If you're doing a lot of testing and don't actually want to send yourself an email each time, you can use a local debug server to test this. It prints out the message to the terminal instead of actually sending an email."

Использование отладочного сервера — это признак зрелого подхода к разработке. Вы не засоряете почтовый ящик, не рискуете блокировкой аккаунта из-за частых запросов и можете проводить тесты в оффлайн-режиме (например, в самолете или при нестабильном интернет-соединении). Когда логика «отладочного» скрипта отточена, вы просто меняете параметры подключения (SMTP-хост и порт) на реальные, и скрипт готов к продакшену. Это избавляет вас от необходимости вносить правки в основной код, минимизируя риск внесения ошибок на финальной стадии.

✅ **Сделайте сейчас**: Запустите локальный сервер командой `python -m smtpd -c DebuggingServer -n localhost:1025` в отдельном терминале. Перенастройте ваш скрипт на `localhost` и порт `1025`. Отправьте письмо с вложением и текстом. Проанализируйте вывод в терминале: найдите блок `Content-Type: multipart/mixed`, увидьте, как закодированы ваши файлы, и убедитесь, что структура полностью соответствует стандарту, прежде чем переходить к реальной отправке.

## 🏋️ Практикум
1. **База**: Настройте SMTP-соединение, используя `smtplib.SMTP_SSL` через порт 465.
2. **Структура**: Перепишите создание письма с использованием класса `EmailMessage` для добавления заголовков `Subject`, `From` и `To`.
3. **HTML**: Создайте письмо с HTML-версткой, содержащей таблицу с 3 строками данных, и добавьте его как альтернативу простому тексту.
4. **Вложения**: Напишите функцию, принимающую список путей к файлам и добавляющую их в письмо в цикле.
5. **Безопасность**: Перенесите логин и пароль в `.env` файл, используя библиотеку `python-dotenv`.
6. **Логика**: Реализуйте отправку email группе получателей, используя `', '.join(contacts)` для корректного формирования заголовка `To`.
7. **Масштабирование**: Добавьте в скрипт обработку исключений `try-except`, чтобы при неудачной отправке одного письма скрипт не прекращал работу, а логировал ошибку.

## 🔑 Итоги: 5 действий на сегодня
1. Настройте пароль приложения в настройках безопасности вашего почтового аккаунта.
2. Удалите все пароли из кода и перенесите их в переменные окружения.
3. Установите библиотеку `python-dotenv` и создайте защищенный файл конфигурации.
4. Отработайте процесс отправки через локальный отладочный сервер `DebuggingServer`.
5. Проверьте свой код на наличие жестко заданных email-адресов и вынесите их в список для итерации.

## 💬 Цитаты для вдохновения
- "I think it's a good habit to never add your sensitive information directly to your script."
- "In this email that I'm gonna send I'm gonna have HTML and plain text as an alternative because some people turn off HTML emails."
- "Using a context manager will make sure that our connection is closed automatically without us needing to do it manually."
- "When you're attaching multiple images, you can simply create a loop while making these attachments."