美文网首页
python smtplib库发送邮件过程分析

python smtplib库发送邮件过程分析

作者: SyKay | 来源:发表于2020-11-17 17:03 被阅读0次

    python进行邮件发送,主要使用到了以下两个库
    smtplib库: 用于发送邮件,它对smtp协议进行了简单的封装。
    email库: 用于构造邮件。

    下面是来自阮一峰老师的代码案例

    from email.header import Header
    from email.mime.text import MIMEText
    from email.utils import parseaddr, formataddr
    import smtplib
    
    def _format_addr(s):
        # 格式化一个邮件地址,注意不能简单地传入name <addr@example.com>
        # 因为如果包含中文,需要通过Header对象进行编码。
        name, addr = parseaddr(s)
        return formataddr((Header(name, 'utf-8').encode(), addr))
    
    # 配置 邮件发件人、收件人信息
    from_addr = "**@**.com"
    password = "******"
    to_addr = "**@**.com"  # 输入收件人地址,接收的是字符串而不是list,如果有多个邮件地址,用,分隔即可。
    smtp_server = "mail.**.com"  # 设置邮箱服务器 mail.**.com
    
    # 构造一封简单的文本邮件
    msg = MIMEText('Hello, send by Python..', 'plain', 'utf-8')  # plain表示纯文本
    msg['From'] = _format_addr("Python爱好者<%s>" % from_addr)
    msg['To'] = _format_addr("管理员<%s>" % to_addr)
    msg['Subject'] = Header('来着SMTP的问候', 'utf-8').encode()
    
    # 进行邮箱登录、邮件发送
    smtpObj = smtplib.SMTP(smtp_server, 25)
    smtpObj.set_debuglevel(1)
    smtpObj.starttls()
    smtpObj.login(from_addr, password)
    smtpObj.sendmail(from_addr, [to_addr], msg.as_string())
    smtpObj.quit()
    

    其中,用于发送邮件代码模块主要如下:

    import smtplib
    smtpObj = smtplib.SMTP(smtp_server, 25)
    smtpObj.set_debuglevel(debuglevel=1)
    smtpObj.starttls()
    smtpObj.login(from_addr, password)
    smtpObj.sendmail(from_addr, [to_addr], msg.as_string())
    smtpObj.quit()
    

    下面对各实现的代码进行简要分析

    1、 smtpObj = smtplib.SMTP(smtp_server, 25)
    本质是与邮箱服务器创建一个socket链接,后续会使用明文通讯,端口默认25;
    此外,也可以使用smtplib.SMTP_SSL(smtp_server,465),后续通讯则会采用SSL加密方式,端口默认465
    如下:连接上服务器后,响应码220,表示连接成功

    retcode (220); Msg: b'Ex201602.zjft.com Microsoft ESMTP MAIL Service ready at Wed, 18 Nov 2020 10:29:53 +0800'
    

    2、 smtpObj.set_debuglevel(debuglevel=1)
    设置debug等级,用于调试(不设置不影响发邮件),设置了之后客户端和邮箱服务器通讯时,会输出通讯内容
    debuglevel参数大于1,控制台输出通讯时间和通讯内容,否则仅输出通讯内容

    3、 smtpObj.starttls()
    告诉邮箱服务器使用 TLS安全协议 进行通信加密(TLS协议 前身是 SSL协议,简单理解 TLS协议 为 SSL协议 的升级版即可)。其中,部分服务器通讯强制需要TLS通讯加密

    1、 有些邮箱服务器强制必须使用TLS协议,没使用starttls()的话,后面在登录邮箱时会报如下异常
    >> No suitable authentication method found.
    
    2、 有些邮箱服务器不支持使用TLS协议,使用了starttls()的话,会报错,如下
    >> STARTTLS extension not supported by server.
    
    3、有些邮箱服务器不强制使用TLS通讯加密,即使不使用starttls()都可以(我公司邮箱服务器就是这样子)。
    - 综上所述,邮箱登录前需不需要使用 `starttls()`,这得实际测试后才知道
    

    以下是执行smtpObj.starttls(),控制台输出的日志,可分为两部分

    send: 'ehlo [192.168.42.1]\r\n'
    reply: b'250-Ex201602.zjft.com Hello [10.1.1.85]\r\n'
    reply: b'250-SIZE 52428800\r\n'
    reply: b'250-PIPELINING\r\n'
    reply: b'250-DSN\r\n'
    reply: b'250-ENHANCEDSTATUSCODES\r\n'
    reply: b'250-STARTTLS\r\n'
    reply: b'250-X-ANONYMOUSTLS\r\n'
    reply: b'250-AUTH NTLM LOGIN\r\n'
    reply: b'250-X-EXPS GSSAPI NTLM\r\n'
    reply: b'250-8BITMIME\r\n'
    reply: b'250-BINARYMIME\r\n'
    reply: b'250-CHUNKING\r\n'
    reply: b'250 XRDST\r\n'
    reply: retcode (250); Msg: b'Ex201602.zjft.com Hello [10.1.1.85]\nSIZE 52428800\nPIPELINING\nDSN\nENHANCEDSTATUSCODES\nSTARTTLS\nX-ANONYMOUSTLS\nAUTH NTLM LOGIN\nX-EXPS GSSAPI NTLM\n8BITMIME\nBINARYMIME\nCHUNKING\nXRDST'
    
    send: 'STARTTLS\r\n'
    reply: b'220 2.0.0 SMTP server ready\r\n'
    reply: retcode (220); Msg: b'2.0.0 SMTP server ready'
    
    • 前面部分,客户端想服务器发送了 EHLO命令(向服务器可以表明自己支持扩展简单邮件传输协议 (ESMTP) 命令),之后服务器会进行响应,客户端根据响应内容判断服务器支不支持 TLS协议(是否包含STARTTLS关键字),不支持的话直接就抛异常

    • 后面部分,客户端再向服务器发送 STARTTLS命令,告诉邮箱服务器使用TLS安全协议进行通信加密。

    4、 smtpObj.login(from_addr, password)
    进行smtp认证,即我们常说的邮箱登录

    send: 'ehlo [192.168.42.1]\r\n'
    reply: b'250-Ex201602.zjft.com Hello [10.1.1.85]\r\n'
    reply: b'250-SIZE 52428800\r\n'
    reply: b'250-PIPELINING\r\n'
    reply: b'250-DSN\r\n'
    reply: b'250-ENHANCEDSTATUSCODES\r\n'
    reply: b'250-STARTTLS\r\n'
    reply: b'250-X-ANONYMOUSTLS\r\n'
    reply: b'250-AUTH NTLM LOGIN\r\n'
    reply: b'250-X-EXPS GSSAPI NTLM\r\n'
    reply: b'250-8BITMIME\r\n'
    reply: b'250-BINARYMIME\r\n'
    reply: b'250-CHUNKING\r\n'
    reply: b'250 XRDST\r\n'
    reply: retcode (250); Msg: b'Ex201602.zjft.com Hello [10.1.1.85]\nSIZE 52428800\nPIPELINING\nDSN\nENHANCEDSTATUSCODES\nSTARTTLS\nX-ANONYMOUSTLS\nAUTH NTLM LOGIN\nX-EXPS GSSAPI NTLM\n8BITMIME\nBINARYMIME\nCHUNKING\nXRDST'
    
    send: 'AUTH LOGIN c2tzdW5AempmdC5jb20=\r\n'
    reply: b'334 UGFzc3dvcmQ6\r\n'
    reply: retcode (334); Msg: b'UGFzc3dvcmQ6'
    
    send: 'U1VOa2FpMTIyNQ==\r\n'
    reply: b'235 2.7.0 Authentication successful\r\n'
    reply: retcode (235); Msg: b'2.7.0 Authentication successful'
    
    • 同前面的starttls一样,通讯前先进行发送 ehlo 命令
    • 根据邮箱服务器相应的内容进行判断,是否需要进行登录
    • 之后客户端发送关键字 AUTH LOGIN + base64加密过的邮箱用户名 给服务器
    • 在接收到服务器提示输入密码后,再把base64加密过的邮箱密码发送给服务器
    • 至此,登录完成 2.7.0 Authentication successful

    5、 smtpObj.sendmail(from_addr, [to_addr], msg.as_string())
    发送邮件,传入参数

    send: 'mail FROM:<12345@**.com> size=133\r\n'
    reply: b'250 2.1.0 Sender OK\r\n'
    reply: retcode (250); Msg: b'2.1.0 Sender OK'
    send: 'rcpt TO:<123456@qq.com>\r\n'
    reply: b'250 2.1.5 Recipient OK\r\n'
    reply: retcode (250); Msg: b'2.1.5 Recipient OK'
    send: 'data\r\n'
    reply: b'354 Start mail input; end with <CRLF>.<CRLF>\r\n'
    reply: retcode (354); Msg: b'Start mail input; end with <CRLF>.<CRLF>'
    data: (354, b'Start mail input; end with <CRLF>.<CRLF>')
    send: b'Content-Type: text/plain; charset="utf-8"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: base64\r\n\r\nSGVsbG8sIHNlbmQgYnkgUHl0aG9uLi4=\r\n.\r\n'
    reply: b'250 2.6.0 <bcbef1ba-1733-442c-a69e-ea3a5feb48b3@ex201601.zjft.com> [InternalId=38594576122924, Hostname=Ex201601.zjft.com] 1343 bytes in 0.141, 9.276 KB/sec Queued mail for delivery\r\n'
    reply: retcode (250); Msg: b'2.6.0 <bcbef1ba-1733-442c-a69e-ea3a5feb48b3@ex201601.zjft.com> [InternalId=38594576122924, Hostname=Ex201601.zjft.com] 1343 bytes in 0.141, 9.276 KB/sec Queued mail for delivery'
    data: (250, b'2.6.0 <bcbef1ba-1733-442c-a69e-ea3a5feb48b3@ex201601.zjft.com> [InternalId=38594576122924, Hostname=Ex201601.zjft.com] 1343 bytes in 0.141, 9.276 KB/sec Queued mail for delivery')
    

    从日志可以看出,客户端先给服务器发送发件人账户信息,命令是mail FROM:

    • send: 'mail FROM:<12345@**.com> size=133\r\n'
      其次发送收件人信息(如有多个收件人则重复发送多次),命令是rcpt TO:
    • send: 'rcpt TO:<123456@qq.com>\r\n'
      最后,发送 data 关键字,再把邮件内容发送过去
      发送完邮件内容,再发送关键词 . ,表示邮件内容结束。

    6、 smtpObj.quit()
    退出与服务器的连接,关闭连接通道。

    send: 'quit\r\n'
    reply: b'221 2.0.0 Service closing transmission channel\r\n'
    reply: retcode (221); Msg: b'2.0.0 Service closing transmission channel'
    

    相关文章

      网友评论

          本文标题:python smtplib库发送邮件过程分析

          本文链接:https://www.haomeiwen.com/subject/gpaebktx.html