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'
网友评论