需求:
    Python 发邮件:
    1、传递字符串
    2、传递正文图片
    3、传递正文html
    4、传递附件

    0、SMTP协议及邮件体系架构
    SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议。规定了一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式。和 www 一样,SMTP是互联网提供的基础服务之一。SMTP的规则是独立于底层传输系统的。它只要求传输系统有序且可靠,并不是只能工作在TCP上。

    SMTP体系架构:
    smtp架构.png
    MUA:Mail User Agent,邮件用户代理,用来编写、收发邮件
    MTA:Mail Transfer Agent,邮件传输代理,将邮件传输到正确的目的地
    MDA:Mail Deliver Agent,邮件分发代理,将邮件分发到正确目的用户
    邮件系统的整体架构设计模仿了现实世界中传递一封信件的过程。即发送人先把邮件投到信箱里,再由邮差将信件投递到目的信箱里,然后客户端去邮箱里取邮件。这里的信箱就是SMTP服务器。SMTP 服务器在中转邮件时是MTA,在向用户终端分发邮件时是MDA。MUA和MTA之间,以及源、目MTA之间运行SMTP协议。MDA和MUA之间运行POP3协议。MUA和MTA之间是以C/S 架构运行SMTP协议,在互联网上基于TCP协议运行,服务器端口25。邮件服务器URL类似于 smtp.163.com,从形式上可以看出SMTP和www类似,是基于互联网提供的一种服务。

    如A用户使用的是QQ邮箱,B用户使用的是163邮箱,A要向B发送一封邮件。流程如下:
    1、A用户通过MUA编写好邮件,并发送。发送时指定发送者MTA,用SMTP协议发送至发送者MTA。
    2、该邮件通过SMTP,发送到发送者MTA:QQ邮件服务器。
    3、QQ邮件服务器发现目的邮箱地址是163,再通过SMTP传递到接收者MTA:163邮件服务器。
    4、163邮件服务器收到该邮件。
    5、由于使用163邮箱的用户有很多,再通过MDA把该邮件发送到正确的用户(用POP3协议)
    6、B用户通过MUA就可以查看A用户发送的邮件。

    既然smtp提供和www类似的服务,那smtp也应该提供类似http协议里的那些方法(get ,post,put等)。事实上smtp确实有,叫做smtp命令。可以直接登陆smtp服务器,只用smtp命令发送邮件,而不需要MUA。
    直接登陆smtp服务器发送邮件,是一种典型的客户端/服务器模式。客户端使用smtp命令与服务器进行交互式通信传输报文。即客户端发出一个命令,服务器返回一个应答。客户端与服务器一问一答的交互,由客户端控制这个对话。这里的客户端可以是一个简单的维持socket连接的程序,比如 Telnet,nc 等。(额外提一句,HTTP其实也可以用类似的方式连接上服务器,发送标准的HTTP命令,获取数据。)

    一、了解SMTP命令,以及如何用原始命令发送邮件。
    常用的SMTP命令(smtp命令对大小写不敏感):
    helo XXX (向smtp服务器打招呼,XXX可以是任意字符串。每个命令输入后服务器都会返回状态响应码)
    auth login (申请登陆,获得提示后先输入账号,再输入密码。注意账号密码都是经过base64加密过的)
    mail from:zhouyy_1987@163.com (指定邮件发送源)
    rcpt to:yiyong.zhou@msxf.com (指定邮件接收目的)
    data (开始输入邮件主体。邮件主体包含邮件头和邮件正文。邮件头有固定的格式。邮件正文是任意字符串,最后以一个 单行的 . 符号结尾,回车后邮件即发出去。)
    下面看一个实际命令行登陆smtp服务器,通过smtp命令发送邮件的例子。

    先在Python里用base64 加密邮箱账号和密码,用于登陆邮箱。
    >>> base64.encodestring(‘zhouyy_1987@163.com’.encode()) (用base64加密账号)
    b’emhvdXl5XzE5ODdAMTYzLmNvbQ==\n’ (base64密文是’’中间的字符串,不包括\n)
    >>>
    >>> base64.encodestring(‘XXXXXXXX’.encode()) (用base64加密密码)
    b’YYYYYYYY\n’

    然后在Linux下用 nc直接登录邮件服务器,用SMTP的原始命令发送邮件。注意登陆时smtp只识别base64加密后的账号和密码。
    [root@bogon ~]# nc smtp.163.com 25
    220 163.com Anti-spam GT for Coremail System (163com[20141201])
    helo test (测试服务可用性)
    250 OK
    auth login (申请登录SMTP服务器)
    334 dXNlcm5hbWU6
    emhvdXl5XzE5ODdAMTYzLmNvbQ== (先输入账号的base64密文)
    334 UGFzc3dvcmQ6
    YYYYYYYY (再输入密码的base64密文)
    235 Authentication successful
    mail from:zhouyy_1987@163.com (指定邮件发送者)
    250 Mail OK
    rcpt to:yiyong.zhou@msxf.com (指定邮件接收者)
    250 Mail OK
    data (编写邮件主体,包括邮件头部消息和 正文消息,头部消息有固定的格式,和正文消息之间必须有一个空行隔开)
    354 End data with .
    To:yiyong.zhou@msxf.com (邮件头部消息1,邮件接收者)
    From:zhouyy_1987@163.com (邮件头部消息2,邮件发送者)
    Subject:test1 mail (邮件头部消息3,邮件主题)
    (邮件头部和邮件主题之间必须有个空行,邮件内容里不显示出来)
    test mail ody (邮件正文,任意长度字符串,可换行,最后用一个单行的 . 符号结束邮件正文。正文结束后邮件即发送出去。)
    hello mail~
    (后面的空行会在邮件内容里显示出来)
    .(用 点 符号告诉服务器邮件内容结束)
    250 Mail OK queued as smtp1,GdxpCgBnlilkPFdeCdIjBw—.576S2 1582775642
    quit (退出邮箱登录)
    221 Bye (退出登录提示)
    ^C (输入 CTRL +C 结束连接)
    [root@bogon ~]#

    总结:
    这里演示了如何用nc 命令直接socket登陆smtp服务器,用smtp命令和smtp服务器交互,发送邮件。
    整个过程是客户端/服务器模式。维持socket连接的nc 进程就是客户端,smtp服务器开放相应端口连接。
    真个过程是交互式地进行的。输入一个命令或数据(账号密码,邮件主体内容等),服务器收到后会返回状态码和其他提示信息。
    还要注意,邮件的主体是有特定格式的字符串。包含邮件头部和邮件正文两个部分。有格式要求。

    二、用Python 交互式完成发邮件。主要是观察smtplib这个模块对smtp协议和命令是怎么封装的。可以看出smtplib对smtp原始命令进行了简单的封装,但对邮件主体的封装不是很完善。这里要求邮件主体必须符合特定的格式。后面会看到,email模块就提供了较良好的邮件主体封装。

    import smtplib
    s = smtplib.SMTP()
    s.connect(‘smtp.163.com’, 25)
    s.login(‘zhouyy_1987@163.com’, ‘密码明文’)
    msg = ‘’’To:yiyong.zhou@msxf.com (这里的msg就是邮件主体,包含消息头和正文,必须符合特定的格式)
    From:zhouyy_1987@163.com
    Subject:test mail 2
    (这里按规定必须有一个空行,隔开邮件主体头部和正文)
    hello,mail!
    ‘’’ (结束多行字符串,即结束邮件主体编辑)
    s.sendmail(‘zhouyy_1987@163.com’, ‘yiyong.zhou@msxf.com’, msg) (调用sendmail方法直接发送邮件)
    s.quit()

    总结:
    smtplib模块对smtp协议和发送邮件的过程进行了封装,主要有以下几个类和方法:

    smtplib.SMTP()类。构造了一个smtp服务器对象。使用格式如下:
    SMTP(host=’’, port=0, local_hostname=None, timeout=, source_address=None)
    可以看到主要的实例化参数是 host 和port 。都有默认参数。host是一个字符串,表示smtp服务器URL,port表示服务器开放端口。可以在实例化时输入参数,直接完成连接服务器socket操作。也可以后面用connect()函数指定smtp服务器和端口连接。(注意如果smtp服务器开启了ssl 的话,要用 smtplib.SMTP_SSL() 类构造smtp服务器对象。)

    connect() 函数: 连接smtp服务器。相当于建立socket连接。主要是两个参数:host和port。分别表示smtp服务器的URL和开放端口。执行该函数后即完成和服务器之间的socket连接。
    connect(host=’localhost’, port=0, sourceaddress=None) method of smtplib.SMTP instance
    Connect to a host on a given port.
    If the hostname ends with a colon (`:’) followed by a number, and
    there is no port specified, that suffix will be stripped off and the
    number interpreted as the port number to use.
    Note: This method is automatically invoked by _init
    , if a host is
    specified during instantiation.

    login() 函数: 登陆smtp服务器,认证并获取权限。参数为邮箱账号明文字符串和登录密码明文字符串。相当于 原始命令:auth login 。

    sendmail() 函数: 发送邮件。三个必须参数分别是:发送源邮箱,发送目的邮箱,发送邮件主体(需要特定格式的主体)。相当于封装了:mail from ;rcpt to ;data 三个原始命令。
    sendmail(from_addr, to_addrs, msg, mail_options=[], rcpt_options=[]) method of smtplib.
    注意 msg 必须是有特定格式字符串,包括邮件头部消息和邮件正文。就好像现实中邮寄一封信,信件上必须有发件人,收件人,这里再加上主题。还要注意,msg这里是一个字符串。即sendmail() 发送邮件时,邮件的内容必须是一个字符串。后面会看到,如果用email构造邮件主体对象,在用sendmail() 函数发送这个邮件主体对象时必须调用as_string()函数,把邮件主体对象转化为字符串。

    quit() 函数: 退出邮箱登陆。同时断开socket连接。相当于封装了:quit 命令加 Ctrl + C操作。

    三、用Python 脚本发纯文本格式的邮件。主要是熟悉smtplib和email两个模块。

    1. #-*-coding:GBK -*-
    2. import smtplib
    3. from email.mime.text import MIMEText
    4. #注意第一行。在Windows里需要用 GBK编码格式,在Linux里需要用utf-8编码格式。。。
    5. #指定邮件中转服务器:MTA
    6. mail_host = 'smtp.163.com'
    7. #发件人
    8. mail_sender = 'zhouyy_1987@163.com'
    9. #邮箱登陆密码或验证码
    10. mail_pass = 'XXXXXXXX'
    11. mail_recevier = 'yiyong.zhou@msxf.com'
    12. mail_subject = '测试邮件主题'
    13. mail_content = '你好!请看邮件。' #邮件内容,文本形式的邮件正文。
    14. msg = MIMEText(mail_content, 'plain', 'utf-8') #构造邮件主体对象。构造的是纯文本格式的对象。
    15. msg["From"] = mail_sender #给这个对象添加属性,这个操作类似于字典。
    16. msg["To"] = mail_recevier #实际上是给这个对象增加一行消息头内容
    17. msg["Subject"] = mail_subject #直接将该对象打印出来可以看到
    18. mail_server = smtplib.SMTP(mail_host, 25)
    19. #创建一个邮件服务器对象,该对象初始化可以指定邮件服务器和邮件端口。也可以不指定,后面再用connect()方法
    20. #连接指定服务器和端口。
    21. mail_server.login(mail_sender, mail_pass) #登陆邮件服务器
    22. mail_server.sendmail(mail_sender, mail_recevier, msg.as_string()) #发送邮件
    23. mail_server.quit() #退出邮件服务器登陆

    总结:
    smtplib 主要用于控制smtp服务器登陆,认证和邮件收发过程。email 模块主要用于构造邮件主体内容。邮件主体内容包含邮件头部和邮件正文,还有可能包括附件。这些都可以用email模块里的类来构造。

    MIMEText() 类是构造邮件主体对象的类。其init函数主要接收3个参数:邮件正文,邮件正文的mime格式,邮件正文字符集。下面是其 init 函数的注释信息:
    init(self, _text, _subtype=’plain’, _charset=None, , policy=None)
    | Create a text/
    type MIME document.
    _text is the string for this message object.
    _subtype is the MIME sub content type, defaulting to “plain”.
    _charset is the character set parameter added to the Content-Type
    | header. This defaults to “us-ascii”. Note that as a side-effect, the
    | Content-Transfer-Encoding header will also be set.

    可以看出,_text 是邮件正文的字符串,是必须参数。 _subtype 是邮件内容的mime格式,默认参数是 ‘plain’ ,即明文字符串。还可以选择 ‘html’,即指定接收方MUA用HTML规则解析邮件正文内容。_charset 是邮件内容的字符集,接收方需要用相同的字符集去解析邮件正文内容。默认是None,即用标志ASCII字符集解析。有中文内容时还可以选择 utf-8 。
    对MIMEText() 类构造的对象。可以通过类似字典增加键值对的方式增加属性。msg[“From”] = XXX 。这样的属性dir() 看不到,但直接打印出来 :print(msg) ,可以看到 msg 头部增加了一行字符: “From:XXX” 。这就是在构造邮件主体的头部消息。一个邮件必须有三个头部消息:From,To,Subject 。不然会出错,或被识别为垃圾邮件。

    MIMEText() 类构造的邮件主体对象,在用sendmail()函数发送时必须转换为字符串。(前面提过,sendmail()智只能发字符串),故msg 还支持 as_string()方法。在邮件发送时需要调用。

    如果想发送HTML,可以把mail_content直接指定为HTML字符串。然后用MIMEText() 类构造邮件对象时选择_subtype参数为 ‘html’即可。这样接收方MUA就会用HTML规则去解析邮件内容。这也是mime协议的目的。

    关于mime协议:
    原始电子邮件只能发送纯文本信息。而后来想对电子邮件发送内容进行扩展,发送更多的种类的信息。这样的信息发出去之后,接收方就需要一个标签来明白收到的信息该如何处理。是该用ASCII码直接解析为纯文本文件呢,还是该用HTML解析呢。这就需要一个消息标签,用来标注信息的格式,告诉消息接收方该用何种方式解析消息。这个消息标签就是mime标签。这种思路后来也扩展到了http协议。这里的消息接收方就是接收电子邮件的 MUA 或接收http消息的网页浏览器。把msg打印出来可以看见其mime标签格式,类似 text/html、image/png 这种格式的数据:
    print(msg)
    Content-Type: text/html; charset=”utf-8”
    MIME-Version: 1.0
    Content-Transfer-Encoding: base64
    / 前面的text,image标签是由构造对象的类决定的。MIMEText()类就是构造了一个text类的邮件主体,MIMEImage()就是创建一个image类的邮件主体。/ 后面的子类在对象实例化时用 _subtype 参数指定 。实例化时还可以指定字符集和转码方法。这样接收方就会先用base64解码消息,然后用html去解析文本,用utf-8字符集显示。

    text模块里MIMEText()类专门用于构建邮件主体。其常用的mime类型是:text/plain 、text/html和octet-stream。分别用于构建纯文本格式的邮件,HTML格式的邮件和邮件附件。

    注意引入类MIMEText() 的过程。 这里的 email, mime 都是包(packet),text是模块(mudule),不能直接引入包然后调用,只能一级一级地引入模块,然后调用里面的类和函数。且包不能 dir()看内容,只能用 help() 看包里有啥内容。

    msg[“To”] = mail_recevier 是在指定邮件发送目的。mail_recevier可以是一个邮箱地址的字符串,也可以是多个邮箱地址,中间用 ; (分号)组成的长字符串。即向多个目的邮箱发送邮件。常用多个邮箱的地址字符串组成列表,然后用 ;.jion() ,这样构造多目的邮箱字符串。

    四、用Python 脚本发正文里带图片的邮件。主要是熟悉 email.mime.image 模块里 MIMEImage() 类。

    1. #-*-coding:utf-8 -*-
    2. import smtplib
    3. from email.mime.image import MIMEImage
    4. mail_host = 'smtp.163.com'
    5. mail_sender = 'zhouyy_1987@163.com'
    6. mail_pass = 'XXXXXXXX'
    7. mail_recevier = 'yiyong.zhou@msxf.com'
    8. mail_subject = '测试邮件主题'
    9. fp = open('./1.png', 'rb')
    10. msg = MIMEImage(fp.read(), 'png')
    11. fp.close()
    12. msg["From"] = mail_sender
    13. msg["To"] = mail_recevier
    14. msg["Subject"] = mail_subject
    15. mail_server = smtplib.SMTP(mail_host, 25)
    16. mail_server.login(mail_sender, mail_pass)
    17. mail_server.sendmail(mail_sender, mail_recevier, msg.as_string())
    18. mail_server.quit()

    总结:
    IMEImage()类构造了一个图片类型的邮件主体对象。mime类型是 image/ ,/ 后面的子类型在对象实例化时用subtype参数指定。这个邮件主体对象和MIMEText()对象一样,可以加上邮件头部信息后,用asstring()函数字符串化,然后用sendmail() 函数发出。这个类的 init函数注释如下:
    __init
    (self, _imagedata, _subtype=None, _encoder=,
    , policy=None, *_params)
    | Create an image/
    type MIME document.
    _imagedata is a string containing the raw image data. If this data
    | can be decoded by the standard Python `imghdr’ module, then the
    | subtype will be automatically included in the Content-Type header.
    | Otherwise, you can specify the specific image subtype via the _subtype
    | parameter.

    1. 直接用图片读出来的信息,用IMEImage()类构造邮件主体,在上面添加邮件头部信息然后发送。注意msg仍然要调用as_string() 方法转换为字符串。 这里仍然有问题,构造的时候指定了mime类型为 'image/png' 。但邮箱接收到时邮件正文里仍然不会显示图片,只会把图片当做一个附件。这个问题后面再研究下。
    2. 研究结果:email包里有mime包。mime包里有三个模块最常用。分别是text image multipart 。里面的类和函数分别用于构建邮件主体,邮件图片附件和多类型的邮件主体。就是说image 里的MIMEImage()只用于构建图片附件,故不能在邮件正文中显示图片。虽然MIMEImage()只能构建图片附件对象,但也能把这个对象当成邮件主体对象对待。即给这个对象添加FromToSubject等属性,用sendmail()函数发出去。只是接收方只能收到邮件附件,邮件正文内容是空的。要在邮件正文里显示图片,必须使用multipart 模块里的MIMEMultipart()类构造一个多类型的邮件主体对象。看下面的例子。

    五、用Python 脚本发带图片的HTML邮件。主要熟悉email.mime.multipart 模块里的 MIMEMultipart()类,和如何在HTML文件里添加图片。

    #-*-coding:utf-8 -*-
    import smtplib
    from email.mime.text import MIMEText
    from email.mime.image import MIMEImage
    from email.mime.multipart import MIMEMultipart
    
    mail_host = 'smtp.163.com'
    mail_sender = 'zhouyy_1987@163.com'
    mail_pass = 'XXXXXXX' 
    mail_recevier = 'zhouyy_1987@163.com'
    mail_subject = '测试邮件主题'
    
    mail_content = '''<p>你好,邮件发送测试...</p >
    <p>这是使用python登录163邮箱发送HTML格式和图片的测试邮件:</p >
    <p>图片演示:</p >
    <p>
    <br>< img src="cid:image1"></br >
    <br>< img src="cid:image2"></br >
    </P>
    '''
    
    mail_body = MIMEText(mail_content, 'html', 'utf-8')
    
    msg = MIMEMultipart()
    msg.attach(mail_body)
    
    msg["From"] = mail_sender
    msg["To"] = mail_recevier
    msg["Subject"] = mail_subject
    
    fp = open('./1.png', 'rb')
    image1 = MIMEImage(fp.read(), 'png')
    fp.close()
    
    fp2 = open('./2.jpg', 'rb')
    image2 = MIMEImage(fp2.read(), 'jpeg')
    fp2.close()
    
    image1.add_header('Content-ID', '<image1>')
    image2.add_header('Contend-ID', '<image2>')
    
    msg.attach(image1)
    msg.attach(image2)
    
    mail_server = smtplib.SMTP(mail_host, 25)
    mail_server.login(mail_sender, mail_pass)
    mail_server.sendmail(mail_sender, mail_recevier, msg.as_string())
    mail_server.quit()
    

    总结:
    MIMEMultipart()类构造了一个多内容(或者说多类型)的邮件主体对象。之前用的MIMEText() 和 MIMEImage() 都是构造邮件主体对象的类。但都只能构造单一类型的邮件主体。这点从邮件主体的mime类型上就可以看出来。如果要发送多类型的主体(比如既有普通文本,还有html超文本,或者包括后面的发送附件),就要用 MIMEMultipart()类构造邮件主体对象。这个类构造的邮件主体对象就像构建了一个箱子。里面可以有多类型的邮件内容,邮件内容可以直接被“添加”到大箱子里(用attach() 函数)。注意邮件内容添加进去之前也要是合法的邮件对象。比如经过MIMEText()或 MIMEImage()处理过的对象。(构造附件用的是MIMEText()类,有点奇怪。。)

    第24行,实例化了一个多类型的邮件主体对象。 第25行,添加了text/html格式的邮件对象(可以没有邮件头部信息,但要有mime 信息,这就是MIMEText() 和MIMEImage() 两个类的意义)。第42行和43行添加了image格式的邮件对象。第47行调用sendmail() 函数把邮件主体对象发出去,注意仍然要调用 as_string() 方法。

    注意HTML里怎么包含图片。 这里是给image格式的邮件对象用add_header()方法添加 ‘Content-ID’ 属性,用后面的属性值去对应HTML里的 cid 值,cid:image1 。这里仍然有问题。我发了两张图片,但只有一张能在邮件正文里显示出来,另一张在附件,正文不显示。后面研究下怎么发多张图片。

    六、用Python 脚本发带附件的邮件。主要熟悉如何在邮件主体对象里添加附件。

    #-*-coding:utf-8 -*-
    import smtplib
    from email.mime.image import MIMEImage
    from email.mime.text import MIMEText
    from email.mime.multipart import MIMEMultipart
    
    mail_host = 'smtp.163.com'
    mail_sender = 'zhouyy_1987@163.com'
    mail_pass = 'XXXXXXXX' 
    mail_recevier = 'zhouyy_1987@163.com'
    mail_subject = '测试邮件主题'
    
    mail_content = '''<p>你好,邮件发送测试...</p >
    <p>这是使用python登录163邮箱发送HTML格式和图片的测试邮件:</p >
    <p>图片演示:</p >
    <p>
    <br>< img src="cid:image1"></br >
    </p >
    '''
    
    mail_body = MIMEText(mail_content, 'html', 'utf-8')
    
    msg = MIMEMultipart()
    msg.attach(mail_body)
    
    msg["From"] = mail_sender
    msg["To"] = mail_recevier
    msg["Subject"] = mail_subject
    
    fp = open('./1.png', 'rb')
    image1 = MIMEImage(fp.read(), 'png')
    fp.close()
    image1.add_header('Content-ID', '<image1>')
    msg.attach(image1)
    
    att1 = MIMEText(open('./附件1-配置命令.txt', 'rb').read(), 'octet-stream', 'utf-8')
    att1.add_header('Content-Disposition', 'attachment', filename = '附件1-配置命令.txt')
    
    att2 = MIMEText(open('./附件2-线路账单.rar', 'rb').read(), 'octet-stream', 'utf-8')
    att2.add_header('Content-Disposition', 'attachment', filename = '附件2-线路账单.rar')
    
    att3 = MIMEText(open('./附件3-账单.pdf', 'rb').read(), 'octet-stream', 'utf-8')
    att3.add_header('Content-Disposition', 'attachment', filename = '附件3-账单.pdf')
    
    msg.attach(att1)
    msg.attach(att2)
    msg.attach(att3)
    
    mail_server = smtplib.SMTP(mail_host, 25)
    mail_server.login(mail_sender, mail_pass)
    mail_server.sendmail(mail_sender, mail_recevier, msg.as_string())
    mail_server.quit()
    

    总结:
    第36行,39行和第42行,构造了三个附件对象。第45,46,47行将三个对象添加进多类型邮件主体对象中。
    这里对附件对象应用 add_header() 方法非常重要。这里错了的话,接收方收到邮件后无法显示名字和扩展名,不知道用什么程序打开附件。add_header()函数的注释如下,注意前面两个参数的含义,以及最后一个参数的赋值方法,要用键值对的方式赋值。
    add_header(_name, _value, **_params) method of email.mime.image.MIMEImage instance
    Extended header setting.

    name is the header field to add. keyword arguments can be used to set
    additional parameters for the header field, with underscores converted
    to dashes. Normally the parameter will be added as key=”value” unless
    value is None, in which case only the key will be added. If a
    parameter value contains non-ASCII characters it can be specified as a
    three-tuple of (charset, language, value), in which case it will be
    encoded according to RFC2231 rules. Otherwise it will be encoded using
    the utf-8 charset and a language of ‘’.

    Examples:

    msg.add_header(‘content-disposition’, ‘attachment’, filename=’bud.gif’)
    msg.add_header(‘content-disposition’, ‘attachment’,
    filename=(‘utf-8’, ‘’, Fußballer.ppt’))
    msg.add_header(‘content-disposition’, ‘attachment’,
    filename=’Fußballer.ppt’))

    构造附件对象,这个例子里用的是MIMEText()类,mime type 是text/octet-stream。除了这种方法外,还可以用mime包里的另一个模块:application, 里面的 MIMEApplication()类来构造附件对象。这样可以把36、37行改为:

    att1 = MIMEApplication(open('./附件1-配置命令.txt', 'rb').read())
    att1.add_header('Content-Disposition', 'attachment', filename = '附件1-配置命令.txt')
    

    七、用第三方模块mailer发送带附件,带图片的HTML邮件。

    # -*- coding: UTF-8 -*-
    from mailer import Mailer
    from mailer import Message
    from email.header import Header
    
    mail_host = 'smtp.163.com'
    mail_sender = 'zhouyy_1987@163.com'
    mail_pass = 'XXXXXXXX'
    mail_recevier = 'zhouyy_1987@163.com'
    mail_subject = Header('测试邮件主题', 'utf-8').encode()
    
    mail_content = '''<p>你好,邮件发送测试...</p >
    <p>这是使用python登录163邮箱发送HTML格式和图片的测试邮件:</p >
    <p>图片演示:</p >
    <p>
    <br>< img src="cid:image1"></br >
    </p >
    '''
    mail_attach = "./1.png"
    mail_image = './2.jpg'
    #mail_body = '这是邮件主体正文。'
    
    msg = Message(From = mail_sender, To = mail_recevier, charset = 'utf-8', Html = mail_content)
    msg.Subject = mail_subject
    #msg.Body = mail_body
    msg.attach(mail_attach)
    msg.attach(mail_image, cid = 'image1')
    
    mail_server = Mailer(host = mail_host, pwd = mail_pass, usr = mail_sender)
    mail_server.send(msg)
    print('Finish!')
    

    总结:
    1、smtplib 和email 是Python的标准库。分别对smtp协议、命令和构造邮件主体内容过程进行了封装。前面的示例演示了如何使用这两个库构造邮件主体及发送邮件。归纳一下 email这个库。其原始的邮件对象叫:Message 对象。另外根据MIME类型的不同,还有多种类用于构造不同MIME类型的邮件对象。其多种对象(类)的继承关系如下:
    Message
    +- MIMEBase
    +- MIMEMultipart
    +- MIMENonMultipart
    +- MIMEMessage
    +- MIEMText
    +- MIMEImage
    如果用MIMEText类,就是构造了一个文本的邮件对象(纯文本或HTML文本),如果用MIMEImage类,就是构造了一个作为附件的图片邮件对象。而要把多类型的邮件对象组合起来,就要用MIMEMultipart类,构造一个多类型的邮件主体对象(就是前面提到的那个“箱子”)。而MIMEBase是基类,可以构造所有的对象。

    2、上面分析了email库里面的用于构造邮件对象的多个类及其继承关系。可以看到,用email这个模块构造邮件主体对象还是有些复杂,用起来比较麻烦。原因在于email这个模块抽象的程度还不够。所以就有了很多第三方模块,一般是基于smtplib 和email这连个标准库进行的二次开发,把构造邮件的过程进行了进一步的抽象,让整个工作变得更加简单。mailer就是其中的一个。

    3、第二行和第三行,从mailer模块里引入两个类:Message类和Mailer类,分别用于构造邮件主体对象和邮件服务器对象。第23行构造了一个邮件主体对象。和email库里的构造邮件主体对象类似,这里在构造的时候可以直接指定邮件正文。即在实例化对象时,用关键字参数指定邮件内容正文:HTML或Body参数,指定HTML格式的或纯文本格式的正文。邮件主体对象实例化时,也会指定邮件发送者和邮件接收者。这两个属性都会给在邮件发送时被提取出来,交给邮件服务器,作为 mail from 和 rcpt to 命令的参数。这样,发件人和收件人不需要写两遍。这个过程有点类似于直接在客户端上编写邮件,在邮件头部写发件人和收件人。Message类的init函数参数如下:
    class Message(builtins.object)
    | Message(kwargs)
    | Set the To, From, Reply-To, Subject, and Body attributes as plain-text strin
    gs.
    | Optionally, set the Html attribute to send an HTML email, or use the
    | attach() method to attach files.
    | init(self,
    kwargs)
    | Parameters and default values (parameter names are case insensitive):
    | To=None, From=None, RTo=None, CC=None, BCC=None, Subject=None, Body=
    one, Html=None,
    | Date=None, Attachments=None, Charset=None, Headers=None

    4、在Message类构造的对象里,邮件相关的内容都是一种属性,可以按照对一个对象属性赋值的方法添加值。比如24行,加了一个Subject 属性,作为邮件主题。 25行,加了一个Body属性,作为邮件正文内容。26和27就和之前的MIMEMultipart类相似地,用attach()函数加附件和图片对象。注意这里的图片资源,直接用文件路径代替即可,不需要 open后read出来再处理。也不需要构造成邮件对象后再加入主体邮件中。比起email中的方法要简单很多。