Статья Отправляем почту с помощью Python. Создадим простой класс для отправки сообщений

Электронная почта уже давно стала, можно сказать, обыденностью. И уже невозможно представить документооборот без пересылки сообщений. Также и Python, отправлять сообщения электронной почты умеет из коробки. Давайте разберемся, как с его помощью сделать отправку сообщений с трех бесплатных почтовых ящиков: gmail.com, mail.ru, yandex.ru.

IMG_1422.jpg


Что потребуется?

На этот раз ничего. Вот ничего не надо устанавливать. Уже все есть в стандартных библиотеках. Нужно их только импортировать в наш скрипт. Поэтому, давайте сделаем это:

Python:
import smtplib
import socket

from email.mime.text import MIMEText

Дополнительно импортируем библиотеку socket, чтобы отлавливать ошибки при отправке и библиотеку email, а точнее функцию MIMEText, которая будет автоматом перекодировать кириллицу в utf-8.


Хотя, немного слукавил. Нам потребуется открыть у всех трех сервисов доступ для небезопасных приложений. И если у Google раньше это можно было сделать перейдя на специальную страницу, то теперь же данная функция переехала в «Управление аккаунтом». Поэтому, заходим в «Управление аккаунтом –> Безопасность -> Двухэтапная аутентификация». Ее нужно включить. После этого тут же, только ниже Заходим в «Пароли приложений», создаем пароль и сразу же копируем и сохраняем. Посмотреть его после не получиться.

Теперь, что касается Яндекса. Тут, в общем-то, тоже все не особо сложно. Заходите в Яндекс, «Управление аккаунтом -> Пароли и авторизация -> Пароли приложений». Также создаете пароль приложения и копируете его для дальнейшего использования.

Ну и mail.ru. Заходите в почту. Щелкаете в правом углу по аккаунту. Выбираете «Пароль и безопасность -> Пароли для внешних приложений» и жмете кнопку «Добавить». Ну и также сохраняете полученный пароль.

Посидев и совсем немного подумав, я решил, что для отправки сообщений лучше сделать отдельный класс, так как у всех трех почт одни и те же параметры. Только у Яндекс.Почты слегка отличается способ ввода логина для авторизации. Все остальные параметры одинаковы.


Класс для отправки почтовых сообщений

Создадим класс PostSend. В его init добавим адрес отправителя, smtp-сервер, пароль приложения, сообщение для отправки и порт, который зададим по умолчанию, как необязательный параметр. Нужен он для того, чтобы, если порт отличается, задать свой.

Python:
class PostSend:
    def __init__(self, e_mail: str, smtp: str, pwd: str, mess: str, port=465):
        self.e_mail = e_mail
        self.smtp = smtp
        self.pwd = pwd
        self.mess = mess
        self.port = port

Теперь создадим функцию для отправки почтовых сообщений. Я назвал ее mail_send(self, send_to: str, subj: str). На входе она принимает еще два параметра, это адрес получателя и тема сообщения.
Давайте выполним подключений по защищенному протоколу SSL к почтовому серверу. Для этого будем использовать smtplib.SMTP_SSL. Данная функция принимает на вход адрес smtp-сервера, и порт для подключения.

s = smtplib.SMTP_SSL(self.smtp, self.port)

Обернем код в try – except, на случай возникновения ошибки соединения.
Теперь выполним проверку, принадлежит ли введенный адрес сервису yandex. Если да, возвращаем логин без собаки.

Python:
        if self.e_mail.split("@")[-1] in ['yandex.ru', 'yandex.com', 'yandex.by', 'yandex.kz', 'ya.ru']:
            login = self.e_mail.split("@")[0]
        else:
            login = self.e_mail

Затем выполняем аутентификацию на сервере с помощью логина и полученного пароля для приложения. Декодируем сообщение для отправки. Добавляем к сообщению поле От, Кому, Тема. И отправляем с помощью функции sendmail, куда передаем адрес отправителя, адрес получателя и сообщение с темой и нужными заголовками. Более того, для Яндекса наличие заголовков От обязательно, иначе он начинает ругаться. Закрываем соединение, после чего возвращаем из функции сообщение об успешной отправке почты.

Python:
        s.sendmail(self.e_mail, send_to, mess.as_string())
        s.quit()

Python:
import smtplib
import socket

from email.mime.text import MIMEText


class PostSend:
    def __init__(self, e_mail: str, smtp: str, pwd: str, mess: str, port=465):
        """
        Инициализируются указанные ниже параметры. Отправка сообщений
        с трех почтовых серверов: mail.ru, yandex.ru, gmail.com.

        :param e_mail: Адрес отправителя.
        :param smtp: smtp-сервер электронной почты.
        :param pwd: Пароль приложения для отправки почты.
        :param mess: Сообщение для отправки.
        :param port: Порт smtp-сервера. Необязательный параметр. Выставлен по умолчанию.
        """
        self.e_mail = e_mail
        self.smtp = smtp
        self.pwd = pwd
        self.mess = mess
        self.port = port

    def mail_send(self, send_to: str, subj: str):
        """
        Функция принимает адрес получателя и тему письма.
        Проверят, не является ли почта отправителя yandex.ru,
        так как там немного другие правила авторизации.
        Сообщение или шаблон переводятся в MIMEText, чтобы
        избежать проблемы с отправкой кириллицы. Подставляются
        поля кому, от кого, тема и отправляется по адресу.

        :param send_to: Адрес получателя.
        :param subj: Тема сообщения.
        :return: Сообщение об отправке или ошибке.
        """
        try:
            s = smtplib.SMTP_SSL(self.smtp, self.port)
        except socket.gaierror:
            return 'Ошибка соединения'
        if self.e_mail.split("@")[-1] in ['yandex.ru', 'yandex.com', 'yandex.by', 'yandex.kz', 'ya.ru']:
            login = self.e_mail.split("@")[0]
        else:
            login = self.e_mail
        s.login(login, self.pwd)
        mess = MIMEText(self.mess, 'html')
        mess['From'] = self.e_mail
        mess['To'] = send_to
        mess['Subject'] = subj
        s.sendmail(self.e_mail, send_to, mess.as_string())
        s.quit()
        return 'Письмо успешно отправлено'


Использование класса
Создание конфигурационных файлов


Давайте создадим отдельный модуль, для примера, назовем его start.py. Импортируем в него нужные библиотеки и созданный класс из модуля – у меня он называется post_send_new.

Python:
import json
import os.path

from post_send_new import PostSend

dir_set = os.path.join(os.getcwd(), 'set')

Создадим функцию make_addr(). Данная функция не будет принимать никаких параметров. Она служит для того, чтобы создать json-файл, в котором будут содержаться параметры для аутентификации на почтовом сервере.
Создадим словарь. В него мы сохраним запрошенные у пользователя параметры. После чего, запросим адрес электронной почты, пароль доступа приложения, а также из адреса почты сформируем адрес smtp-сервера и зададим порт для соединения.

Python:
    set_dict = dict()

    e_mail = input('\nВведите адрес электронной почты: ')
    pass_post = input('Введите пароль доступа приложения: ')
    smtp = f'smtp.{e_mail.split("@")[-1]}'
    port = 465

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

dir_set = os.path.join(os.getcwd(), 'set')

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

Python:
    if not os.path.exists(dir_set):
        os.mkdir(dir_set)
    set_dict.update({e_mail: {"smtp": smtp, "port": port, "pwd": pass_post}})
    with open(os.path.join(dir_set, f'{e_mail}.json'), 'w', encoding='utf-8') as post:
        json.dump(set_dict, post, indent=4, ensure_ascii=False)
    print('\nКонфигурация сохранена!')

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

Python:
ch = input('Желаете использовать созданный адрес в скрипте? Y/N >>> ')
    if ch.lower() == "y":
        return e_mail
    else:
        new = input('\nЖелаете добавить еще один адрес? Y/N >>> ')
        if new.lower() == "y":
            make_addr()
        else:
            start_set()
            return

Python:
def make_addr():
    """
    Запрашивает параметры почты у пользователя. Адрес, пароль приложения.
    Сохраняет полученные значения в файл json.
    Запрашивает у пользователя согласие на использование созданного
    адреса в текущем скрипте. Если да, возвращает адрес. Если нет,
    возвращается в стартовую функцию.

    :return: Возвращает адрес отправителя
    """
    set_dict = dict()

    e_mail = input('\nВведите адрес электронной почты: ')
    pass_post = input('Введите пароль доступа приложения: ')
    smtp = f'smtp.{e_mail.split("@")[-1]}'
    port = 465
    if not os.path.exists(dir_set):
        os.mkdir(dir_set)
    set_dict.update({e_mail: {"smtp": smtp, "port": port, "pwd": pass_post}})
    with open(os.path.join(dir_set, f'{e_mail}.json'), 'w', encoding='utf-8') as post:
        json.dump(set_dict, post, indent=4, ensure_ascii=False)
    print('\nКонфигурация сохранена!')
    ch = input('Желаете использовать созданный адрес в скрипте? Y/N >>> ')
    if ch.lower() == "y":
        return e_mail
    else:
        new = input('\nЖелаете добавить еще один адрес? Y/N >>> ')
        if new.lower() == "y":
            make_addr()
        else:
            start_set()
            return


Проверка наличия файлов конфигурации

Создадим функцию start_set(). Она, также как и предыдущая, не будет принимать никаких параметров.

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

Python:
    if os.path.exists(dir_set):
        list_post = [x.split(".json")[0] for x in os.listdir(dir_set) if x.endswith(".json")]
        if len(list_post) > 0:
            print('\nОбнаружены следующие доступные адреса:')
            for addr in list_post:
                print(f'   - Адрес: {addr}')
            ch = input('\nВведите адрес для использования в скрипте\n   или "new", для создания нового адреса.\n   '
                       '>>> ')
            if ch.lower() == 'new':
                make_addr()
            else:
                return ch

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

Python:
        else:
            print('Адреса не обнаружены!')
            ch = input('Желаете создать адрес? Y/N >>> ')
            if ch.lower() == "y":
                make_addr()
                return
            else:
                main()
                return
    else:
        print('Адреса не обнаружены!')
        ch = input('Желаете создать адрес? Y/N >>> ')
        if ch.lower() == "y":
            make_addr()
        else:
            main()
            return


Считывание настроек из конфигурационного файла

Создадим функцию open_set(address). На вход она принимает адрес электронной почты введенной пользователем. Затем считывается файла с конфигурацией, переводится в словарь и уже из словаря раскидываются значения по нужным переменным. Если в директории есть файл json, но он не содержит настроек, обработается исключение, выведется сообщение и выполнится возврат в стартовую функцию. Если файл с настройками не будет найден, также обработается исключение и выполнится возврат в стартовую функцию.

Python:
def open_set(address):
    """
    Считывают параметры из файла конфигурации и присваиваются в переменные.
    Имя файла конфигурации передается на вход функции.

    :param address: Адрес электронной почты для считывания параметров из файла.
    :return: Возвращает smtp-сервер, порт сервера, пароль приложения.
    """
    try:
        with open(os.path.join(dir_set, f'{address}.json'), 'r', encoding='utf-8') as file:
            read = dict(json.load(file))
            smtp = read[address]['smtp']
            port = read[address]['port']
            pwd = read[address]['pwd']
        return smtp, pwd, port
    except json.decoder.JSONDecodeError:
        print('Указанный файл не содержит настроек')
        start_set()
        return
    except FileNotFoundError:
        print('Файл не найден')
        start_set()
        return


Шаблон простого сообщения

Тут особо нечего описывать. В данной функции содержится html-шаблон простого сообщения, в который добавляется сообщение, введенное пользователем. Создадим функцию template(mess: str). На вход она принимает сообщение, которое ввел пользователь для отправки. И возвращает шаблон сообщения с сообщением пользователя.

Python:
def template(mess: str):
    """
    Шаблон простого сообщения для отправки.

    :param mess: Текстовое сообщение набранное пользователем.
    :return: Возвращает шаблон с сообщением в теле.
    """
    templ = f"""
    <!DOCTYPE html>
    <html lang="ru">
        <head>
            <meta charset="UTF-8">
            <meta http-equip="X-UA-Compatible" content="IE-edge">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>Сообщение</title>
        </head>
        <body>
            <span>{mess}</span>
    </body>
    </html>
    """
    return templ


Функция main()

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

Выполняются проверки и считываются параметры сервера, после чего создается экземпляр класса PostSend, в который передаются нужные параметры. После чего запускается метод класса mail_send, в который также передается адресат и тема сообщения.

Python:
def main():
    """
    Запрос параметров у пользователя. Ввод темы сообщения.
    Адресата. ВЫбор для отправки: простого сообщения, шаблона подготовленного
    заранее. Если пользователь выбирает простое сообщение, запрашивается его текст.
    Если шаблон, путь к заранее приготовленному шаблону.
    Выполняет проверка, если шаблон по указанному пути.
    Считываются параметры выбранной почты. Создается экземпляр
    класса для отправки сообщения. Передаются параметры: адрес отправителя,
    smtp-сервер, пароль приложения, шаблон сообщения, порт - необязательный параметр.
    ВЫзывается метод отправки сообщения. Выводится в терминал возврат из метода.

    :return: Выход из функции
    """
    address = start_set()
    subj = input('\nТема сообщения: ')
    send_to = input('Адресат сообщения: ')
    ch = input('\nОтправить:\n   [1] Текст сообщения\n   [2] Шаблон\n   >>> ')

    if ch == "1":
        mess = input("\nВведите сообщение для отправки: ")
        temp = template(mess)
    elif ch == "2":
        temp_local = input('\nВведите путь к html-шаблону: ')
        if not os.path.exists(temp_local):
            print('\nШаблона по указанному пути нет.')
            main()
            return
        with open(temp_local, 'r', encoding='utf-8') as file:
            temp = file.read()
    else:
        print('Вы не сделали выбор')
        main()
        return

    params = open_set(address)

    post = PostSend(address, params[0], params[1], temp, params[2])
    print(f'\n{post.mail_send(send_to, subj)}')


Заключение

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

Python:
import json
import os.path

from post_send_new import PostSend

dir_set = os.path.join(os.getcwd(), 'set')


def make_addr():
    """
    Запрашивает параметры почты у пользователя. Адрес, пароль приложения.
    Сохраняет полученные значения в файл json.
    Запрашивает у пользователя согласие на использование созданного
    адреса в текущем скрипте. Если да, возвращает адрес. Если нет,
    возвращается в стартовую функцию.

    :return: Возвращает адрес отправителя
    """
    set_dict = dict()

    e_mail = input('\nВведите адрес электронной почты: ')
    pass_post = input('Введите пароль доступа приложения: ')
    smtp = f'smtp.{e_mail.split("@")[-1]}'
    port = 465
    if not os.path.exists(dir_set):
        os.mkdir(dir_set)
    set_dict.update({e_mail: {"smtp": smtp, "port": port, "pwd": pass_post}})
    with open(os.path.join(dir_set, f'{e_mail}.json'), 'w', encoding='utf-8') as post:
        json.dump(set_dict, post, indent=4, ensure_ascii=False)
    print('\nКонфигурация сохранена!')
    ch = input('Желаете использовать созданный адрес в скрипте? Y/N >>> ')
    if ch.lower() == "y":
        return e_mail
    else:
        new = input('\nЖелаете добавить еще один адрес? Y/N >>> ')
        if new.lower() == "y":
            make_addr()
        else:
            start_set()
            return


def start_set():
    """
    Сканируется директория на наличие файлов конфигурации.
    Создается список файлов, который выводится в терминал.
    Если файлов не обнаружено, выполняется запрос на создание файла
    конфигурации.
    Запрашивается у пользователя адрес для использования в скрипте.

    :return: Возвращает адрес электронной почты из файла.
    """
    if os.path.exists(dir_set):
        list_post = [x.split(".json")[0] for x in os.listdir(dir_set) if x.endswith(".json")]
        if len(list_post) > 0:
            print('\nОбнаружены следующие доступные адреса:')
            for addr in list_post:
                print(f'   - Адрес: {addr}')
            ch = input('\nВведите адрес для использования в скрипте\n   или "new", для создания нового адреса.\n   '
                       '>>> ')
            if ch.lower() == 'new':
                make_addr()
            else:
                return ch
        else:
            print('Адреса не обнаружены!')
            ch = input('Желаете создать адрес? Y/N >>> ')
            if ch.lower() == "y":
                make_addr()
                return
            else:
                main()
                return
    else:
        print('Адреса не обнаружены!')
        ch = input('Желаете создать адрес? Y/N >>> ')
        if ch.lower() == "y":
            make_addr()
        else:
            main()
            return


def open_set(address):
    """
    Считывают параметры из файла конфигурации и присваиваются в переменные.
    Имя файла конфигурации передается на вход функции.

    :param address: Адрес электронной почты для считывания параметров из файла.
    :return: Возвращает smtp-сервер, порт сервера, пароль приложения.
    """
    try:
        with open(os.path.join(dir_set, f'{address}.json'), 'r', encoding='utf-8') as file:
            read = dict(json.load(file))
            smtp = read[address]['smtp']
            port = read[address]['port']
            pwd = read[address]['pwd']
        return smtp, pwd, port
    except json.decoder.JSONDecodeError:
        print('Указанный файл не содержит настроек')
        start_set()
        return
    except FileNotFoundError:
        print('Файл не найден')
        start_set()
        return


def template(mess: str):
    """
    Шаблон простого сообщения для отправки.

    :param mess: Текстовое сообщение набранное пользователем.
    :return: Возвращает шаблон с сообщением в теле.
    """
    templ = f"""
    <!DOCTYPE html>
    <html lang="ru">
        <head>
            <meta charset="UTF-8">
            <meta http-equip="X-UA-Compatible" content="IE-edge">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>Сообщение</title>
        </head>
        <body>
            <span>{mess}</span>
    </body>
    </html>
    """
    return templ


def main():
    """
    Запрос параметров у пользователя. Ввод темы сообщения.
    Адресата. ВЫбор для отправки: простого сообщения, шаблона подготовленного
    заранее. Если пользователь выбирает простое сообщение, запрашивается его текст.
    Если шаблон, путь к заранее приготовленному шаблону.
    Выполняет проверка, если шаблон по указанному пути.
    Считываются параметры выбранной почты. Создается экземпляр
    класса для отправки сообщения. Передаются параметры: адрес отправителя,
    smtp-сервер, пароль приложения, шаблон сообщения, порт - необязательный параметр.
    ВЫзывается метод отправки сообщения. Выводится в терминал возврат из метода.

    :return: Выход из функции
    """
    address = start_set()
    subj = input('\nТема сообщения: ')
    send_to = input('Адресат сообщения: ')
    ch = input('\nОтправить:\n   [1] Текст сообщения\n   [2] Шаблон\n   >>> ')

    if ch == "1":
        mess = input("\nВведите сообщение для отправки: ")
        temp = template(mess)
    elif ch == "2":
        temp_local = input('\nВведите путь к html-шаблону: ')
        if not os.path.exists(temp_local):
            print('\nШаблона по указанному пути нет.')
            main()
            return
        with open(temp_local, 'r', encoding='utf-8') as file:
            temp = file.read()
    else:
        print('Вы не сделали выбор')
        main()
        return

    params = open_set(address)

    post = PostSend(address, params[0], params[1], temp, params[2])
    print(f'\n{post.mail_send(send_to, subj)}')


if __name__ == "__main__":
    main()

А на этом, пожалуй, все.
Код из статьи, а также файла шаблона будет прикреплен к статье во вложении.

Спасибо за внимание. Надеюсь, что данная информация была вам полезна
 

Вложения

  • post_send.zip
    8,2 КБ · Просмотры: 194
Последнее редактирование модератором:

MughtyPuma

New member
13.08.2022
1
0
BIT
0
if self.e_mail.split("@")[-1] in ['yandex.ru', 'yandex.com', 'yandex.by', 'yandex.kz', 'ya.ru']:
login = self.e_mail.split("@")[0]
else:
login = self.e_mail

Можно получить развернутые комментарии по строкам?
 

Johan Van

Green Team
13.06.2020
363
691
BIT
390
if self.e_mail.split("@")[-1] in ['yandex.ru', 'yandex.com', 'yandex.by', 'yandex.kz', 'ya.ru']:
login = self.e_mail.split("@")[0]
else:
login = self.e_mail

Можно получить развернутые комментарии по строкам?

Если у вас есть почта Яндекса и Гугл, то обратите внимание какой именно логин используется для авторизации. К примеру, у Гугл это: test@gmail.com и далее пароль. А вот у Яндекса используется для авторизации только та часть, которая идет до собаки. Потому, делаем проверку. Для этого разбиваем адрес почты по собаке, забираем последний элемент, к примеру это будет yandex.ru и проверяем, есть ли данный элемент в списке. Если есть, значит почта Яндекса, следовательно, нужно использовать логин без собаки. Присваиваем переменной login значение адреса почты, разбитое по собаке, где забираем первую часть. К примеру, если адрес: test@yandex.tu, забираем test. Если же проверяемого значения нет в списке, следовательно, это не Яндекс, а значит, авторизация будет по полному адресу почты, то есть login присваиваем это значение.
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!