Python smtplib模块详解:SMTP协议命令与SSL安全邮件发送实战
本文深入解析Python标准库smtplib,详细讲解SMTP协议核心命令(HELO、MAIL FROM、RCPT TO等)及两种常见发信流程。重点介绍通过AUTH LOGIN进行身份认证的方法,以及利用SMTP_SSL类和starttls命令实现SSL安全邮件发送的两种技术方案,附带完整代码示例与注意事项。
Python内置的smtplib模块为发送电子邮件提供了便捷途径,它对复杂的SMTP协议进行了简洁封装,使得开发者能够轻松实现邮件发送功能。
SMTP协议包含一系列基本命令用于控制邮件传输过程:HELO用于向服务器标识用户身份;MAIL FROM初始化邮件传输并指定发件人;RCPT TO用于标识单个邮件接收者,通常跟随在MAIL命令之后,可指定多个接收者;DATA命令在确认所有接收者后启动数据传输,以单独一行的句点(.)作为结束标志;VRFY命令用于验证指定用户或邮箱是否存在,出于安全考虑,多数服务器已禁用此命令;EXPN命令用于验证邮箱列表或扩充邮箱列表,同样常被禁用;HELP命令用于查询服务器支持的命令集;NOOP命令执行无操作,服务器应响应OK;QUIT命令用于结束会话;RSET命令用于重置当前会话并取消正在进行的传输;此外还有MAIL FROM指定发送者地址和RCPT TO指定接收者地址的具体用法。
常规的SMTP会话主要分为两种方式。第一种是直接投递模式,例如向zzz@163.com发送邮件时,客户端直接连接163.com的邮件服务器并将信件投递给该用户。第二种是经过身份验证后的发信模式,例如用户希望通过自己在sina.com的邮箱向zzz@163.com发送邮件。此时,客户端首先连接sina.com的SMTP服务器,完成身份认证后,将邮件提交给sina.com服务器,由sina.com服务器负责将邮件最终投递到163.com。
第一种直接投递方式的命令执行流程大致如下:
1. 执行helo命令 2. 执行mail from命令指定发件人 3. 执行rcpt to命令指定收件人 4. 执行data命令开始发送数据 5. 执行quit命令结束会话
然而,第一种发送方式通常存在限制,即RCPT TO指定的邮件接收者必须在该服务器上真实存在,否则服务器将拒绝接收邮件。以下代码展示了这一过程:
#-*- encoding: gb2312 -*-
import os, sys, string
import smtplib
# 邮件服务器地址
mailserver = "smtp.163.com"
# smtp会话过程中的mail from地址
from_addr = "asfgysg@zxsdf.com"
# smtp会话过程中的rcpt to地址
to_addr = "zhaoweikid@163.com"
# 信件内容
msg = "test mail"
svr = smtplib.SMTP(mailserver)
# 设置为调试模式,就是在会话过程中会有输出信息
svr.set_debuglevel(1)
# helo命令,docmd方法包括了获取对方服务器返回信息
svr.docmd("HELO server")
# mail from, 发送邮件发送者
svr.docmd("MAIL FROM: <%s>" % from_addr)
# rcpt to, 邮件接收者
svr.docmd("RCPT TO: <%s>" % to_addr)
# data命令,开始发送数据
svr.docmd("DATA")
# 发送正文数据
svr.send(msg)
# 比如以 . 作为正文发送结束的标记,用send发送的,所以要用getreply获取返回信息
svr.send(" . ")
svr.getreply()
# 发送结束,退出
svr.quit()
需要注意的是,163.com等主流邮箱服务商具备反垃圾邮件过滤机制,上述直接投递方法很可能无法通过反垃圾邮件系统的检测。因此,一般不建议个人用户采用这种方式发送邮件。
第二种发信方式流程有所不同,具体步骤如下: 1. 执行ehlo命令 2. 执行auth login进行身份认证 3. 执行mail from命令 4. 执行rcpt to命令 5. 执行data命令 6. 执行quit命令
相较于第一种方式,第二种方式增加了一个关键的认证环节,即通过auth login命令进行用户身份验证。
#-*- encoding: gb2312 -*-
import os, sys, string
import smtplib
import base64
# 邮件服务器地址
mailserver = "smtp.163.com"
# 邮件用户名
username = "xxxxxx@163.com"
# 密码
password = "xxxxxxx"
# smtp会话过程中的mail from地址
from_addr = "xxxxxx@163.com"
# smtp会话过程中的rcpt to地址
to_addr = "yyyyyy@163.com"
# 信件内容
msg = "my test mail"
svr = smtplib.SMTP(mailserver)
# 设置为调试模式,就是在会话过程中会有输出信息
svr.set_debuglevel(1)
# ehlo命令,docmd方法包括了获取对方服务器返回信息
svr.docmd("EHLO server")
# auth login 命令
svr.docmd("AUTH LOGIN")
# 发送用户名,是base64编码过的,用send发送的,所以要用getreply获取返回信息
svr.send(base64.encodestring(username))
svr.getreply()
# 发送密码
svr.send(base64.encodestring(password))
svr.getreply()
# mail from, 发送邮件发送者
svr.docmd("MAIL FROM: <%s>" % from_addr)
# rcpt to, 邮件接收者
svr.docmd("RCPT TO: <%s>" % to_addr)
# data命令,开始发送数据
svr.docmd("DATA")
# 发送正文数据
svr.send(msg)
# 比如以 . 作为正文发送结束的标记
svr.send(" . ")
svr.getreply()
# 发送结束,退出
svr.quit()
上述讨论的是普通邮件发送情况,但不可忽视的是,许多企业邮箱支持通过SSL加密发送安全邮件。实现SSL安全邮件主要有两种方案:一种是较老的做法,专门开启465端口来接收SSL邮件;另一种是更新的做法,在标准的25端口SMTP基础上增加starttls命令来支持安全传输。
下面介绍第一种方案的实现方法:
#-*- encoding: gb2312 -*-
import os, sys, string, socket
import smtplib
class SMTP_SSL (smtplib.SMTP):
def __init__(self, host='', port=465, local_hostname=None, key=None, cert=None):
self.cert = cert
self.key = key
smtplib.SMTP.__init__(self, host, port, local_hostname)
def connect(self, host='localhost', port=465):
if not port and (host.find(':') == host.rfind(':')):
i = host.rfind(':')
if i >= 0:
host, port = host[:i], host[i+1:]
try: port = int(port)
except ValueError:
raise socket.error, "nonnumeric port"
if not port: port = 654
if self.debuglevel > 0: print>>stderr, 'connect:', (host, port)
msg = "getaddrinfo returns an empty list"
self.sock = None
for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
af, socktype, proto, canonname, sa = res
try:
self.sock = socket.socket(af, socktype, proto)
if self.debuglevel > 0: print>>stderr, 'connect:', (host, port)
self.sock.connect(sa)
# 新增加的创建ssl连接
sslobj = socket.ssl(self.sock, self.key, self.cert)
except socket.error, msg:
if self.debuglevel > 0:
print>>stderr, 'connect fail:', (host, port)
if self.sock:
self.sock.close()
self.sock = None
continue
break
if not self.sock:
raise socket.error, msg
# 设置ssl
self.sock = smtplib.SSLFakeSocket(self.sock, sslobj)
self.file = smtplib.SSLFakeFile(sslobj);
(code, msg) = self.getreply()
if self.debuglevel > 0: print>>stderr, "connect:", msg
return (code, msg)
if __name__ == '__main__':
smtp = SMTP_SSL('192.168.2.10')
smtp.set_debuglevel(1)
smtp.sendmail("zzz@xxx.com", "zhaowei@zhaowei.com", "xxxxxxxxxxxxxxxxx")
smtp.quit()
在此方案中,我从原有的smtplib.SMTP类派生出了新的SMTP_SSL类,专门用于处理SSL连接。测试中使用的192.168.2.10是我本地的测试服务器地址。
第二种方案则是新增starttls命令,这在smtplib库中已有对应方法smtplib.starttls()。需要注意的是,并非所有邮件系统都支持安全邮件功能,这需要从EHLO命令的返回值中确认,如果返回值中包含starttls关键字,则表明服务器支持该功能。相较于发送普通邮件的第二种方法,只需新增一行代码即可实现:
#-*- encoding: gb2312 -*-
import os, sys, string
import smtplib
import base64
# 邮件服务器地址
mailserver = "smtp.163.com"
# 邮件用户名
username = "xxxxxx@163.com"
# 密码
password = "xxxxxxx"
# smtp会话过程中的mail from地址
from_addr = "xxxxxx@163.com"
# smtp会话过程中的rcpt to地址
to_addr = "yyyyyy@163.com"
# 信件内容
msg = "my test mail"
svr = smtplib.SMTP(mailserver)
# 设置为调试模式,就是在会话过程中会有输出信息
svr.set_debuglevel(1)
# ehlo命令,docmd方法包括了获取对方服务器返回信息,如果支持安全邮件,返回值里会有starttls提示
svr.docmd("EHLO server")
svr.starttls() # <------ 这行就是新加的支持安全邮件的代码!
# auth login 命令
svr.docmd("AUTH LOGIN")
# 发送用户名,是base64编码过的,用send发送的,所以要用getreply获取返回信息
svr.send(base64.encodestring(username))
svr.getreply()
# 发送密码
svr.send(base64.encodestring(password))
svr.getreply()
# mail from, 发送邮件发送者
svr.docmd("MAIL FROM: <%s>" % from_addr)
# rcpt to, 邮件接收者
svr.docmd("RCPT TO: <%s>" % to_addr)
# data命令,开始发送数据
svr.docmd("DATA")
# 发送正文数据
svr.send(msg)
# 比如以 . 作为正文发送结束的标记
svr.send(" . ")
svr.getreply()
# 发送结束,退出
svr.quit()
请注意:为了简化代码展示,以上示例均未对服务器返回值进行判断。严格来说,应当检查返回的状态码,因为在SMTP协议中,只有返回代码为2xx或3xx时才能继续执行下一步操作,而返回4xx或5xx则表示出现错误。
以上就是本文的全部内容,希望对大家的学习有所帮助,也欢迎大家多多支持本站。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













