email-examples.md
1> **Источник:** https://python-all.ru/3.4/library/email-examples.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# 19.1.14. [`email`](https://python-all.ru/3.4/library/email.html#module-email): Examples89Вот несколько примеров использования пакета [`email`](https://python-all.ru/3.4/library/email.html#module-email) для чтения, записи и отправки простых email-сообщений, а также более сложных MIME-сообщений.1011Сначала посмотрим, как создать и отправить простое текстовое сообщение:1213```python14# Импорт smtplib для функции отправки15import smtplib1617# Импортируются необходимые модули email.18from email.mime.text import MIMEText1920# Открыть обычный текстовый файл для чтения. В этом примере предполагается, что21# текстовый файл содержит только символы ASCII.22with open(textfile) as fp:23 # Создать сообщение text/plain24 msg = MIMEText(fp.read())2526# me == адрес электронной почты отправителя27# you == адрес электронной почты получателя28msg['Subject'] = 'The contents of %s' % textfile29msg['From'] = me30msg['To'] = you3132# Отправить сообщение через собственный SMTP-сервер.33s = smtplib.SMTP('localhost')34s.send_message(msg)35s.quit()36```3738Разбор заголовков RFC822 легко выполняется с помощью методов parse(filename) или parsestr(message\_as\_string) класса Parser():3940```python41# Импортируются необходимые модули email.42from email.parser import Parser4344# Если заголовки e-mail находятся в файле, раскомментируйте эти две строки:45# with open(messagefile) as fp:46# headers = Parser().parse(fp)4748# Или для разбора заголовков из строки используйте:49headers = Parser().parsestr('From: <user@example.com>\n'50 'To: <someone_else@example.com>\n'51 'Subject: Test message\n'52 '\n'53 'Body would go here\n')5455# Теперь к элементам заголовков можно обращаться как к словарю:56print('To: %s' % headers['to'])57print('From: %s' % headers['from'])58print('Subject: %s' % headers['subject'])59```6061Вот пример отправки MIME-сообщения, содержащего много семейных фотографий, которые могут находиться в каталоге:6263```python64# Импорт smtplib для функции отправки65import smtplib6667# Вот модули пакета email, которые нам понадобятся68from email.mime.image import MIMEImage69from email.mime.multipart import MIMEMultipart7071COMMASPACE = ', '7273# Создать контейнер (внешнее) email-сообщение.74msg = MIMEMultipart()75msg['Subject'] = 'Our family reunion'76# me == адрес электронной почты отправителя77# family = список адресов электронной почты всех получателей78msg['From'] = me79msg['To'] = COMMASPACE.join(family)80msg.preamble = 'Our family reunion'8182# Предположим, что все файлы изображений в формате PNG.83for file in pngfiles:84 # Открыть файлы в двоичном режиме. Пусть класс MIMEImage автоматически85 # определяет конкретный тип изображения.86 with open(file, 'rb') as fp:87 img = MIMEImage(fp.read())88 msg.attach(img)8990# Отправляем email через собственный SMTP-сервер.91s = smtplib.SMTP('localhost')92s.send_message(msg)93s.quit()94```9596Вот пример отправки всего содержимого каталога в виде email- сообщения: [\[1\]](https://python-all.ru/3.4/library/email-examples.html#id4)9798```python99#!/usr/bin/env python3100101"""Отправляем содержимое каталога как MIME-сообщение."""102103import os104import sys105import smtplib106# Для определения типа MIME по расширению имени файла107import mimetypes108109from argparse import ArgumentParser110111from email import encoders112from email.message import Message113from email.mime.audio import MIMEAudio114from email.mime.base import MIMEBase115from email.mime.image import MIMEImage116from email.mime.multipart import MIMEMultipart117from email.mime.text import MIMEText118119COMMASPACE = ', '120121def main():122 parser = ArgumentParser(description="""\123Send the contents of a directory as a MIME message.124Unless the -o option is given, the email is sent by forwarding to your local125SMTP server, which then does the normal delivery process. Your local machine126must be running an SMTP server.127""")128 parser.add_argument('-d', '--directory',129 help="""Mail the contents of the specified directory,130 otherwise use the current directory. Only the regular131 files in the directory are sent, and we don't recurse to132 subdirectories.""")133 parser.add_argument('-o', '--output',134 metavar='FILE',135 help="""Print the composed message to FILE instead of136 sending the message to the SMTP server.""")137 parser.add_argument('-s', '--sender', required=True,138 help='The value of the From: header (required)')139 parser.add_argument('-r', '--recipient', required=True,140 action='append', metavar='RECIPIENT',141 default=[], dest='recipients',142 help='A To: header value (at least one required)')143 args = parser.parse_args()144 directory = args.directory145 if not directory:146 directory = '.'147 # Создать внешнее (оборачивающее) сообщение.148 outer = MIMEMultipart()149 outer['Subject'] = 'Contents of directory %s' % os.path.abspath(directory)150 outer['To'] = COMMASPACE.join(args.recipients)151 outer['From'] = args.sender152 outer.preamble = 'You will not see this in a MIME-aware mail reader.\n'153154 for filename in os.listdir(directory):155 path = os.path.join(directory, filename)156 if not os.path.isfile(path):157 continue158 # Определяем тип содержимого по расширению файла. Кодировка159 # будет проигнорирована, хотя стоит проверять простые вещи, такие как160 # gzip-файлы или сжатые файлы.161 ctype, encoding = mimetypes.guess_type(path)162 if ctype is None or encoding is not None:163 # Не удалось определить тип, или файл закодирован (сжат), поэтому164 # используем общий тип 'набор битов'.165 ctype = 'application/octet-stream'166 maintype, subtype = ctype.split('/', 1)167 if maintype == 'text':168 with open(path) as fp:169 # Примечание: нужно обработать вычисление кодировки.170 msg = MIMEText(fp.read(), _subtype=subtype)171 elif maintype == 'image':172 with open(path, 'rb') as fp:173 msg = MIMEImage(fp.read(), _subtype=subtype)174 elif maintype == 'audio':175 with open(path, 'rb') as fp:176 msg = MIMEAudio(fp.read(), _subtype=subtype)177 else:178 with open(path, 'rb') as fp:179 msg = MIMEBase(maintype, subtype)180 msg.set_payload(fp.read())181 # Закодировать полезную нагрузку с помощью Base64.182 encoders.encode_base64(msg)183 # Установить параметр filename.184 msg.add_header('Content-Disposition', 'attachment', filename=filename)185 outer.attach(msg)186 # Теперь отправляем или сохраняем сообщение187 composed = outer.as_string()188 if args.output:189 with open(args.output, 'w') as fp:190 fp.write(composed)191 else:192 with smtplib.SMTP('localhost') as s:193 s.sendmail(args.sender, args.recipients, composed)194195if __name__ == '__main__':196 main()197```198199Вот пример распаковки MIME-сообщения вроде показанного выше в каталог с файлами:200201```python202#!/usr/bin/env python3203204"""Распаковываем MIME-сообщение в каталог файлов."""205206import os207import sys208import email209import errno210import mimetypes211212from argparse import ArgumentParser213214def main():215 parser = ArgumentParser(description="""\216Unpack a MIME message into a directory of files.217""")218 parser.add_argument('-d', '--directory', required=True,219 help="""Unpack the MIME message into the named220 directory, which will be created if it doesn't already221 exist.""")222 parser.add_argument('msgfile')223 args = parser.parse_args()224225 with open(args.msgfile) as fp:226 msg = email.message_from_file(fp)227228 try:229 os.mkdir(args.directory)230 except FileExistsError:231 pass232233 counter = 1234 for part in msg.walk():235 # multipart/* – это просто контейнеры236 if part.get_content_maintype() == 'multipart':237 continue238 # Приложениям следует тщательно проверять имя файла, чтобы239 # email-сообщение не могло перезаписать важные файлы240 filename = part.get_filename()241 if not filename:242 ext = mimetypes.guess_extension(part.get_content_type())243 if not ext:244 # Использовать универсальное расширение для набора битов.245 ext = '.bin'246 filename = 'part-%03d%s' % (counter, ext)247 counter += 1248 with open(os.path.join(args.directory, filename), 'wb') as fp:249 fp.write(part.get_payload(decode=True))250251if __name__ == '__main__':252 main()253```254255Here’s an example of how to create an HTML message with an alternative plain text version: [\[2\]](https://python-all.ru/3.4/library/email-examples.html#id5)256257```python258#!/usr/bin/env python3259260import smtplib261262from email.mime.multipart import MIMEMultipart263from email.mime.text import MIMEText264265# me == мой адрес электронной почты.266# you == адрес электронной почты получателя.267me = "my@email.com"268you = "your@email.com"269270# Создать контейнер сообщения – правильный тип MIME – multipart/alternative.271msg = MIMEMultipart('alternative')272msg['Subject'] = "Link"273msg['From'] = me274msg['To'] = you275276# Создать тело сообщения (версии в виде простого текста и HTML).277text = "Hi!\nHow are you?\nHere is the link you wanted:\nhttps://www.python.org"278html = """\279<html>280 <head></head>281 <body>282 <p>Hi!<br>283 How are you?<br>284 Here is the <a href="https://www.python.org">link</a> you wanted.285 </p>286 </body>287</html>288"""289290# Записать MIME-типы обеих частей – text/plain и text/html.291part1 = MIMEText(text, 'plain')292part2 = MIMEText(html, 'html')293294# Прикрепить части к контейнеру сообщения.295# Согласно RFC 2046, последняя часть многокомпонентного сообщения, в данном случае296# HTML-сообщение, является наилучшим и предпочтительным.297msg.attach(part1)298msg.attach(part2)299300# Отправка сообщения через локальный SMTP-сервер.301s = smtplib.SMTP('localhost')302# Функция sendmail принимает 3 аргумента: адрес отправителя, адрес получателя303# и сообщение для отправки – здесь оно отправляется в виде одной строки.304s.sendmail(me, you, msg.as_string())305s.quit()306```307308## 19.1.14.1. Examples using the Provisional API309310Вот переработка последнего примера с использованием временного API. Чтобы сделать пример немного интереснее, в HTML-часть включается связанное изображение, а также сохраняется копия отправляемого сообщения на диск помимо самой отправки.311312Этот пример также показывает, как легко включать символы, отличные от ASCII, и упрощает отправку сообщения с помощью метода [`send_message()`](https://python-all.ru/3.4/library/smtplib.html#smtplib.SMTP.send_message) модуля [`smtplib`](https://python-all.ru/3.4/library/smtplib.html#module-smtplib).313314```python315#!/usr/bin/env python3316317import smtplib318319from email.message import EmailMessage320from email.headerregistry import Address321from email.utils import make_msgid322323# Создаём базовое текстовое сообщение.324msg = EmailMessage()325msg['Subject'] = "Ayons asperges pour le déjeuner"326msg['From'] = Address("Pepé Le Pew", "pepe@example.com")327msg['To'] = (Address("Penelope Pussycat", "penelope@example.com"),328 Address("Fabrette Pussycat", "fabrette@example.com"))329msg.set_content("""\330Salut!331332Cela ressemble à un excellent recipie[1] déjeuner.333334[1] http://www.yummly.com/recipe/Roasted-Asparagus-Epicurious-203718335336--Pepé337""")338339# Добавляем HTML-версию. Это преобразует сообщение в multipart/alternative.340# контейнер, где исходное текстовое сообщение – первая часть, а новое html341# сообщение – вторая часть.342asparagus_cid = make_msgid()343msg.add_alternative("""\344<html>345 <head></head>346 <body>347 <p>Salut!<\p>348 <p>Cela ressemble à un excellent349 <a href="http://www.yummly.com/recipe/Roasted-Asparagus-Epicurious-203718>350 recipie351 </a> déjeuner.352 </p>353 <img src="cid:{asparagus_cid}" \>354 </body>355</html>356""".format(asparagus_cid=asparagus_cid[1:-1]), subtype='html')357# Обратите внимание: для использования в html потребовалось снять угловые скобки с msgid.358359# Теперь добавьте связанное изображение в html-часть.360with open("roasted-asparagus.jpg", 'rb') as img:361 msg.get_payload()[1].add_related(img.read(), 'image', 'jpeg',362 cid=asparagus_cid)363364# Создается локальная копия отправляемых данных.365with open('outgoing.msg', 'wb') as f:366 f.write(bytes(msg))367368# Отправка сообщения через локальный SMTP-сервер.369with smtplib.SMTP('localhost') as s:370 s.send_message(msg)371```372373Если бы вместо этого нам отправили сообщение из последнего примера, вот один из способов его обработки:374375```python376import os377import sys378import tempfile379import mimetypes380import webbrowser381382# Импортируются необходимые модули email.383from email import policy384from email.parser import BytesParser385386# Воображаемый модуль, который мог бы выполнить эту работу и быть безопасным.387from imaginary import magic_html_parser388389# В реальной программе имя файла получается из аргументов.390with open('outgoing.msg', 'rb') as fp:391 msg = BytesParser(policy=policy.default).parse(fp)392393# Теперь к элементам заголовка можно обращаться как к словарю, и любые не-ASCII символы будут394# преобразованы в Unicode:395print('To:', msg['to'])396print('From:', msg['from'])397print('Subject:', msg['subject'])398399# Если мы хотим вывести предварительный просмотр содержимого сообщения, мы можем извлечь любое400# наименее отформатированную полезную нагрузку и вывести первые три строки. Конечно,401# если сообщение не содержит части в обычном тексте, вывод первых трех строк html402# вероятно, бесполезен, но это всего лишь концептуальный пример.403simplest = msg.get_body(preferencelist=('plain', 'html'))404print()405print(''.join(simplest.get_content().splitlines(keepends=True)[:3]))406407ans = input("View full message?")408if ans.lower()[0] == 'n':409 sys.exit()410411# Можно извлечь наиболее полную альтернативу для отображения:412richest = msg.get_body()413partfiles = {}414if richest['content-type'].maintype == 'text':415 if richest['content-type'].subtype == 'plain':416 for line in richest.get_content().splitlines():417 print(line)418 sys.exit()419 elif richest['content-type'].subtype == 'html':420 body = richest421 else:422 print("Don't know how to display {}".format(richest.get_content_type()))423 sys.exit()424elif richest['content-type'].content_type == 'multipart/related':425 body = richest.get_body(preferencelist=('html'))426 for part in richest.iter_attachments():427 fn = part.get_filename()428 if fn:429 extension = os.path.splitext(part.get_filename())[1]430 else:431 extension = mimetypes.guess_extension(part.get_content_type())432 with tempfile.NamedTemporaryFile(suffix=extension, delete=False) as f:433 f.write(part.get_content())434 # снова удалите угловые скобки, чтобы перейти от формы cid в email к форме в html.435 partfiles[part['content-id'][1:-1]] = f.name436else:437 print("Don't know how to display {}".format(richest.get_content_type()))438 sys.exit()439with tempfile.NamedTemporaryFile(mode='w', delete=False) as f:440 # Магический_парсер_html должен переписать атрибуты href="cid:...." так, чтобы они441 # указывали на имена файлов в partfiles. Он также должен выполнить safety-sanitize442 # HTML. Его можно написать с помощью html.parser.443 f.write(magic_html_parser(body.get_content(), partfiles))444webbrowser.open(f.name)445os.remove(f.name)446for fn in partfiles.values():447 os.remove(fn)448449# Конечно, есть много email-сообщений, которые могут нарушить эту простую450# программу, но она справится с наиболее распространенными.451```452453До приглашения вывод из примера выше:454455```python456To: Penelope Pussycat <"penelope@example.com">, Fabrette Pussycat <"fabrette@example.com">457From: Pepé Le Pew <pepe@example.com>458Subject: Ayons asperges pour le déjeuner459460Salut!461462Cela ressemble à un excellent recipie[1] déjeuner.463```464465Сноски466467| [\[1\]](https://python-all.ru/3.4/library/email-examples.html#id2) | Спасибо Matthew Dixon Cowles за оригинальную идею и примеры. |468| --- | --- |469470| [\[2\]](https://python-all.ru/3.4/library/email-examples.html#id3) | Автор: Martin Matejek. |471| --- | --- |472