python要实现发送邮件的功能,需要使用smtplib库。
1. 过程大致如下:
1. 建立和SMTP邮件服务器的连接
# 默认端口25 smtp = smtplib.SMTP(host, port) # 或者 smtp = smtplib.SMTP() smtp.connect(host, port) # 带SSL,默认端口465 smtp = smtp.SMTP_SSL() smtp.connect(host, port)
2. 完成身份认证
# 对于163而言,第三方客户端登陆使用的是授权码而不是密码 smtp.login(username, password)
3. 发送邮件(必须明确发件人、收件人、主题、内容,而抄送人、密送人、附件、内嵌式图片等资源是可选的)
smtp.sendmail(from_addr, to_addrs, msg, mail_options=[], rcpt_options=[])
4. 结束会话
smtp.quit()
2. MIME相关知识介绍
MIME全称Multipurpose Internet Mail Extensions(多用途互联网邮件扩展)。是一个互联网标准,它扩展了电子邮件标准,使其能够支持:
- 非ASCII字符文本;
- 非文本格式附件(二进制、声音、图像等);
- 由多部分(multiple parts)组成的消息体;
- 包含非ASCII字符的头信息(Header information)
此外,web中使用的HTTP协议也使用了MIMIE的框架,标准被扩展为互联网媒体类型。
MIME头部信息:
MIME版本: 目前版本1.0
MIME-Version: 1.0
内容类型(Content-Type),用于指定消息类型。 形式如下:
Content-Type: [type]/[subtype]; parameter
type有以下形式:
- Text: 文本消息, 可以使用charset参数指定字符集;
- Multipart: 用于连接消息体的多个部分构成一个消息,这些部分可以是不同类型的数据;
- Application:用于传输应用程序数据或者二进制数据;
- Message:用于包装一个E-mail消息;
- Image:用于传输静态图片数据;
- Audio:用于传输音频数据;
- Video:用于传输动态影像数据,可以是与音频编辑在一起的视频数据格式
常见的文件扩展名与Content-Type对应关系如下:
文件扩展名 | Content-Type |
---|---|
.txt | text/plain |
.html | text/html |
.xhtml | application/xhtml+xml |
.css | text/css |
.js | application/javascript |
.xml | application/atom+xml |
.json | application/json |
.jpg | image/jpeg |
.png | image/png |
.gif | image/gif |
.mp3 | audio/mpeg3 |
.mp4 | video/mp4 |
.wav | audio/wav |
.avi | video/avi |
.doc | application/msword |
.docx | application/vnd.openxmlformats-officedocument.wordprocessingml.document |
.xls | application/vnd.ms-excel |
.xlsx | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet |
.ppt | application/vnd.ms-powerpoint |
.pptx | application/vnd.openxmlformats-officedocument.presentationml.presentation |
application/pdf | |
.rar | application/x-rar-compressed |
.zip | application/x-compressed |
.tar | application/x-tar |
.gz | application/x-compressed |
.bz2 | application/x-bzip2 |
表示任意二进制数据 | application/octet-stream |
使用HTTP的POST方法提交表单 | application/x-www-form-urlencoded |
主要用于表单提交时伴随文件上传的场合 | multipart/form-data |
Text默认是text/plain,Application默认是application/octet-stream而Multipart默认情况下被看作multipart/mixed。
内容传输编码(Content-Transfer-Encoding)
Content-Transfer-Encoding: [mechanism]
其中,mechanism的值可以指定为“7bit”,“8bit”,“binary”,“quoted-printable”,“base64”
MIME信息剖析
一封普通的文本邮件的信息包含一个头部分(例如:From、To、Subject 等等)和一个体部分。体部分通常为单体类型(例如:text、image、audio、video、application 等等)或是复合类型(即:multipart)。头部分和体部分之间用一个空行进行分隔,并且体部分的类型由信头内容类型字段 Content-Type 描述。
信头含义 (Headers)
域名 | 含义 |
---|---|
Content-Type | 内容的类型 |
MIME-Version | MIME 版本 |
Content-Transfer-Encoding | 内容的传输编码方式 |
From | 发件人地址 |
To | 收件人地址 |
Cc | 抄送地址 |
Bcc | 暗送地址 |
Date | 日期和时间 |
Subject | 主题 |
Received | 传输路径 |
Return-Path | 回复地址 |
Delivered-To | 发送地址 |
Reply-To | 回复地址 |
Message-ID | 消息 ID |
信体部分
-
邮件中常见的简单类型有 text/plain(纯文本)和 text/html(超文本)。
-
复杂的邮件内容格式采用 multipart 类型,可以包括纯文本/超文本(alternative)、内嵌资源(图片)(related)、附件类型(mixed)等等。
multipart 类型的邮件体被分为多个段,每个段又包含段头和段体两部分,这两部分之间也以空行分隔。
信体头含义
域名 | 含义 |
---|---|
Content-Type | 段体的类型 |
Content-Transfer-Encoding | 段体的传输编码方式 |
Content-Disposition | 段体的位置,内嵌(inline)或附件(attachment) |
Content-ID | 段体的 ID, 唯一标识 |
Content-Location | 段体的位置(路径) |
Content-Base | 段体的基位置 |
常见的 multipart 类型有三种:multipart/mixed, multipart/related 和 multipart/alternative。
复合类型层次关系示例图:
multipart 诸类型的共同特征是,在段头指定 boundary 参数字符串,段体内的每个子段以此字符串定界。
所有的子段都以 --boundary 行开始,父段则以 --boundary-- 行结束。段与段之间也以空行分隔。
例子:
1) 发送纯文本文件:
Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: base64 aGVsbG8gd29ybGQK5L2g5aW95LiW55WM
2)发送包含富文本的邮件:
Content-Type: multipart/alternative; boundary="----=ALIBOUNDARY_14343_4f941940_5a9a0b10_90106" ------=ALIBOUNDARY_14343_4f941940_5a9a0b10_90106 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: base64 aGVsbG8gd29ybGTkvaDlpb3kuJbnlYw= ------=ALIBOUNDARY_14343_4f941940_5a9a0b10_90106 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: base64 PGRpdiBjbGFzcz0iX19hbGl5dW5fZW1haWxfYm9keV9ibG9jayI+PGRpdiAgc3R5bGU9ImNsZWFy OmJvdGg7Ij48c3BhbiAgc3R5bGU9ImZvbnQtZmFtaWx5OlRhaG9tYSxBcmlhbCxTVEhlaXRpLFNp bVN1bjtmb250LXNpemU6MTQuMHB4O2NvbG9yOiMwMDAwMDA7Ij5oZWxsbyB3b3JsZDwvc3Bhbj48 L2Rpdj48ZGl2ICBzdHlsZT0iY2xlYXI6Ym90aDsiPjxzcGFuICBzdHlsZT0iZm9udC1mYW1pbHk6 VGFob21hLEFyaWFsLFNUSGVpdGksU2ltU3VuO2NvbG9yOiMwMDAwMDA7YmFja2dyb3VuZC1jb2xv cjojZmYwMDAwO2ZvbnQtc2l6ZTozMi4wcHg7Ij7kvaDlpb3kuJbnlYw8L3NwYW4+PC9kaXY+PC9k aXY+ ------=ALIBOUNDARY_14343_4f941940_5a9a0b10_90106--
”aGVsbG8gd29ybGTkvaDlpb3kuJbnlYw=" base64前明文:
hello world你好世界
"
PGRpdiBjbGFzcz0iX19hbGl5dW5fZW1haWxfYm9keV9ibG9jayI+PGRpdiAgc3R5bGU9ImNsZWFy OmJvdGg7Ij48c3BhbiAgc3R5bGU9ImZvbnQtZmFtaWx5OlRhaG9tYSxBcmlhbCxTVEhlaXRpLFNp bVN1bjtmb250LXNpemU6MTQuMHB4O2NvbG9yOiMwMDAwMDA7Ij5oZWxsbyB3b3JsZDwvc3Bhbj48 L2Rpdj48ZGl2ICBzdHlsZT0iY2xlYXI6Ym90aDsiPjxzcGFuICBzdHlsZT0iZm9udC1mYW1pbHk6 VGFob21hLEFyaWFsLFNUSGVpdGksU2ltU3VuO2NvbG9yOiMwMDAwMDA7YmFja2dyb3VuZC1jb2xv cjojZmYwMDAwO2ZvbnQtc2l6ZTozMi4wcHg7Ij7kvaDlpb3kuJbnlYw8L3NwYW4+PC9kaXY+PC9k aXY+" 明文:
<div class="__aliyun_email_body_block"><div style="clear:both;"><span style="font-family:Tahoma,Arial,STHeiti,SimSun;font-size:14.0px;color:#000000;">hello world</span></div>
<div style="clear:both;"><span style="font-family:Tahoma,Arial,STHeiti,SimSun;color:#000000;background-color:#ff0000;font-size:32.0px;">你好世界</span></div></div>
3) 将图片内嵌:
Content-Type: multipart/related; boundary="----=ALIBOUNDARY_25467_4f851940_5a9a032e_8ff9d" ------=ALIBOUNDARY_25467_4f851940_5a9a032e_8ff9d Content-Type: multipart/alternative; boundary="----=ALIBOUNDARY_25467_4f851940_5a9a032e_8ff9e" ------=ALIBOUNDARY_25467_4f851940_5a9a032e_8ff9e Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: base64 6L+Z5pivcHl0aG9u55qEbG9nb8KgCg== ------=ALIBOUNDARY_25467_4f851940_5a9a032e_8ff9e Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: base64 PGRpdiBjbGFzcz0iX19hbGl5dW5fZW1haWxfYm9keV9ibG9jayI+PGRpdiAgc3R5bGU9ImNsZWFy OmJvdGg7Ij48c3BhbiAgc3R5bGU9ImZvbnQtZmFtaWx5OlRhaG9tYSxBcmlhbCxTVEhlaXRpLFNp bVN1bjtmb250LXNpemU6MTQuMHB4O2NvbG9yOiMwMDAwMDA7Ij7ov5nmmK9weXRob27nmoRsb2dv PC9zcGFuPjwvZGl2PjxkaXYgIHN0eWxlPSJjbGVhcjpib3RoOyI+PHNwYW4gIHN0eWxlPSJmb250 LWZhbWlseTpUYWhvbWEsQXJpYWwsU1RIZWl0aSxTaW1TdW47Zm9udC1zaXplOjE0LjBweDtjb2xv cjojMDAwMDAwOyI+PGltZyAgc3JjPSJjaWQ6X19hbGl5dW4xNTIwMDQyNzk4MjEzOTg3MjEiPiZu YnNwOzxiciA+PC9zcGFuPjwvZGl2PjwvZGl2Pg== ------=ALIBOUNDARY_25467_4f851940_5a9a032e_8ff9e-- ------=ALIBOUNDARY_25467_4f851940_5a9a032e_8ff9d Content-Type: application/octet-stream Content-ID: <__aliyun152004279821398721> Content-Disposition: inline; filename="=?UTF-8?B?cHl0aG9uLWxvZ28ucG5n?=" Content-Transfer-Encoding: base64 下面多行是图片二进制数据base64编码后的数据 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ------=ALIBOUNDARY_25467_4f851940_5a9a032e_8ff9d--
"6L+Z5pivcHl0aG9u55qEbG9nb8KgCg==" 是经过base64编码后的数据,明文为:
这是python的logo
"
PGRpdiBjbGFzcz0iX19hbGl5dW5fZW1haWxfYm9keV9ibG9jayI+PGRpdiAgc3R5bGU9ImNsZWFy
OmJvdGg7Ij48c3BhbiAgc3R5bGU9ImZvbnQtZmFtaWx5OlRhaG9tYSxBcmlhbCxTVEhlaXRpLFNp
bVN1bjtmb250LXNpemU6MTQuMHB4O2NvbG9yOiMwMDAwMDA7Ij7ov5nmmK9weXRob27nmoRsb2dv
PC9zcGFuPjwvZGl2PjxkaXYgIHN0eWxlPSJjbGVhcjpib3RoOyI+PHNwYW4gIHN0eWxlPSJmb250
LWZhbWlseTpUYWhvbWEsQXJpYWwsU1RIZWl0aSxTaW1TdW47Zm9udC1zaXplOjE0LjBweDtjb2xv
cjojMDAwMDAwOyI+PGltZyAgc3JjPSJjaWQ6X19hbGl5dW4xNTIwMDQyNzk4MjEzOTg3MjEiPiZu
YnNwOzxiciA+PC9zcGFuPjwvZGl2PjwvZGl2Pg==" 明文为:
<div class="__aliyun_email_body_block"><div style="clear:both;"><span style="font-family:Tahoma,Arial,STHeiti,SimSun;font-size:14.0px;color:#000000;">
这是python的logo</span></div><div style="clear:both;"><span style="font-family:Tahoma,Arial,STHeiti,SimSun;font-size:14.0px;color:#000000;">
<img src="cid:__aliyun152004279821398721"> <br ></span></div></div>
4) 发送带附件的邮件
Content-Type: multipart/mixed; boundary="----=ALIBOUNDARY_68661_4cbc5940_5a9a0f2a_90709" This is a multi-part message in MIME format. ------=ALIBOUNDARY_68661_4cbc5940_5a9a0f2a_90709 Content-Type: multipart/alternative; boundary="----=ALIBOUNDARY_68661_4cbc5940_5a9a0f2a_9070a" ------=ALIBOUNDARY_68661_4cbc5940_5a9a0f2a_9070a Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: base64 5Y+R6YCB6ZmE5Lu25rWL6K+V ------=ALIBOUNDARY_68661_4cbc5940_5a9a0f2a_9070a Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: base64 PGRpdiBjbGFzcz0iX19hbGl5dW5fZW1haWxfYm9keV9ibG9jayI+PGRpdiAgc3R5bGU9ImNsZWFy OmJvdGg7Ij48c3BhbiAgc3R5bGU9ImZvbnQtZmFtaWx5OlRhaG9tYSxBcmlhbCxTVEhlaXRpLFNp bVN1bjtmb250LXNpemU6MTQuMHB4O2NvbG9yOiMwMDAwMDA7Ij7lj5HpgIHpmYTku7Y8c3BhbiAg c3R5bGU9ImNvbG9yOiMwMDAwMDA7Zm9udC1mYW1pbHk6VGFob21hLEFyaWFsLFNUSGVpdGksU2lt U3VuO2ZvbnQtc2l6ZToxNC4wcHg7Zm9udC1zdHlsZTpub3JtYWw7Zm9udC12YXJpYW50LWxpZ2F0 dXJlczpub3JtYWw7Zm9udC12YXJpYW50LWNhcHM6bm9ybWFsO2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0 LWFsaWduOnN0YXJ0O3RleHQtaW5kZW50Oi4wcHg7dGV4dC10cmFuc2Zvcm06bm9uZTt3aWRvd3M6 MjtiYWNrZ3JvdW5kLWNvbG9yOiNmZmZmZmY7dGV4dC1kZWNvcmF0aW9uLXN0eWxlOmluaXRpYWw7 dGV4dC1kZWNvcmF0aW9uLWNvbG9yOmluaXRpYWw7ZmxvYXQ6bm9uZTtkaXNwbGF5OmlubGluZTsi Pua1i+ivlTwvc3Bhbj48L3NwYW4+PC9kaXY+PC9kaXY+ ------=ALIBOUNDARY_68661_4cbc5940_5a9a0f2a_9070a-- ------=ALIBOUNDARY_68661_4cbc5940_5a9a0f2a_90709 Content-Type: application/octet-stream Content-Disposition: attachment; filename="=?UTF-8?B?5L2g5aW9d29ybGQudHh0?=" Content-Transfer-Encoding: base64 aGVsbG8gd29ybGQNCsTjusPKwL3n ------=ALIBOUNDARY_68661_4cbc5940_5a9a0f2a_90709--
multipart/alternative:
在同时提供多种消息格式,这些消息格式间是相互可替换的,如提供同一消息的多语言版本。
multipart/related:
添加内嵌资源时必须指定的Content-Type类型,通常配合Content-ID使用。
Content-ID头字段用于为“multipart/related” 组合消息中的内嵌资源指定一个唯一的标识符。在html格式的正文中使用这个唯一标识号来引用该内嵌资源。格式如下:
<img src="cid:CONTENTID" /> # CONTENTID用具体的数值替换
multipart/mixed:
添加附件时必须指定的Content-Type类型, 配合Content-Disposition来获取上文文件的名称。
Content-Disposition: attachment; filename=”上传文件的文件名" # 一般中文文件名都需要base64编码 # 以python_logo.png为例,base64编码后变为: cHl0aG9uX2xvZ28ucG5n # 最后结果为: =?UTF-8?B?cHl0aG9uLWxvZ28ucG5n?=
3. python中的MIME
参考: email.mime
MIMENonMultipart 作为中间类,主要用途是防止调用attach()方法
MIMEMultipart 与Conent-Type中的multipart对应,默认类型为multipart/mixed
MIMEApplication与Content-Type中的application对应,默认类型为application/octed-stream,默认编码类型为base64
MIMEText与Content-Type中的text对应,默认类型为text/plain。
身份认证发生错误:
smtplib.SMTPAuthenticationError: (535, 'Error: authentication failed')
原因分析:网易163邮箱需要启用授权码,并且使用授权码登录,而不是密码登录
1. 发送纯文本
import smtplib from email.mime.text import MIMEText from email.header import Header host = "smtp.aliyun.com" username = "u1@aliyun.com" # 对于163邮箱使用授权码而不是密码登录 password = "xxxxxxxxxxxx" # 接收人列表 receivers = ['q1@qq.com'] # 抄送人列表 cc_list = ['q2@qq.com'] # 密送人列表 bcc_list = ['q3@qq.com'] # 主题 subject = "阿里邮箱给qq邮箱发信" # 正文 body = "Python 发送邮件测试" sender = username # 邮件正文 message = MIMEText(body, 'plain', 'utf-8') # 发件人 message['From'] = sender # 收件人 message['To'] = ",".join(receivers) # 多个接收人之间要用逗号隔开 # 抄送人 message['Cc'] = ','.join(cc_list) # 密送人 message['Bcc'] = ','.join(bcc_list) # 主题 message['Subject'] = Header(subject, "utf-8") try: smtp = smtplib.SMTP() smtp.set_debuglevel(1) # 开启调试,方便观察和邮件服务器通信全过程 smtp.connect(host) # 默认端口是25 # 登陆 smtp.login(username, password) # 发送邮件 # 邮件服务器在转发邮件的过程中,省略了Bcc项 # To: Cc: Bcc: 只是一种表现形式,归根结底都是邮件的接收者,都需要服务器发送RCPT TO:来告知邮件服务器发送给谁。 # 因此接收者列表应该是收件人、抄送人、密送人的集合。 smtp.sendmail(sender, receivers + cc_list + bcc_list, message.as_string()) smtp.quit() except smtplib.SMTPException as e: print("error: ", e)
案例: 将磁盘使用情况以邮件的形式发送
# coding=utf-8 import smtplib import subprocess def send_email(smtp_server, sender, passwd, receiver_list, subject, content): receiver = receiver_list if isinstance(receiver_list, list): receiver = ",".join(receiver_list) # 多个收件人之间用,隔开 # 注意主题和正文中有一个空行 msg = '\r\n'.join([ 'From: %s' % sender, 'To: %s' % receiver, 'Subject: %s' % subject, '', content ]) try: s = smtplib.SMTP() # 连接smtp服务器 s.connect(smtp_server) # 登陆 对于163邮箱,使用授权码而不是密码登陆第三方邮件客户端 s.login(sender, passwd) # 发送邮件 s.sendmail(sender, receiver_list, msg) print 'success' s.quit() except Exception as e: print e # 发送邮件测试 def send_email_test(): smtp_server = 'smtp.163.com' sender = 'sender@163.com' # 发件人 password = 'authorized_code' # 163邮箱的登陆授权码 # 收件人 receiver = ['qq1@qq.com', 'qq2@qq.com'] # 邮件主题 subject = 'Disk Usage Report' p = subprocess.Popen('df -h', shell=True, stdout=subprocess.PIPE) content = p.stdout.read() # 获取磁盘使用情况 send_email(smtp_server, sender, password, receiver, subject, content)
2. 发送富文本邮件
host = "smtp.aliyun.com" username = "u1@aliyun.com" password = “password" receivers = ['q1@qq.com'] subject = "python发送html" sender = username # 类型为alternative message = MIMEMultipart("alternative") message['From'] = sender message['To'] = ",".join(receivers) # 多个接收人之间要用逗号隔开 message['Subject'] = Header(subject, "utf-8") # 正文有两部分 part1 = MIMEText("<h1>使用python发送邮件</h1>", "html", "utf-8") part2 = MIMEText('<a href="http://www.python.org">python官网</a>', "html", "utf-8") message.attach(part1) message.attach(part2)
3. 发送带附件的邮件
import os import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from email.header import Header from email.mime.application import MIMEApplication def add_attchment(message, file): """添加附件 :param message: MIMEMultipart对象 :param file: file为文件的路径 :return: None """ filename = os.path.basename(file) # 获取文件名 with open(file, "rb") as f: attachment = MIMEApplication(f.read()) attachment.add_header( "Content-Disposition", "attachment", filename=( "gbk", # 指定编码格式, qq邮箱对邮件使用gbk编码,否则可能会出现文件名乱码 "", filename)) message.attach(attachment) host = "smtp.aliyun.com" username = "u1@aliyun.com" password = “password" receivers = ['q1@qq.com'] subject = "python发送附件" sender = username # 发送附件,Content-Type必须为multipart/mixed message = MIMEMultipart("mixed") message['From'] = sender message['To'] = ",".join(receivers) # 多个接收人之间要用逗号隔开 message['Subject'] = Header(subject, "utf-8") # 添加正文 body = MIMEText("测试发送附件", "html", "utf-8") message.attach(body) # 添加附件 add_attchment(message, "百度logo.jpg") add_attchment(message, "测试.txt") add_attchment(message, "测试.xlsx")
4. 发送带内嵌图片的邮件
def get_base64name(file): """ :param file: 文件路径 :return: 返回base64编码的文件名 """ filename = os.path.basename(file) base64name = base64.b64encode(filename.encode('utf-8')) filename = (str(base64name))[2:-1] return filename def add_nested_picture(message, msgalternative, file): """ 向邮件中嵌入图片 """ # 将base64编码的文件名作为cid cid = get_base64name(file) link = MIMEText('<img src="cid:%s" />' % cid, "html", "utf-8") msgalternative.attach(link) with open(file, "rb") as f: nested_picture = MIMEApplication(f.read()) nested_picture.add_header("Content-ID", cid) # 下面这一行可以省略 nested_picture.add_header("Content-Disposition", "inline", filename=("gbk", "", os.path.basename(file))) message.attach(nested_picture) sender = username
# 指定类型为multipart/related message = MIMEMultipart("related") message['From'] = sender message['To'] = ",".join(receivers) # 多个接收人之间要用逗号隔开 message['Subject'] = Header(subject, "utf-8") msgalternative = MIMEMultipart("alternative") message.attach(msgalternative) # 添加正文 body = MIMEText("测试发送含有内嵌图片的邮件", "html", "utf-8") msgalternative.attach(body) add_nested_picture(message, msgalternative, "C:/users/hupeng/pictures/lena.jpg")
层次结果:
里层: 类型为multipart/alternative的MIMEMultipart对象attach 装载html文本的MIMEText对象。
外层: 类型为multipart/related的MIMEMultipart对象 attach 表示内嵌资源的MIMEApplication对象和表示文本信息类型为mulitpart/alternative的MIMEMultipart对象。
当然也可以直接使用类型为multipart/mixed类型的对象attach 表示html文本的MIME对象和表示内嵌资源的MIMEApplication对象
代码如下:
def get_base64name(file): """ :param file: 文件路径 :return: 返回base64编码的文件名 """ filename = os.path.basename(file) base64name = base64.b64encode(filename.encode('utf-8')) filename = (str(base64name))[2:-1] return filename def add_nested_picture(message, file): """ 向邮件中嵌入图片 """ # 将base64编码的文件名作为cid cid = get_base64name(file) link = MIMEText('<img src="cid:%s" />' % cid, "html", "utf-8") message.attach(link) with open(file, "rb") as f: nested_picture = MIMEApplication(f.read()) nested_picture.add_header("Content-ID", cid) message.attach(nested_picture) sender = username # 指定最外层信息段类型为multipart/mixed message = MIMEMultipart("mixed") message['From'] = sender message['To'] = ",".join(receivers) # 多个接收人之间要用逗号隔开 message['Subject'] = Header(subject, "utf-8") # 添加正文 body = MIMEText("测试发送含有内嵌图片的邮件", "html", "utf-8") message.attach(body) # 添加嵌入式图片 add_nested_picture(message, "百度logo.jpg")
备注: 邮件客户端(web端)包含查看邮件原文的功能,使用该功能可以很好的帮助学习。