Привет-привет! С вами снова Оля — программист Учебного центра компании «Тензор». Ранее я писала здесь о разработке ботов в Telegram, но в моем отделе не скупятся на творческие задачи — так что и эта статья вряд ли будет последней.

В этой статье расскажу, как автоматизировать процесс рассылки писем по электронной почте. Статья будет полезна абсолютно каждому, кто когда-либо сталкивался с необходимостью отправлять информацию или рекламные сообщения большому количеству получателей.

Задача

Поскольку Тензор работает с сотней вузов по всей России и каждый год отправляет около 4000 сертификатов о прохождении обучения или поздравления от компании, мы решили автоматизировать этот процесс?.

Отправка писем по email — тема не новая, поэтому я направила силы в сторону интернета. Первый же запрос в google выдал нужный результат. Но радоваться было рано: при детальном разборе оказалось, что почта работает не так, как обычные мессенджеры. Сообщение, конечно, отправляется получателю, но не появляется в «Исходящих» вашего почтового ящика. В первых ссылках google по запросу «python отправить email» такая проблема не была даже упомянута ?.

Для нас важно видеть все отправленные письма. Выяснилось: чтобы сообщение появилось в «Исходящих», его нужно принудительно туда записать. Как отправлять письма по email так, чтобы они сохранялись в вашем почтовом ящике, а не канули в Лету, расскажу в этой статье.

Процесс

Шаг 1. Создание

Перед тем как отправить письмо, его нужно создать (ясно как день). Для этого используется библиотека email. Её возможностям нет предела: отформатировать текст письма (поддерживается html-разметка), указать тему и даже прикрепить необходимые файлы.

Шаг 2. Отправка

Для отправки используется библиотека smtplib. С ее помощью подключаемся к серверу и отправляем письмо. Обычно по умолчанию используется порт 25, но пути email неисповедимы. Тут все индивидуально.

Шаг 3. Добавление в «Исходящие»

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

Скрипт готов. Итоговый вариант для отправки писем и записи в «Исходящие» с подробными комментариями прикреплен ниже.

import os
import time
import imaplib
import smtplib
from email.mime.text import MIMEText
from email.header    import Header
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
from email.utils import formatdate


from_mail = "corp-mail@mail.ru"                           # Почта отправителя
from_passwd = "***"                                       # пароль от почты отправителя
server_adr = "***.mail.ru"                                # адрес почтового сервера
to_mail = 'students-mail@mail.ru'                         # адрес получателя

msg = MIMEMultipart()                                     # Создаем сообщение
msg["From"] = from_mail                                   # Добавляем адрес отправителя
msg['To'] = to_mail                                       # Добавляем адрес получателя
msg["Subject"] = Header('Тема сообщения', 'utf-8')        # Пишем тему сообщения
msg["Date"] = formatdate(localtime=True)                  # Дата сообщения
msg.attach(MIMEText("Текст сообщения", 'html', 'utf-8'))  # Добавляем форматированный текст сообщения

# Добавляем файл
filepath = "сертификат.pdf"                               # путь к файлу
part = MIMEBase('application', "octet-stream")            # Создаем объект для загрузки файла
part.set_payload(open(filepath,"rb").read())              # Подключаем файл
encoders.encode_base64(part)
part.add_header('Content-Disposition',
                f'attachment; filename="{os.path.basename(filepath)}"')
msg.attach(part)                                          # Добавляем файл в письмо

smtp = smtplib.SMTP(server_adr, 25)                       # Создаем объект для отправки сообщения 
smtp.starttls()                                           # Открываем соединение
smtp.ehlo()
smtp.login(from_mail, from_passwd)                        # Логинимся в свой ящик
smtp.sendmail(from_mail, to_mail, msg.as_string())        # Отправляем сообщения
smtp.quit()                                               # Закрываем соединение

# Сохраняем сообщение в исходящие
imap = imaplib.IMAP4(server_adr, 143)                     # Подключаемся в почтовому серверу
imap.login(from_mail, from_passwd)                        # Логинимся в свой ящик
imap.select('Sent')                                       # Переходим в папку Исходящие
imap.append('Sent', None,                                 # Добавляем наше письмо в папку Исходящие
            imaplib.Time2Internaldate(time.time()),
            msg.as_bytes())

Ошибки

Без них никуда. В работе я столкнулась с ошибкой mailbox full: мой почтовый ящик переполнился, письма перестали отражаться в «Исходящих», а рассылка продолжала работать.

Как избежать ошибки? Следите за объемом папки «Исходящие», иначе она переполнится и новых отправленных писем вы не увидите.

Спасибо за прочтение, успеха вашим рассылкам!

Комментарии (12)


  1. useribs
    18.04.2024 13:28

    А зачем именно в IMAP ящике сохранять, можно же в условные логи. Но да, раз уж готовая инфраструктура используется, почему бы нет. Честно говоря ожидал прочитать что-то про свой sendmail на Питоне )


  1. Eugone
    18.04.2024 13:28
    +1

    Интересно, а какой timeout у подобной рассылки? Можно ли подобным методом положить кому-то извне ящик?


    1. LolaS Автор
      18.04.2024 13:28
      +1

      В своем коде timeout не задаю. По умолчанию SMTP используется значение socket._global_default_timeout.


  1. kolabaister
    18.04.2024 13:28

    А это абстрактная задача или реальный кейс Тензора?


    1. LolaS Автор
      18.04.2024 13:28

      Это реальная задача, которую мы решили в рамках педагогического проекта.


  1. Fangaro
    18.04.2024 13:28

    Извините, но автоматизация отправки писем это разве не шаг к спаму?


    1. Klochko
      18.04.2024 13:28
      +1

      А Вы бы стали 4000 писем ручками отправлять?

      Я б точно нет. Спам это когда отправляют письма которые пользователи не хотели бы получать. Нежелательные письма, не неразрешенные адреса.

      А здесь же просто автоматизация рутинных действий.


  1. NAI
    18.04.2024 13:28
    +2

    Я конечно не питонщик(и не программист), но хранить логины и пароли в коде... не надо так. Хотя бы в ENV'ах.

    Про параллельность, забор данных писать наверно не стоит, этожеж не продуктовый код?

    Постановка задачи вызывает вопросы - зачем делать из почтового ящика базу данных? Давайте откровенно - вам письма в исходящих нужны чтобы всегда можно было найти когда отправили письмо и в какой текстовке. Не надо так. Воткните какую-ндь монгу и пишите в нее.


  1. PbIXTOP
    18.04.2024 13:28

    Не пойму, а зачем еще дополнительно с IMAP взаимодействовать, раньше это делалось или на стороне сервере или простой добавкой отправителя в скрытые получатели с дальнейшим разбором на стороне почтового сервера.


    1. LolaS Автор
      18.04.2024 13:28
      +1

      Нам требовалось сохранить письма в исходящие. После отправки письма автоматически туда не попадают. Это делается с помощью IMAP.


  1. ipatov_dn
    18.04.2024 13:28
    +1

    Интересное решение с хранением писем в исходящих, тк я работаю в уц тоже нужно рассылать сертификаты для этого я на фласке сделал рассылку с веб интерфейсом в котором нужно указать тему и приложить эл таблицу в которой все данные для заполнения шаблона удостоверения и шаблона эл письма, после чего пользователю приходит письму с текстом и вложение в виде сертификата в пдф, а мне отображается список кому отправилось а кому нет ... Если интересно я соберусь с мыслями и напишу статью про это


    1. LolaS Автор
      18.04.2024 13:28

      Буду рада почитать Вашу статью!