python电子邮件系列(二)之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 = 'your password'
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')

以上邮件我们还没有为其添加邮件头,也就是主题,发件人,收件人,这会造成一些问题,比如会提示不在收件人中,或收件人无

尤其是,当你用网易邮箱发邮件时,会提示错误,原因就是你的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->stras_string函数完成。最后一步不在代码中体现。

对于二进制文件来说:

binary code->base64->str->bytes

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,193评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,306评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,130评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,110评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,118评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,085评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,007评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,844评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,283评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,508评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,395评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,985评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,630评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,797评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,653评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,553评论 2 352

推荐阅读更多精彩内容