email: 예제

다음은 email 패키지를 사용하여 간단한 전자 우편 메시지뿐만 아니라 더 복잡한 MIME 메시지를 읽고 쓰고 보내는 방법에 대한 몇 가지 예입니다.

먼저 간단한 텍스트 메시지를 만들고 보내는 방법을 살펴보겠습니다 (텍스트 내용과 주소에 유니코드 문자가 포함될 수 있습니다):

# 실제 전송 기능을 위해 smtplib를 임포트 합니다
import smtplib

# 필요한 email 모듈을 임포트 합니다
from email.message import EmailMessage

# textfile에 있는 이름의 텍스트 파일을 읽기 위해 엽니다.
with open(textfile) as fp:
    # text/plain 메시지를 만듭니다
    msg = EmailMessage()
    msg.set_content(fp.read())

# me == 발신자의 전자 우편 주소
# you == 수신자의 전자 우편 주소
msg['Subject'] = f'The contents of {textfile}'
msg['From'] = me
msg['To'] = you

# 자체 SMTP 서버를 통해 메시지를 보냅니다.
s = smtplib.SMTP('localhost')
s.send_message(msg)
s.quit()

parser 모듈의 클래스를 사용하여 RFC 822 헤더를 쉽게 구문 분석할 수 있습니다:

# 필요한 email 모듈을 임포트 합니다
from email.parser import BytesParser, Parser
from email.policy import default

# 전자 우편 헤더가 파일에 있으면, 이 두 줄의 주석 처리를 제거하십시오:
# with open(messagefile, 'rb') as fp:
#     headers = BytesParser(policy=default).parse(fp)

#  또는 문자열에서 헤더를 구문 분석하려면 (이는 드문 작업입니다), 다음을 사용하십시오:
headers = Parser(policy=default).parsestr(
        'From: Foo Bar <user@example.com>\n'
        'To: <someone_else@example.com>\n'
        'Subject: Test message\n'
        '\n'
        'Body would go here\n')

#  이제 헤더 항목을 딕셔너리로 액세스 할 수 있습니다:
print('To: {}'.format(headers['to']))
print('From: {}'.format(headers['from']))
print('Subject: {}'.format(headers['subject']))

# 여러분은 또한 주소의 일부에 액세스 할 수 있습니다:
print('Recipient username: {}'.format(headers['to'].addresses[0].username))
print('Sender name: {}'.format(headers['from'].addresses[0].display_name))

다음은 디렉터리에 있을 수 있는 가족사진을 포함하는 MIME 메시지를 보내는 방법의 예입니다:

# 실제 전송 기능을 위해 smtplib를 임포트 합니다
import smtplib

# 그리고 이미지의 유형을 찾기 위해 imghdr
import imghdr

# 다음은 필요한 email 패키지입니다
from email.message import EmailMessage

# 컨테이너 전자 우편 메시지를 만듭니다.
msg = EmailMessage()
msg['Subject'] = 'Our family reunion'
# me == 발신자의 전자 우편 주소
# family = 모든 수신자의 전자 우편 주소 목록
msg['From'] = me
msg['To'] = ', '.join(family)
msg.preamble = 'You will not see this in a MIME-aware mail reader.\n'

# 바이너리 모드로 파일을 엽니다. imghdr을 사용하여 각 특정 이미지의 MIME 서브 
# 유형을 파악합니다.
for file in pngfiles:
    with open(file, 'rb') as fp:
        img_data = fp.read()
    msg.add_attachment(img_data, maintype='image',
                                 subtype=imghdr.what(None, img_data))

# 자체 SMTP 서버를 통해 전자 우편을 보냅니다.
with smtplib.SMTP('localhost') as s:
    s.send_message(msg)

다음은 디렉터리의 전체 내용을 전자 우편 메시지로 보내는 방법의 예입니다: 1

#!/usr/bin/env python3

"""디렉터리의 내용을 MIME 메시지로 보냅니다."""

import os
import smtplib
# 파일 이름 확장자를 기반으로 MIME 유형을 추측하기 위해
import mimetypes

from argparse import ArgumentParser

from email.message import EmailMessage
from email.policy import SMTP


def main():
    parser = ArgumentParser(description="""\
Send the contents of a directory as a MIME message.
Unless the -o option is given, the email is sent by forwarding to your local
SMTP server, which then does the normal delivery process.  Your local machine
must be running an SMTP server.
""")
    parser.add_argument('-d', '--directory',
                        help="""Mail the contents of the specified directory,
                        otherwise use the current directory.  Only the regular
                        files in the directory are sent, and we don't recurse to
                        subdirectories.""")
    parser.add_argument('-o', '--output',
                        metavar='FILE',
                        help="""Print the composed message to FILE instead of
                        sending the message to the SMTP server.""")
    parser.add_argument('-s', '--sender', required=True,
                        help='The value of the From: header (required)')
    parser.add_argument('-r', '--recipient', required=True,
                        action='append', metavar='RECIPIENT',
                        default=[], dest='recipients',
                        help='A To: header value (at least one required)')
    args = parser.parse_args()
    directory = args.directory
    if not directory:
        directory = '.'
    # 메시지를 만듭니다
    msg = EmailMessage()
    msg['Subject'] = f'Contents of directory {os.path.abspath(directory)}'
    msg['To'] = ', '.join(args.recipients)
    msg['From'] = args.sender
    msg.preamble = 'You will not see this in a MIME-aware mail reader.\n'

    for filename in os.listdir(directory):
        path = os.path.join(directory, filename)
        if not os.path.isfile(path):
            continue
        # 파일 확장자를 기준으로 콘텐츠 유형을 추측합니다. gzip 되었거나 압축된 
        # 파일과 같은 간단한 사항을 확인해야 하지만, 인코딩은 무시됩니다.
        ctype, encoding = mimetypes.guess_type(path)
        if ctype is None or encoding is not None:
            # 추측할 수 없거나 파일이 인코딩(압축)되었기 때문에, 일반적인 비트 덩어리 유형을 사용합니다.
            ctype = 'application/octet-stream'
        maintype, subtype = ctype.split('/', 1)
        with open(path, 'rb') as fp:
            msg.add_attachment(fp.read(),
                               maintype=maintype,
                               subtype=subtype,
                               filename=filename)
    # 이제 메시지를 보내거나 저장합니다
    if args.output:
        with open(args.output, 'wb') as fp:
            fp.write(msg.as_bytes(policy=SMTP))
    else:
        with smtplib.SMTP('localhost') as s:
            s.send_message(msg)


if __name__ == '__main__':
    main()

다음은 위와 같은 MIME 메시지를 디렉터리로 푸는 방법의 예입니다:

#!/usr/bin/env python3

"""MIME 메시지를 디렉터리에 풉니다."""

import os
import email
import mimetypes

from email.policy import default

from argparse import ArgumentParser


def main():
    parser = ArgumentParser(description="""\
Unpack a MIME message into a directory of files.
""")
    parser.add_argument('-d', '--directory', required=True,
                        help="""Unpack the MIME message into the named
                        directory, which will be created if it doesn't already
                        exist.""")
    parser.add_argument('msgfile')
    args = parser.parse_args()

    with open(args.msgfile, 'rb') as fp:
        msg = email.message_from_binary_file(fp, policy=default)

    try:
        os.mkdir(args.directory)
    except FileExistsError:
        pass

    counter = 1
    for part in msg.walk():
        # multipart/* 는 단지 컨테이너입니다
        if part.get_content_maintype() == 'multipart':
            continue
        # 전자 우편 메시지를 사용하여 중요한 파일을 덮어쓸 수 없도록 응용 프로그램은
        # 주어진 파일명을 청소해야 합니다.
        filename = part.get_filename()
        if not filename:
            ext = mimetypes.guess_extension(part.get_content_type())
            if not ext:
                # 일반적인 비트 덩어리 확장자를 사용합니다
                ext = '.bin'
            filename = f'part-{counter:03d}{ext}'
        counter += 1
        with open(os.path.join(args.directory, filename), 'wb') as fp:
            fp.write(part.get_payload(decode=True))


if __name__ == '__main__':
    main()

다음은 대체 일반 텍스트 버전으로 HTML 메시지를 만드는 방법의 예입니다. 좀 더 흥미롭게 하기 위해, html 부분에 관련 이미지를 포함하고, 보낼 뿐만 아니라, 보낼 것의 사본을 디스크에 저장합니다.

#!/usr/bin/env python3

import smtplib

from email.message import EmailMessage
from email.headerregistry import Address
from email.utils import make_msgid

# 기본 텍스트 메시지를 만듭니다.
msg = EmailMessage()
msg['Subject'] = "Ayons asperges pour le déjeuner"
msg['From'] = Address("Pepé Le Pew", "pepe", "example.com")
msg['To'] = (Address("Penelope Pussycat", "penelope", "example.com"),
             Address("Fabrette Pussycat", "fabrette", "example.com"))
msg.set_content("""\
Salut!

Cela ressemble à un excellent recipie[1] déjeuner.

[1] http://www.yummly.com/recipe/Roasted-Asparagus-Epicurious-203718

--Pepé
""")

# html 버전을 추가합니다. 그러면 메시지가 원본 텍스트 메시지가 첫 번째 부분이고 새 
# html 메시지가 두 번째 부분인 multipart/alternative 컨테이너로 변환됩니다.
asparagus_cid = make_msgid()
msg.add_alternative("""\
<html>
  <head></head>
  <body>
    <p>Salut!</p>
    <p>Cela ressemble à un excellent
        <a href="http://www.yummly.com/recipe/Roasted-Asparagus-Epicurious-203718">
            recipie
        </a> déjeuner.
    </p>
    <img src="cid:{asparagus_cid}" />
  </body>
</html>
""".format(asparagus_cid=asparagus_cid[1:-1]), subtype='html')
# HTML에서 사용하기 위해 msgid에서 <>를 벗겨야 했습니다.

# 이제 관련 이미지를 html 부분에 추가합니다.
with open("roasted-asparagus.jpg", 'rb') as img:
    msg.get_payload()[1].add_related(img.read(), 'image', 'jpeg',
                                     cid=asparagus_cid)

# 우리가 보낼 것의 지역 사본을 만듭니다.
with open('outgoing.msg', 'wb') as f:
    f.write(bytes(msg))

# 지역 SMTP 서버를 통해 메시지를 보냅니다.
with smtplib.SMTP('localhost') as s:
    s.send_message(msg)

마지막 예에서 메시지를 보냈다면, 다음은 그것을 처리하는 한 가지 방법입니다:

import os
import sys
import tempfile
import mimetypes
import webbrowser

# 필요한 email 모듈을 임포트 합니다
from email import policy
from email.parser import BytesParser

# 이 작업을 수행하고 안전하게 만드는 가상의 모듈.
from imaginary import magic_html_parser

# 실제 프로그램에서는 인자에서 파일명을 얻습니다.
with open('outgoing.msg', 'rb') as fp:
    msg = BytesParser(policy=policy.default).parse(fp)

# 이제 헤더 항목을 딕셔너리로 액세스할 수 있으며, 비 ASCII는 유니코드로 변환됩니다:
print('To:', msg['to'])
print('From:', msg['from'])
print('Subject:', msg['subject'])

# 메시지 내용의 미리 보기를 인쇄하려면, 가장 덜 포맷된 형식의 페이 로드를 추출하고 처음 세 줄을 
# 인쇄합니다. 물론, 메시지에 일반 텍스트 부분이 없을 때, html의 처음 세 줄을 인쇄하는 것은 쓸모가 
# 없지만, 이것은 단지 개념적인 예일 뿐입니다.
simplest = msg.get_body(preferencelist=('plain', 'html'))
print()
print(''.join(simplest.get_content().splitlines(keepends=True)[:3]))

ans = input("View full message?")
if ans.lower()[0] == 'n':
    sys.exit()

# 표시하기 위해 가장 풍부한 대안을 추출할 수 있습니다:
richest = msg.get_body()
partfiles = {}
if richest['content-type'].maintype == 'text':
    if richest['content-type'].subtype == 'plain':
        for line in richest.get_content().splitlines():
            print(line)
        sys.exit()
    elif richest['content-type'].subtype == 'html':
        body = richest
    else:
        print("Don't know how to display {}".format(richest.get_content_type()))
        sys.exit()
elif richest['content-type'].content_type == 'multipart/related':
    body = richest.get_body(preferencelist=('html'))
    for part in richest.iter_attachments():
        fn = part.get_filename()
        if fn:
            extension = os.path.splitext(part.get_filename())[1]
        else:
            extension = mimetypes.guess_extension(part.get_content_type())
        with tempfile.NamedTemporaryFile(suffix=extension, delete=False) as f:
            f.write(part.get_content())
            # cid의 전자 우편 형식에서 html 형식으로 가기 위해 <>을 다시 제거합니다.
            partfiles[part['content-id'][1:-1]] = f.name
else:
    print("Don't know how to display {}".format(richest.get_content_type()))
    sys.exit()
with tempfile.NamedTemporaryFile(mode='w', delete=False) as f:
    # magic_html_parser는 부분 파일의 파일명을 가리키도록 href="cid:...." 어트리뷰트를 다시 
    # 써야 합니다. 또한 HTML의 안전성 검사를 수행해야 합니다. html.parser를 사용하여 작성할 수 
    # 있습니다.
    f.write(magic_html_parser(body.get_content(), partfiles))
webbrowser.open(f.name)
os.remove(f.name)
for fn in partfiles.values():
    os.remove(fn)

# 물론, 이 간단한 프로그램을 망칠 수 있는 많은 전자 우편 메시지가 있지만, 가장 흔한 것을 처리합니다.

프롬프트까지, 위의 출력은 다음과 같습니다:

To: Penelope Pussycat <penelope@example.com>, Fabrette Pussycat <fabrette@example.com>
From: Pepé Le Pew <pepe@example.com>
Subject: Ayons asperges pour le déjeuner

Salut!

Cela ressemble à un excellent recipie[1] déjeuner.

각주

1

영감과 예를 주신 Matthew Dixon Cowles에게 감사드립니다.