Что такое 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 серверу, искать там нужные письма и извлекать из них необходимую информацию. Сюда остается только добавить обработку ошибок и логирование, про которое ты можешь прочитать в соседней моей заметке. И конечно не оставляй пароли внутри кода, для этого есть переменные окружения.