使用Python smtplib 发送邮件

简介

SMTP是发送邮件的协议,Python内置对SMTP的支持,可以发送纯文本邮件、HTML邮件以及带附件的邮件。
Python对SMTP支持有smtplib和email两个模块,email负责构造邮件,smtplib负责发送邮件。

sample 1.发送文本邮件

构造MIMEText

1
2
3
4

from email.mime.text import MIMEText
msg = MIMEText('hello, send by Python...', 'plain', 'utf-8')

注意到构造MIMEText对象时,第一个参数就是邮件正文,第二个参数是MIME的subtype,传入’plain’表示纯文本,最终的MIME就是’text/plain’,最后一定要用utf-8编码保证多语言兼容性。

使用smtp发送

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

# 输入Email地址和口令:
from_addr = xxx@x.com
password = xxxxxxx
# 输入收件人地址:
to_addr = xxx@xx.com
# 输入SMTP服务器地址,以QQ企业邮箱为例
smtp_server = smtp.exmail.qq.com
def _format_addr(s):
name, addr = parseaddr(s)
return formataddr((Header(name, 'utf-8').encode(), addr))

msg = MIMEText('hello, send by Python...', 'plain', 'utf-8')
msg['From'] = _format_addr('Python爱好者 <%s>' % from_addr)
msg['To'] = _format_addr('管理员 <%s>' % to_addr)
msg['Cc'] = _format_addr('其他成员 <%s>' % to_addr)
msg['Subject'] = Header('来自SMTP的问候……', 'utf-8').encode()

server = smtplib.SMTP_SSL(smtp_server, 465) # SMTP SSL协议默认端口是465,注意使用SMTP_SSL
#server = smtplib.SMTP(smtp_server, 25) # 25端口的话注意使用SMTP
server.set_debuglevel(1)
try:
server.login(from_addr, password)
print('ok')
server.sendmail(from_addr, [to_addr], msg.as_string())
server.quit()
except Exception as e:
print(e)

  • 编写了一个函数_format_addr()来格式化一个邮件地址。注意不能简单地传入name <addr@example.com>,因为如果包含中文,需要通过Header对象进行编码。msg['To']接收的是字符串而不是list,如果有多个邮件地址,用,分隔即可。

  • set_debuglevel(1)就可以打印出和SMTP服务器交互的所有信息。SMTP协议就是简单的文本命令和响应。login()方法用来登录SMTP服务器,sendmail()方法就是发邮件,由于可以一次发给多个人,所以传入一个list,邮件正文是一个stras_string()MIMEText对象变成str

  • From:发件人

  • To:收件人

  • Cc:抄送人

  • Subject:主题

  • 此处必须注意smtplib.SMTP_SSLsmtplib.SMTP的区分,否则会报错提示连接失败。

这样我们就收到了一封测试邮件。

代码

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

#!usr/bin/env python
# -*- coding:utf-8 _*-
"""
@author:medivh
@file: ex_send_mail.py
@time: 2019/02/24
"""

import smtplib
from email.mime.text import MIMEText
from email import encoders
from email.header import Header
from email.mime.text import MIMEText
from email.utils import parseaddr, formataddr


def _format_addr(s):
name, addr = parseaddr(s)
return formataddr((Header(name, 'utf-8').encode(), addr))


# 输入Email地址和口令:
from_addr = xxx@x.com
password = xxxxxxx
# 输入收件人地址:
to_addr = xxx@xx.com
# 输入SMTP服务器地址,以QQ企业邮箱为例
smtp_server = smtp.exmail.qq.com

msg = MIMEText('hello, send by Python...', 'plain', 'utf-8')
msg['From'] = _format_addr('Python爱好者 <%s>' % from_addr)
msg['To'] = _format_addr('管理员 <%s>' % to_addr)
msg['Cc'] = _format_addr('其他成员 <%s>' % to_addr)
msg['Subject'] = Header('来自SMTP的问候……', 'utf-8').encode()

server = smtplib.SMTP_SSL(smtp_server, 465) # SMTP SSL协议默认端口是465,注意使用SMTP_SSL
#server = smtplib.SMTP(smtp_server, 25) # 25端口的话注意使用SMTP
server.set_debuglevel(1)
try:
server.login(from_addr, password)
print('ok')
server.sendmail(from_addr, [to_addr], msg.as_string())
server.quit()
except Exception as e:
print(e)

samele 2.附件邮件

如果Email中要加上附件怎么办?带附件的邮件可以看做包含若干部分的邮件:文本和各个附件本身,所以,可以构造一个MIMEMultipart对象代表邮件本身,然后往里面加上一个MIMEText作为邮件正文,再继续往里面加上表示附件的MIMEBase对象即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 邮件对象:
msg = MIMEMultipart()
msg['From'] = _format_addr('Python爱好者 <%s>' % from_addr)
msg['To'] = _format_addr('管理员 <%s>' % to_addr)
msg['Subject'] = Header('来自SMTP的问候……', 'utf-8').encode()
msg_text = '<html><body><h1>Hello</h1>' + '<p>send by <a href="http://www.econow.cn">Python</a>...</p>' + '</body></html>'
msg.attach(MIMEText(msg, 'html', 'utf-8'))

# 添加附件就是加上一个MIMEBase,从本地读取一个图片:
with open('./hi.png', 'rb') as f:
# 设置附件的MIME和文件名,这里是png类型:
FileApart = MIMEApplication(f.read())
FileApart.add_header('Content-Disposition', 'attachment', filename='hi.png')
msg.attach(FileApart)

sample 3.html 邮件

在构造MIMEText对象时,把HTML字符串传进去,再把第二个参数由plain变为html就可以了:

1
2
3
4

msg = MIMEText('<html><body><h1>Hello</h1>' +
'<p>send by <a href="http://www.econow.cn">Python</a>...</p>' +
'</body></html>', 'html', 'utf-8')
### samele 3.图片邮件 要把图片嵌入到邮件正文中,我们只需按照发送附件的方式,先把邮件作为附件添加进去,然后,在HTML中通过引用src="cid:0"就可以把附件作为图片嵌入了。如果有多个图片,给它们依次编号,然后引用不同的cid:x即可。 事实上,无论图片还是音频文件,使用方式都是一样的。
1
2
3
4
5
6
7
8
msgText = '<html><body><h1>Hello</h1>' + '<p><img src="cid:0"></p>' + '<p>send by <a href="http://www.econow.cn">Python</a>...</p>' + '</body></html>'

# 添加附件就是加上一个MIMEImage,从本地读取一个图片:
with open('./hi.png', 'rb') as f:
# 设置附件的MIME和文件名,这里是png类型:
FileApart = MIMEImage(f.read())
FileApart.add_header('Content-ID', '<0>')
msg.attach(FileApart)

全部代码如下:

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
#!usr/bin/env python
# -*- coding:utf-8 _*-
"""
@author:medivh
@file: ex_send_mail.py
@time: 2019/02/24
"""

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
from email.mime.image import MIMEImage
from email.header import Header
from email.utils import parseaddr, formataddr


def _format_addr(s):
name, addr = parseaddr(s)
return formataddr((Header(name, 'utf-8').encode(), addr))


# 输入Email地址和口令:
from_addr = xxx@x.com
password = xxxx
# 输入收件人地址:
to_addr = xx@xx.com
# 输入SMTP服务器地址,以QQ企业邮箱为例
smtp_server = 'smtp.exmail.qq.com'

msg = MIMEMultipart()
msg['From'] = _format_addr('Python爱好者 <%s>' % from_addr)
msg['To'] = _format_addr('管理员 <%s>' % to_addr)
msg['Subject'] = Header('来自SMTP的问候……', 'utf-8').encode()
msgText = '<html><body><h1>Hello</h1>' + '<p><img src="cid:0"></p>' + '<p>send by <a href="http://www.econow.cn">Python</a>...</p>' + '</body></html>'
msg.attach(MIMEText(msgText, 'html', 'utf-8'))
# 添加附件就是加上一个MIMEImage,从本地读取一个图片:
with open('./hi.png', 'rb') as f:
# 设置附件的MIME和文件名,这里是png类型:
FileApart = MIMEImage(f.read())
FileApart.add_header('Content-ID', '<0>')
msg.attach(FileApart)

server = smtplib.SMTP_SSL(smtp_server, 465) # SMTP SSL协议默认端口是465,注意使用SMTP_SSL
server.set_debuglevel(3)
try:
server.login(from_addr, password)
server.sendmail(from_addr, [to_addr], msg.as_string())
server.quit()
except Exception as e:
print(e)

MIME 类型

通用类型

1
2
3
4
5
6
7
8
9
10
11
12
class email.mime.application.MIMEApplication(_data, _subtype='octet-stream', _encoder=email.encoders.encode_base64, *, policy=compat32, **_params)
Module: email.mime.application

A subclass of MIMENonMultipart, the MIMEApplication class is used to represent MIME message objects of major type application. _data is a string containing the raw byte data. Optional _subtype specifies the MIME subtype and defaults to octet-stream.

Optional _encoder is a callable (i.e. function) which will perform the actual encoding of the data for transport. This callable takes one argument, which is the MIMEApplication instance. It should use get_payload() and set_payload() to change the payload to encoded form. It should also add any Content-Transfer-Encoding or other headers to the message object as necessary. The default encoding is base64. See the email.encoders module for a list of the built-in encoders.

Optional policy argument defaults to compat32.

_params are passed straight through to the base class constructor.

Changed in version 3.6: Added policy keyword-only parameter.

音频类型

1
2
3
4
5
6
7
8
9
10
11
12
class email.mime.audio.MIMEAudio(_audiodata, _subtype=None, _encoder=email.encoders.encode_base64, *, policy=compat32, **_params)¶
Module: email.mime.audio

A subclass of MIMENonMultipart, the MIMEAudio class is used to create MIME message objects of major type audio. _audiodata is a string containing the raw audio data. If this data can be decoded by the standard Python module sndhdr, then the subtype will be automatically included in the Content-Type header. Otherwise you can explicitly specify the audio subtype via the _subtype argument. If the minor type could not be guessed and _subtype was not given, then TypeError is raised.

Optional _encoder is a callable (i.e. function) which will perform the actual encoding of the audio data for transport. This callable takes one argument, which is the MIMEAudio instance. It should use get_payload() and set_payload() to change the payload to encoded form. It should also add any Content-Transfer-Encoding or other headers to the message object as necessary. The default encoding is base64. See the email.encoders module for a list of the built-in encoders.

Optional policy argument defaults to compat32.

_params are passed straight through to the base class constructor.

Changed in version 3.6: Added policy keyword-only parameter.

图片类型

1
2
3
4
5
6
7
8
9
10
11
12
class email.mime.image.MIMEImage(_imagedata, _subtype=None, _encoder=email.encoders.encode_base64, *, policy=compat32, **_params)
Module: email.mime.image

A subclass of MIMENonMultipart, the MIMEImage class is used to create MIME message objects of major type image. _imagedata is a string containing the raw image data. If this data can be decoded by the standard Python module imghdr, then the subtype will be automatically included in the Content-Type header. Otherwise you can explicitly specify the image subtype via the _subtype argument. If the minor type could not be guessed and _subtype was not given, then TypeError is raised.

Optional _encoder is a callable (i.e. function) which will perform the actual encoding of the image data for transport. This callable takes one argument, which is the MIMEImage instance. It should use get_payload() and set_payload() to change the payload to encoded form. It should also add any Content-Transfer-Encoding or other headers to the message object as necessary. The default encoding is base64. See the email.encoders module for a list of the built-in encoders.

Optional policy argument defaults to compat32.

_params are passed straight through to the MIMEBase constructor.

Changed in version 3.6: Added policy keyword-only parameter.

综上所述,如果想不起来啥类型就使用MIMEApplication

参考资料:https://docs.python.org/3/library/email.mime.html