Email via Python
1 MIME - Multipurpose Internet Mail Extensions
2 SMTP - Simple Message Transport Protocol
3
4 邮件的发送,
5 例子 - send email
6 import smtplib
7 from email.mime.text import MIMEText
8 from email.utils import formatdate, make_msgid
9 mess = 'hello there'
10 mesgobj = MIMEText(mess)
11 mesgobj['To'] = 'testrecevier@example.com'
12 mesgobj['From'] = 'testsender@example.com'
13 mesgobj['Subject'] = 'Greeting'
14 mesgobj['Date'] = formatdate(localtime=1) # Date header, formatdate() 生成 email 专门日期格式
15 mesgobj['Message-ID'] = make_msgid() # make_msgid() 方法生成唯一的 Message-ID,
16 print(mesgobj.as_string())
17 print(mesgobj.as_bytes())
18
19 s = smtplib.SMTP('SMTP-SERVER-ADDR')
20 s.send_message(mesgobj)
21 s.quit()
22
23 邮件的解析,
24 例子 - parsing email,
25 from email.parser import Parser
26 headers = Parser().parsestr(mesgobj.as_string()) # mesgobj 为上例中对象.
27 for h,v in headers.items():
28 print("Header[%s] - " % h,v)
29 print('Emial content - ', mesgobj.get_payload()) # 打印邮件正文.
30
31 带有附件的邮件发送处理 - Packing MIME,
32 邮件的附件有 音频, 图像, 压缩文件等等, 通过 python doc 的上的例子看一下儿
33 对邮件的附件的处理方式,
34 例子 - send email with attachments,
35 * an example of how to send the entire contents of a directory as an email message
36
37 import os
38 import sys
39 import smtplib
40 # For guessing MIME type based on file name extension
41 import mimetypes
42 from optparse import OptionParser
43 from email import encoders
44 from email.message import Message
45 from email.mime.audio import MIMEAudio
46 from email.mime.base import MIMEBase
47 from email.mime.image import MIMEImage
48 from email.mime.multipart import MIMEMultipart
49 from email.mime.text import MIMEText
50
51 COMMASPACE = ', '
52
53 def main():
54 parser = OptionParser(usage='''\
55 Send the contents of a directory as a MIME message.
56
57 Usage: %prog [options]
58
59 Unless the -o option is given, the email is sent by forwarding to your local
60 SMTP server, which then does the normal delivery process. Your local machine
61 must be running an SMTP server.
62 ''')
63 parser.add_option('-d', '--directory',
64 type='string', action='store',
65 help='''Mail the contents of the specified directory,
66 otherwise use the current directory. Only the regular
67 files in the directory are sent, and we don't recurse to
68 subdirectories.''')
69 parser.add_option('-o', '--output',
70 type='string', action='store', metavar='FILE',
71 help='''Print the composed message to FILE instead of
72 sending the message to the SMTP server.''')
73 parser.add_option('-s', '--sender',
74 type='string', action='store', metavar='SENDER',
75 help='The value of the From: header (required)')
76 parser.add_option('-r', '--recipient',
77 type='string', action='append', metavar='RECIPIENT',
78 default=[], dest='recipients',
79 help='A To: header value (at least one required)')
80 opts, args = parser.parse_args()
81 if not opts.sender or not opts.recipients:
82 parser.print_help() # SMTP server 只处理地址有效的 sender 跟 recipients
83 sys.exit(1)
84 directory = opts.directory
85 if not directory:
86 directory = '.' # 之前的代码的功能是通过 OptionParser 没款完成对命令行参数的解析
87 # Create the enclosing (outer) message
88 outer = MIMEMultipart() # 构建一个带附件的 email 对象
89 outer['Subject'] = 'Contents of directory %s' % os.path.abspath(directory)
90 outer['To'] = COMMASPACE.join(opts.recipients)
91 outer['From'] = opts.sender
92 outer.preamble = 'You will not see this in a MIME-aware mail reader.\n'
93
94 for filename in os.listdir(directory): # 按附件类型做相应的处理
95 path = os.path.join(directory, filename)
96 if not os.path.isfile(path):
97 continue
98 # Guess the content type based on the file's extension. Encoding
99 # will be ignored, although we should check for simple things like
100 # gzip'd or compressed files.
101 ctype, encoding = mimetypes.guess_type(path) # 附件类型的判断
102 if ctype is None or encoding is not None:
103 # No guess could be made, or the file is encoded (compressed), so
104 # use a generic bag-of-bits type.
105 ctype = 'application/octet-stream' # zip, pdf 等附件的类型
106 maintype, subtype = ctype.split('/', 1)
107 if maintype == 'text': # 处理的 文本text 类型的附件
108 fp = open(path)
109 # Note: we should handle calculating the charset
110 msg = MIMEText(fp.read(), _subtype=subtype)
111 fp.close()
112 elif maintype == 'image': # 处理的 图像image 类型的附件
113 fp = open(path, 'rb')
114 msg = MIMEImage(fp.read(), _subtype=subtype)
115 fp.close()
116 elif maintype == 'audio': # 处理的 音频audio 类型的附件
117 fp = open(path, 'rb')
118 msg = MIMEAudio(fp.read(), _subtype=subtype)
119 fp.close()
120 else:
121 fp = open(path, 'rb') # 处理的其他类型的附件:如 zip, pdf 等
122 msg = MIMEBase(maintype, subtype)
123 msg.set_payload(fp.read())
124 fp.close()
125 # Encode the payload using Base64
126 encoders.encode_base64(msg)
127 # Set the filename parameter
128 msg.add_header('Content-Disposition', 'attachment', filename=filename)
129 outer.attach(msg)
130 # Now send or store the message
131 composed = outer.as_string()
132 if opts.output: # 保存到文件 store
133 fp = open(opts.output, 'w')
134 fp.write(composed)
135 fp.close()
136 else:
137 s = smtplib.SMTP('localhost') # SMTP server 发送处理
138 s.sendmail(opts.sender, opts.recipients, composed)
139 s.quit()
140
141 if __name__ == '__main__':
142 main()
143
144 MIME 的解包 - Unpacking MIME
145
146 接收方对收到的 MIMEMultipart 的处理,
147 '''Unpack a MIME message into a directory of files.'''
148
149 import os
150 import sys
151 import email
152 import errno
153 import mimetypes
154 from optparse import OptionParser
155 def main():
156 parser = OptionParser(usage='''\
157 Unpack a MIME message into a directory of files.
158
159 Usage: %prog [options] msgfile
160 ''')
161 parser.add_option('-d', '--directory',
162 type='string', action='store',
163 help='''Unpack the MIME message into the named
164 directory, which will be created if it doesn't already
165 exist.''')
166 opts, args = parser.parse_args()
167 if not opts.directory:
168 parser.print_help()
169 sys.exit(1)
170
171 try:
172 msgfile = args[0]
173 except IndexError:
174 parser.print_help()
175 sys.exit(1)
176
177 try:
178 os.mkdir(opts.directory)
179 except OSError as e:
180 # Ignore directory exists error
181 if e.errno != errno.EEXIST:
182 raise
183
184 fp = open(msgfile)
185 msg = email.message_from_file(fp)
186 fp.close()
187
188 counter = 1
189 for part in msg.walk():
190 # multipart/* are just containers
191 if part.get_content_maintype() == 'multipart':
192 continue
193 # Applications should really sanitize the given filename so that an
194 # email message can't be used to overwrite important files
195 filename = part.get_filename()
196 if not filename:
197 ext = mimetypes.guess_extension(part.get_content_type())
198 if not ext:
199 # Use a generic bag-of-bits extension
200 ext = '.bin'
201 filename = 'part-%03d%s' % (counter, ext)
202 counter += 1
203 fp = open(os.path.join(opts.directory, filename), 'wb')
204 fp.write(part.get_payload(decode=True))
205 fp.close()
206
207 if __name__ == '__main__':
208 main()
209
210 Reference,
211 https://docs.python.org/3/library/email.html