Лого

IMAP Python

Что такое IMAP?

IMAP - протокол, который позволяет тебе получить письма в почтовом ящике. В отличие от POP3, когда письма загружались на твое устройство, IMAP позволяет тебе манипулировать сообщениями прямо на сервере.

Так ты можешь искать нужное тебе письмо по теме, названию или еще чему-нибудь, сортировать по папкам или удалять ненужное. IMAP особенно полезен, если ты хочешь работать с почтой на нескольких устройствах одновременно, так как все изменения (например, пометка письма как прочитанного) синхронизируются между устройствами.

Основные понятия

Папки

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

Флаги

На каждое сообщение ты можешь повесить нужный статус - "прочитано", "помечено" и т.д. Эти все твои метки синхронизируются по всем устройствам.

Уникальные идентификаторы писем

Самое надежное, что есть у письма. Этот UID не меняется ни при перемещениях, ни при каких-либо других действиях. Позволяет надежно отслеживать сообщения и их статус.

Установка и настройка

Для работы с IMAP в Python предусмотрен модуль imaplib. При настройке ты должен указать:

  • имя IMAP сервера
  • порт
  • имя пользователя
  • ну и пароль

Давай практику, ёмоё!

Давай для начала подключимся к серверу и выберем папку:

    
        import imaplib

        mail = imaplib.IMAP4_SSL('imap.mail.ru', 993)
        mail.login('test@mail.ru', 'password')
        mail.select('INBOX')
    

Найти письма можно различными методами:

    
        # Поиск всех писем
        result, data = mail.search(None, 'ALL')

        # Поиск непрочитанных писем
        result, data = mail.search(None, 'UNSEEN')

        # Поиск писем от конкретного отправителя с указанной даты
        result, data = mail.search(None, f'FROM "{sender}" SINCE {current_time.strftime("%d-%b-%Y")}')

        # Поиск писем от конкретного отправителя
        result, data = mail.search(None, f'FROM "{sender}"')

        # Поиск писем к конкретному получателю
        result, data = mail.search(None, f'TO "{recipient}"')

        # Поиск писем с определенной темой
        result, data = mail.search(None, f'SUBJECT "{subject}"')

        # Поиск по определенному тексту в теле
        result, data = mail.search(None, f'BODY "{text}"')

        # Поиск писем до определенной даты
        result, data = mail.search(None, f'SENTBEFORE {current_time.strftime("%d-%b-%Y")}')

        # Поиск писем после определенной даты
        result, data = mail.search(None, f'SENTON {current_time.strftime("%d-%b-%Y")}')

        # Поиск писем с флагом "Помечено"
        result, data = mail.search(None, 'FLAGGED')

        # Поиск писем без флага "Помечено"
        result, data = mail.search(None, 'UNFLAGGED')
    

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

    
        import imaplib
        import email
        from email.header import decode_header
        from datetime import datetime

        result, data = mail.search(None, 'ALL')
        if result != "OK" or not data[0]:
            print("Нет писем для обработки.")
            exit()

        # Обработка писем
        for email_id in data[0].split():
            result, msg_data = mail.fetch(email_id, '(RFC822)')
            if result != "OK":
                print(f"Не удалось получить письмо с ID: {email_id}")
                continue

            msg = email.message_from_bytes(msg_data[0][1])

            # Декодирование темы
            subject = decode_header(msg['Subject'])[0][0] if msg['Subject'] else "Без темы"
            subject = subject.decode(decode_header(msg['Subject'])[0][1] or 'utf-8', errors='ignore') \
                if isinstance(subject, bytes) else subject

            # Декодирование отправителя
            from_ = decode_header(msg.get('From'))[0][0] if msg.get('From') else "Отправитель неизвестен"
            from_ = from_.decode(decode_header(msg.get('From'))[0][1] or 'utf-8', errors='ignore') \
                if isinstance(from_, bytes) else from_

            # Парсинг даты
            date_tuple = email.utils.parsedate_tz(msg['Date'])
            local_date = datetime.fromtimestamp(email.utils.mktime_tz(date_tuple)).date() \
                if date_tuple else "Дата неизвестна"

            # Извлечение тела письма
            body = ""
            for part in msg.walk():
                if part.get_content_type() == "text/plain" and not part.get_filename():
                    body = part.get_payload(decode=True).decode(part.get_content_charset() or 'utf-8', errors='ignore').strip()
                    break

            # Вывод информации о письме
            print(f"Тема: {subject}")
            print(f"Отправитель: {from_}")
            print(f"Дата: {local_date}")
            print(f"Тело письма: {body}")

        # Отключение от сервера
        mail.logout()
    

Заголовки писем могут быть закодированы в разных форматах (например, UTF-8). Функция decode_header помогает корректно декодировать их. Можешь еще сказать где же тут mail определяется. Но это лишь фрагмент кода, который ты добавишь к своей программе, где сам все сделаешь как надо. А мы пока посмотрим как вложение взять:

    
        for part in msg.walk():
        if part.get_content_disposition() == 'attachment':
            filename = part.get_filename()
            if filename:
                decoded_parts = decode_header(filename)
                decoded_name = ''.join(
                    str(part[0], part[1] or 'utf-8') if isinstance(part[0], bytes) else part[0]
                    for part in decoded_parts
                )
                print(f"Найдено вложение: {decoded_name}")
    

В итоге посмотрим на полный код:

    
        import imaplib
        import email
        from email.header import decode_header
        from datetime import datetime

        mail = imaplib.IMAP4_SSL('imap.mail.ru', 993)
        mail.login('test@mail.ru', 'password')
        mail.select('INBOX')

        result, data = mail.search(None, 'ALL')
        if result != "OK" or not data[0]:
            print("Нет писем для обработки.")
            exit()

        for email_id in data[0].split():
            result, msg_data = mail.fetch(email_id, '(RFC822)')
            if result != "OK":
                print(f"Не удалось получить письмо с ID: {email_id}")
                continue

            msg = email.message_from_bytes(msg_data[0][1])

            subject = decode_header(msg['Subject'])[0][0] if msg['Subject'] else "Без темы"
            subject = subject.decode(decode_header(msg['Subject'])[0][1] or 'utf-8', errors='ignore') \
                if isinstance(subject, bytes) else subject

            from_ = decode_header(msg.get('From'))[0][0] if msg.get('From') else "Отправитель неизвестен"
            from_ = from_.decode(decode_header(msg.get('From'))[0][1] or 'utf-8', errors='ignore') \
                if isinstance(from_, bytes) else from_

            date_tuple = email.utils.parsedate_tz(msg['Date'])
            local_date = datetime.fromtimestamp(email.utils.mktime_tz(date_tuple)).date() \
                if date_tuple else "Дата неизвестна"

            body = ""
            for part in msg.walk():
                if part.get_content_type() == "text/plain" and not part.get_filename():
                    body = part.get_payload(decode=True).decode(part.get_content_charset() or 'utf-8', errors='ignore').strip()
                    break

            print(f"Тема: {subject}")
            print(f"Отправитель: {from_}")
            print(f"Дата: {local_date}")
            print(f"Тело письма: {body}")

            for part in msg.walk():
                if part.get_content_disposition() == 'attachment':
                    filename = part.get_filename()
                    if filename:
                        decoded_parts = decode_header(filename)
                        decoded_name = ''.join(
                            str(part[0], part[1] or 'utf-8') if isinstance(part[0], bytes) else part[0]
                            for part in decoded_parts
                        )
                        print(f"Найдено вложение: {decoded_name}")

        mail.logout()
    

Заключение

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