Python的smtplib提供了一种很方便的途径发送电子邮件。它对smtp协议进行了简单的封装。
smtp协议的基本命令包括:
HELO 向服务器标识用户身份
MAIL 初始化邮件传输 mail from:
RCPT 标识单个的邮件接收人;常在MAIL命令后面,可有多个rcpt to:
DATA 在单个或多个RCPT命令后,表示所有的邮件接收人已标识,并初始化数据传输,以.结束
VRFY 用于验证指定的用户/邮箱是否存在;由于安全方面的原因,服务器常禁止此命令
EXPN 验证给定的邮箱列表是否存在,扩充邮箱列表,也常被禁用
HELP 查询服务器支持什么命令
NOOP 无操作,服务器应响应OK
QUIT 结束会话
RSET 重置会话,当前传输被取消
MAIL FROM 指定发送者地址
RCPT TO 指明的接收者地址
一般smtp会话有两种方式,一种是邮件直接投递,就是说,比如你要发邮件給[email protected],那就直接连接163.com的邮件服务器,把信投給[email protected]; 另一种是验证过后的发信,它的过程是,比如你要发邮件給[email protected],你不是直接投到163.com,而是通过自己在sina.com的另一个邮箱来发。这样就要先连接sina.com的smtp服务器,然后认证,之后在把要发到163.com的信件投到sina.com上,sina.com会帮你把信投递到163.com。
第一种方式的命令流程基本是这样:
1. helo
2. mail from
3. rcpt to
4. data
5. quit
但是第一种发送方式一般有限制的,就是rcpt to指定的这个邮件接收者必须在这个服务器上存在,否则是不会接收的。 先看看代码:
#-*- encoding: gb2312 -*-importos, sys, string
importsmtplib
#邮件服务器地址mailserver="smtp.163.com"#smtp会话过程中的mail from地址from_addr="[email protected]"#smtp会话过程中的rcpt to地址to_addr="[email protected]"#信件内容msg="test mail"
svr=smtplib.SMTP(mailserver)
#设置为调试模式,就是在会话过程中会有输出信息svr.set_debuglevel(1)
#helo命令,docmd方法包括了获取对方服务器返回信息svr.docmd("HELO server")
#mail from, 发送邮件发送者svr.docmd("MAIL FROM: "%from_addr)
#rcpt to, 邮件接收者svr.docmd("RCPT TO: "%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 -*-importos, sys, stringimportsmtplibimportbase64#邮件服务器地址mailserver="smtp.163.com"#邮件用户名username="[email protected]"#密码password="xxxxxxx"#smtp会话过程中的mail from地址from_addr="[email protected]"#smtp会话过程中的rcpt to地址to_addr="[email protected]"#信件内容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: "%from_addr)#rcpt to, 邮件接收者svr.docmd("RCPT TO: "%to_addr)#data命令,开始发送数据svr.docmd("DATA")#发送正文数据svr.send(msg)#比如以 . 作为正文发送结束的标记svr.send(".")
svr.getreply()#发送结束,退出svr.quit()
上面说的是最普通的情况,但是不能忽略的是现在好多企业邮件是支持安全邮件的,就是通过SSL发送的邮件,这个怎么发呢?SMTP对SSL安全邮件的支持有两种方案,一种老的是专门开启一个465端口来接收ssl邮件,另一种更新的做法是在标准的25端口的smtp上增加一个starttls的命令来支持。
看看第一种怎么办:
#-*- encoding: gb2312 -*-importos, sys, string, socketimportsmtplibclassSMTP_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)defconnect(self, host='localhost', port=465):ifnotportand(host.find(':')==host.rfind(':')):
i=host.rfind(':')ifi>=0:
host, port=host[:i], host[i+1:]try: port=int(port)exceptValueError:raisesocket.error,"nonnumeric port"ifnotport: port=654ifself.debuglevel>0:print>>stderr,'connect:', (host, port)
msg="getaddrinfo returns an empty list"self.sock=Noneforresinsocket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
af, socktype, proto, canonname, sa=restry:
self.sock=socket.socket(af, socktype, proto)ifself.debuglevel>0:print>>stderr,'connect:', (host, port)
self.sock.connect(sa)#新增加的创建ssl连接sslobj=socket.ssl(self.sock, self.key, self.cert)exceptsocket.error, msg:ifself.debuglevel>0:print>>stderr,'connect fail:', (host, port)ifself.sock:
self.sock.close()
self.sock=Nonecontinuebreakifnotself.sock:raisesocket.error, msg#设置sslself.sock=smtplib.SSLFakeSocket(self.sock, sslobj)
self.file=smtplib.SSLFakeFile(sslobj);
(code, msg)=self.getreply()ifself.debuglevel>0:print>>stderr,"connect:", msgreturn(code, msg)if__name__=='__main__':
smtp=SMTP_SSL('192.168.2.10')
smtp.set_debuglevel(1)
smtp.sendmail("[email protected]","[email protected]","xxxxxxxxxxxxxxxxx")
smtp.quit()
这里我是从原来的smtplib.SMTP派生出了新的SMTP_SSL类,它专门来处理ssl连接。我这里测试的192.168.2.10是我自己的测试服务器.
第二种是新增加了starttls的命令,这个很简单,smtplib里就有这个方法,叫smtplib.starttls()。当然,不是所有的邮件系统都支持安全邮件的,这个需要从ehlo的返回值里来确认,如果里面有starttls,才表示支持。相对于发送普通邮件的第二种方法来说,只需要新增加一行代码就可以了:
#-*- encoding: gb2312 -*-importos, sys, stringimportsmtplibimportbase64#邮件服务器地址mailserver="smtp.163.com"#邮件用户名username="[email protected]"#密码password="xxxxxxx"#smtp会话过程中的mail from地址from_addr="[email protected]"#smtp会话过程中的rcpt to地址to_addr="[email protected]"#信件内容msg="my test mail"svr=smtplib.SMTP(mailserver)#设置为调试模式,就是在会话过程中会有输出信息svr.set_debuglevel(1)#ehlo命令,docmd方法包括了获取对方服务器返回信息,如果支持安全邮件,返回值里会有starttls提示svr.docmd("EHLO server")
svr.starttls()#
svr.getreply()#发送密码svr.send(base64.encodestring(password))
svr.getreply()#mail from, 发送邮件发送者svr.docmd("MAIL FROM: "%from_addr)#rcpt to, 邮件接收者svr.docmd("RCPT TO: "%to_addr)#data命令,开始发送数据svr.docmd("DATA")#发送正文数据svr.send(msg)#比如以 . 作为正文发送结束的标记svr.send(".")
svr.getreply()#发送结束,退出svr.quit()
注意: 以上的代码为了方便我都没有判断返回值,严格说来,是应该判断一下返回的代码的,在smtp协议中,只有返回代码是2xx或者3xx才能继续下一步,返回4xx或5xx的,都是出错了。