layout: post
title: 电子邮件系列(二)
subtitle: SMTP发送邮件
date: 2018-03-03
author: Simon
header-img: img/post-bg-git-learn.jpg
catalog: true
tags:
- MIME
- 电子邮件
- SMTP
邮件发送与接收的流程
SMTP是发送邮件的协议,是MUA(Mail User Agent)发送到MTA(Mail Transfer Agent)和MTA之间传输的协议。
编写程序来发送和接收邮件,本质上就是以下这样的过程:
发件人->MUA->MTA->若干MTA->MDA->收件人
其中,你是新浪的用户,就会先发到新浪的MTA上,经过若干MTA,会到达收件人的邮件服务商的MTA上,这个MTA会把邮件投递到最终目的地MDA(Mail Delivery Agent)邮件投递代理,Email到达MDA后,会静静地躺在邮件服务商的服务器上,等待用户开机联网通过MUA从MDA把邮件取到本地。
几种邮件类型
纯文本邮件/HTML文本邮件
发送纯文本邮件:
先构造邮件消息(用到email库):
构造一个最简单的纯文本邮件:
from email.mime.text import MIMEText
msg = MIMEText('send by simon...', 'plain', 'utf-8')
构造MIMEText
对象时,第一个参数就是邮件正文,第二个是子格式,plain
表示传入的是纯文本,另一个值是html
。
charset
值为utf-8
,保证多语言兼容性。此值也将被添加到Content-Type头中去。
此外,若charset
值设定,则Content-Transfer-Encoding
会被设定为对应值,如utf-8
对应base64
。关于Content-Transfer-Encoding
内容在上一篇电子邮件系列(一)中已经详细介绍过。
当我们print
这个msg
会发现其信件内容(不含header)已经是base64
格式了。所以,当指定了charset
后,会指定Content-Type-Encoding
,信件内容相应的由字符编码为bytes,其本质是二进制,之后再将二进制转化为base64
。
接下来,发送邮件(用到smtplib库):
import smtplib
from_addr = '1005121394@qq.com'
to_addr = 'simonkindle@126.com'
password = 'zotggqxvhhktbfig'
smtp_server = 'smtp.qq.com'
server_port = 465
server = smtplib.SMTP_SSL(smtp_server, server_port)
server.set_debuglevel(1) #打印出所有和SMTP服务器交互的信息
server.login(from_addr, password)
server.sendmail(from_addr, [to_addr], msg.as_string())
server.quit()
对于QQ邮箱来说,其要求SMTP发送邮件时必须使用SSL加密,所以,使用SSL加密端口465
,正常情况下不要求SSL加密的使用25
端口,同时创建smtp
对象时使用的是smtp.SMTP
函数。
sendmail
的第二个参数to_addrs
接收的是一个list
,可以传入多个收件人地址。第三个是邮件内容,as_astring
函数会将格式化的message对象(包含MIMEHeader以及内容)全部转化为str
,这样才能在网络上传输(当然,网络上传输的是这些str
的字节流bytes
格式,这一过程未在代码中体现,个人猜测应该在sendmail
中完成。)
发送HTML格式邮件:
只需要在构建message时将传入的文本格式改为html
,并在传入html
格式的文本即可。例如:
msg = MIMEText('<html><body><h1>Hello</h1>' +
'<p>send by <a href="http://www.python.org">Python</a>...</p>' +
'</body></html>', 'html', 'utf-8')
以上邮件我们还没有为其添加邮件头,也就是主题,发件人,收件人,这会造成一些问题,比如会提示不在收件人中,或收件人无
[图片上传失败...(image-df844d-1520222326027)]
[图片上传失败...(image-f7aa6e-1520222326027)]
尤其是,当你用网易邮箱发邮件时,会提示错误,原因就是你的sendmail
函数中的发收件人与邮件头中的不匹配。
添加邮件头:
def _format_addr(s):
name, addr = parseaddr(s)
return formataddr((Header(name, 'utf-8').encode(), addr))
from_addr = input('From: ')
password = input('Password: ')
to_addr = input('To: ')
smtp_server = input('SMTP server: ')
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['Subject'] = Header('来自SMTP的问候……', 'utf-8').encode()
我们定义了一个_format_addr
格式化邮件地址,不能直接传入name <addr@example.com>
,因为name如果包含中文,需要对name进行编码。
但是,subject
经测试是可以直接传入中文字符的。
同时支持HTML和plain格式的文本邮件
如果我们发送HTML邮件,收件人通过浏览器或者Outlook之类的软件是可以正常浏览邮件内容的,但是,如果收件人使用的设备太古老,查看不了HTML邮件怎么办?
可以在发送HTML文本的同时再附加一个纯文本,如果收件人的设备不支持查看HTML格式邮件,可以显示纯文本(两个只能显示一个)
我们可以创建一个MIMEMultipart
类型对象,subtype
子类型是'alternative',在上一篇文章中我们已经讲过,这代表纯文本和HTML
文本混合的内容。
创建之后再向其中添加两个MIMTText
对象即可。
代码如下:
msg = MIMEMultipart('alternative')
msg['From'] = ...
msg['To'] = ...
msg['Subject'] = ...
msg.attach(MIMEText('hello', 'plain', 'utf-8'))
msg.attach(MIMEText('<html><body><h1>Hello</h1></body></html>', 'html', 'utf-8'))
发送附件
如何发送带附件比如像图片或mp3这种二进制文件的邮件呢?
同样,我们可以先构造一个MIMEMultipart
对象,向其中添加MIMEText
对象作为邮件正文,再向其中添加MIMEBase
作为附件即可。
msg = MIMEMultipart()
msg.attach(MIMEText('你好', 'plain', 'utf-8'))
# 添加附件就是添加一个MIMEBase对象,注意文件要以二进制形式打开。
with open('F://my pictures//beauty//222.JPG', 'rb') as f:
mime = MIMEBase('image', 'jpeg', filename='222.JPG')
# 添加文件头,此filename会显示在邮件附件中
mime.add_header('Content-Disposition', 'attachment', filename='222.JPG')
# 定义图片ID,以便在文本中引用
mime.add_header('Content-ID', '<0>')
# 读入附件内容
mime.set_payload(f.read())
# Encode the message's payload in Base64. 并添加Content-Transfer-Encoding头信息
encoders.encode_base64(mime)
msg.attach(mime)
将图片嵌入邮件正文中
如何把图片附件嵌入邮件正文中(邮件服务商一般不允许使用HTML图片外链,防止有害信息):
要把图片嵌入到邮件正文中,我们只需按照发送附件的方式,先把邮件作为附件添加进去,然后,在HTML
中通过引用src="cid:0"
就可以把附件作为图片嵌入了。如果有多个图片,给它们依次编号,然后引用不同的cid:x
即可。
msg.attach(MIMEText('<html><body><h1>Hello</h1>' +
'<p><img src="cid:0"></p>' +
'</body></html>', 'html', 'utf-8'))
再次发送,即可在正文中看到图片了。
加密邮件
使用标准的25端口连接SMTP服务器时,使用的是明文传输,发送邮件的整个过程可能会被窃听。要更安全地发送邮件,可以加密SMTP会话,实际上就是先创建SSL安全连接,然后再使用SMTP协议发送邮件
比如腾讯qq邮箱要求SMTP
服务必须要SSL加密。端口号是465
。
我们上文所用的smtp.SMTP_SSL(server_addr, server_port)
就是加密方式
非加密方式使用的函数是smtp.SMTP(server_addr, 25)
。
总结
对于非ASCII编码的纯文本,由输入的字符到网络传输这一过程中的编码变化为:
str->bytes->base64->str->bytes
其中,前三步由MIMEText
函数完成,base64->str
由as_string
函数完成。最后一步不在代码中体现。
对于二进制文件来说:
binary code->base64->str->bytes
其中,binary code
由open
函数以rb
模式读入,二进制转到base64
由encoders.encode_base64
函数完成,之后与纯文本相同,由as_string
完成message
格式到str
的转化。
网友评论