본문 바로가기
Back-end/Python

[SMTP] Python으로 메일 발송 하기. (With. 첨부파일 )

by 허도치 2020. 2. 22.

2020/02/20 - [Back-end/JAVA] - [SMTP] JAVA로 메일 발송 하기. (With. 첨부파일 )

 

0. 서론

 지난 포스트에서는 Java로 메일을 발송하는 예제를 다루어보았다. 이번에는 Python을 가지고 만들어 보았는데, Java로 한번 만들어봐서 그런지 좀 더 만들기 수월했다. 특히, Java는 라이브러리를 다운받아서 사용해야되지만 Python은 내장 패키지를 사용하므로 다운받을 필요가 없었다.

 

 

 

1. 프로젝트 준비
1-1. Gmail 인증을 위한 앱 비밀번호 발급

[ 구글 계정 ] > [ 보안 ] > [ Google에 로그인 ]

1) 구글 계정으로 접속 후 로그인.
   : https://myaccount.google.com/

2) [ 보안 ] > [ Google에 로그인 ] > [ 2단계 인증 ]을 클릭 후 등록.
   : https://myaccount.google.com/signinoptions/two-step-verification

3) [ 보안 ] > [ Google에 로그인 ] > [ 앱 비밀번호 ]을 클릭.
   : https://myaccount.google.com/apppasswords

4) 앱 비밀번호를 생성할 앱 및 기기를 선택.
   : [ 앱 선택 ]은 '메일'을 선택.
   : [ 기기 선택 ]은 아무거나 선택.

5) 생성 클릭
   : 발급된 패스워드가 팝업에 패스워드가 발급되며
   : 발급 받은 패스워드는 따로 저장.

 

 

 

2. 텍스트(HTML) 메일 발송
2-1. 스크립트 작성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# send_plain.py
import os
import smtplib
 
from email.utils import formataddr
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
 
from_addr = formataddr(('Google Dochi''dnjsakf@google.com'))
to_addr = formataddr(('Naver Dochi''dnjsakf@naver.com'))
 
session = None
try:
    # SMTP 세션 생성
    session = smtplib.SMTP('smtp.gmail.com'587)
    session.set_debuglevel(True)
    
    # SMTP 계정 인증 설정
    session.ehlo()
    session.starttls()
    session.login('dnjsakf@gmail.com''구글 앱 비밀번호')
 
    # 메일 콘텐츠 설정
    message = MIMEMultipart("alternative")
    
    # 메일 송/수신 옵션 설정
    message.set_charset('utf-8')
    message['From'= from_addr
    message['To'= to_addr
    message['Subject'= '안녕하세요'
 
    # 메일 콘텐츠 - 내용
    body = '''
    <h2>안녕하세요.</h1>
    <h4>허도치입니다.</h1>
    '''
    bodyPart = MIMEText(body, 'html''utf-8')
    message.attach( bodyPart )
 
    # 메일 발송
    session.sendmail(from_addr, to_addr, message.as_string())            
 
    print'Successfully sent the mail!!!' )
except Exception as e:
    print( e )
finally:
    if session is not None:
        session.quit()
cs

 

2-2. 실행

$ python send_plain.py

 

2-3. 실행 결과

[ 메일 발송 로그 ]

- session.set_debuglevel를 True로 설정했기 때문에, 상세 로그가 출력되면서 정상적으로 메일이 발송됨.

 

[ 메일 수신 확인 ]

- 메일 수신함을 확인해보면 방금 전송한 메일을 확인할 수 있음.

 

 

 

3. 첨부파일이 포함된 메일 발송
3-1. 스크립트 작성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# send_attachment.py
import os
import smtplib
 
from email import encoders
from email.utils import formataddr
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
 
from_addr = formataddr(('Google Dochi''dnjsakf@google.com'))
to_addr = formataddr(('Naver Dochi''dnjsakf@naver.com'))
 
session = None
try:
    # SMTP 세션 생성
    session = smtplib.SMTP('smtp.gmail.com'587)
    session.set_debuglevel(True)
    
    # SMTP 계정 인증 설정
    session.ehlo()
    session.starttls()
    session.login('dnjsakf@gmail.com''구글 앱 비밀번호')
 
    # 메일 콘텐츠 설정
    message = MIMEMultipart("mixed")
    
    # 메일 송/수신 옵션 설정
    message.set_charset('utf-8')
    message['From'= from_addr
    message['To'= to_addr
    message['Subject'= '안녕하세요'
 
    # 메일 콘텐츠 - 내용
    body = '''
    <h2>안녕하세요.</h1>
    <h4>허도치입니다.</h1>
    '''
    bodyPart = MIMEText(body, 'html''utf-8')
    message.attach( bodyPart )
 
    # 메일 콘텐츠 - 첨부파일
    attachments = [
        os.path.join( os.getcwd(), 'storage''example.py' )
    ]
 
    for attachment in attachments:
        attach_binary = MIMEBase("application""octect-stream")
        try:
            binary = open(attachment, "rb").read() # read file to bytes
 
            attach_binary.set_payload( binary )
            encoders.encode_base64( attach_binary ) # Content-Transfer-Encoding: base64
            
            filename = os.path.basename( attachment )
            attach_binary.add_header("Content-Disposition"'attachment', filename=('utf-8''', filename))
            
            message.attach( attach_binary )
        except Exception as e:
            print( e )
 
    # 메일 발송
    session.sendmail(from_addr, to_addr, message.as_string())        
 
    print'Successfully sent the mail!!!' )
except Exception as e:
    print( e )
finally:
    if session is not None:
        session.quit()
cs

 

3-2. 실행

$ python send_attachment.py

 

3-3. 실행 결과

[ 메일 발송 로그 ]

- 첨부파일이 추가되면서 좀 더 복잡한 로그가 출력됨.

[ 메일 수신 확인 ]

- 메일 수신함을 확인해보면 방금 전송한 메일에 첨부파일도 추가된 것을 확인할 수 있음.

 

 

 

4. Class로 모듈화 하기
4-1. 스크립트 작성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#sendmail.py
import os
import smtplib
 
from email import encoders
from email.utils import formataddr
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
 
 
class DochiMail(object):
    ENCODING = 'UTF-8'
 
    host = None
    port = None
    from_addr = None
    to_addrs = None
    message = None
    
    debug = False
    _isLogin = False
    _existAttachments = False
 
    def __init__(self, address:tuple, from_addr:str, to_addrs:list, debug=False, **kwargs):
        self.host, self.port = address
        self.from_addr = from_addr
        self.to_addrs = to_addrs
        
        # 인증 계정
        if kwargs.get("username"is not None:
            self.setAuth(
                kwargs.get("username")
                , kwargs.get("password")
            )
        
        # 디버그 모드
        self.debug = debug is not False
    
        # 첨부파일로 전송 불가능한 확장자
        self.blocked_extensions = (
            ".ade"".adp"".apk"".appx"".appxbundle"".bat"
            , ".cab"".chm"".cmd"".com"".cpl"".dll"".dmg"
            , ".exe"".hta"".ins"".isp"".iso"".jar"".js"
            , ".jse"".lib"".lnk"".mde"".msc"".msi"".msix"
            , ".msixbundle"".msp"".mst"".nsh"".pif"".ps1"
            , ".scr"".sct"".shb"".sys"".vb"".vbe"".vbs"
            , ".vxd"".wsc"".wsf"".wsh"
        )
 
    def setAuth(self, username, password):
        self._isLogin = True
        self.username = username
        self.password = password
 
    def setMail(self, subject, body, body_type='plain', attachments=None):
        '''
        Content-Type:
            - multipart/alternative: 평문 텍스트와 HTML과 같이 다른 포맷을 함께 보낸 메시지 
            - multipart/mixed: 첨부파일이 포함된 메시지
 
        REF:
            - https://ko.wikipedia.org/wiki/MIME#Content-Type
        '''
        # 첨부파일 여부
        self._existAttachments = attachments is not None
        self.content_sub_type = "mixed" if self._existAttachments else "alternative"
 
        # 메일 콘텐츠 설정
        self.message = MIMEMultipart( self.content_sub_type )
 
        # 받는 사람 주소 설정. [( 이름 <이메일> )]
        self.FROM_ADDR_FMT = formataddr( self.from_addr )
        self.TO_ADDRS_FMT = ",".join([ formataddr( addr ) for addr in self.to_addrs ])
        
        # 메일 헤더 설정
        self.message.set_charset( self.ENCODING )
        self.message['From'= self.FROM_ADDR_FMT
        self.message['To'= self.TO_ADDRS_FMT
        self.message['Subject'= subject
        
        # 메일 콘텐츠 - 내용
        self.message.attach(MIMEText( body, body_type, self.ENCODING ))
 
        # 메일 콘텐츠 - 첨부파일
        if self._existAttachments:
            self._attach_files( attachments )
 
        return self
    
    def _attach_files(self, attachments):
        '''
        Content-disposition:
            - 파일명 지정
        MIME type:
            - application/octect-stream: Binary Data
        REF:
            - https://www.freeformatter.com/mime-types-list.html
        '''
 
        for attachment in attachments:
            attach_binary = MIMEBase("application""octect-stream")
            try:
                binary = open(attachment, "rb").read() # read file to bytes
 
                attach_binary.set_payload( binary )
                encoders.encode_base64( attach_binary ) # Content-Transfer-Encoding: base64
                
                filename = os.path.basename( attachment )
                attach_binary.add_header("Content-Disposition"'attachment', filename=(self.ENCODING, '', filename))
                
                self.message.attach( attach_binary )
            except Exception as e:
                print( e )
 
    def send(self):
        session = None
        try:
            # 메일 세션 생성
            session = smtplib.SMTP( self.host, self.port )
            session.set_debuglevel( self.debug )
            session.ehlo()
 
            # SMTP 서버 계정 인증
            if self._isLogin:
                session.starttls()
                session.login(self.username, self.password)
            
            # 메일 발송
            session.sendmail(self.FROM_ADDR_FMT, self.TO_ADDRS_FMT, self.message.as_string())
            print'Successfully sent the mail!!!' )
            
        except Exception as e:
            print( e )
        finally:
            if session is not None:
                session.quit()
 
 
 
# SMTP 서버 정보
smtp_address = ( 'smtp.gmail.com'587 )        # SMTP 서버 호스트, 포트
 
# SMTP 계정 정보
username = "dnjsakf@gmail.com";                 # Gmail 계정
password = "구글 앱 비밀번호";                   # Gmail 앱 비밀번호
 
# 메일 송/수신자 정보
from_addr = ('Google Dochi''dnjsakf@gmail.com')  # 보내는 사람 주소. (이름, 이메일)
to_addrs = [                                     # 받는 사람 주소. (이름, 이메일)
    ('Naver Dochi''dnjsakf@naver.com')
]
 
# 메일 제목
subject = '안녕하세요'
 
# 메일 내용
body = '''
<h2>안녕하세요.</h1>
<h4>허도치입니다.</h1>
'''
 
# 첨부파일
attachments = [
    os.path.join( os.getcwd(), 'storage''example.py' )
]
 
if __name__ == '__main__':
    mail = DochiMail(smtp_address, from_addr, to_addrs, debug=False)
    
    mail.setAuth(username, password)
    mail.setMail( subject , body, 'html', attachments=attachments )
    mail.send()
cs

 

4-2. 실행

$ python sendmail.py

 

4-3. 실행 결과

[ 메일 발송 로그 ]
[ 메일 수신 확인 ]

- 위에서 만들었던 예제들과 똑같이 잘 동작함.
- 굳이 Class로 안만들어도 되지만 나중에 써먹을데가 있을것 같아서 만들어보았음.

 

 

 

마치며

- Java에 이어서 Python으로도 메일을 발송하는 기능을 구현해보았는데, 확실히 Python으로 만드는게 더 편하고 빠른것 같다.

댓글