SMTP发送邮件


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 = '[email protected]'
to_addr = '[email protected]'
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('

Hello

' + '

send by Python...

' + '', '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 ,因为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('

Hello

', '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('

Hello

' + '

' + '', '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->stras_string函数完成。最后一步不在代码中体现。

对于二进制文件来说:

binary code->base64->str->bytes

其中,binary codeopen函数以rb模式读入,二进制转到base64encoders.encode_base64函数完成,之后与纯文本相同,由as_string完成message格式到str的转化。

你可能感兴趣的:(SMTP发送邮件)