一、smtplib
SMTP是一个主要发送、转寄和保存E-mail的协议
#!/usr/bin/env python
import sys, smtplib, socket
if len(sys.argv)<4:
print 'Syntax: %s server fromaddr toaddr [to addr...]' % sys.argv[0]
sys.exit(1)
server=sys.argv[1]
fromaddr=sys.argv[2]
toaddrs=sys.argv[3:]
message='''To: %s
From: %s
Subject: Test
Hello, this is a test'''%(', '.join(toaddrs), fromaddr)
try:
s=smtplib.SMTP(server)
#s.set_debuglevel(1) #(开启隐藏在表面之下的运行情况)
s.sendmail(fromaddr, toaddrs, message)
except (socket.gaierror, socket.errnor, socket.herror, smtplib.SMTPException), e:
print '*** Your message may not have been sent'
print e
sys.exit(1)
else:
print 'Message successfully sent to %d recipient(s)'%len(toaddrs)
以上程序调用几个命令行参数:SMTP服务器名称,一个发送者的地址和一个或多个收件人的地址。发件人和收件人的地址必须是有效的。只有传递给sendmail()的收件人列表才能确定谁可以收到邮件,即使To和Cc header全有不同的值,也只有在这个列表上的用户可以收到邮件。
s.set_debuglevel(1)
开启这个可以看到smtplib模块和SMTP服务器在网络上的会话。首先,客户端(使用smtplib)发送了一个包含本地主机名的EHLO指令。远程主机以它的主机名响应,同时返回它支持的可选特性的细节。下一步,客户端通过指令发送邮件,它指出了寄件人的地址和邮件的大小。这时,服务器有机会拒绝这个邮件,若接收则返回250返回码。之后客户端发送一个rcpt指令。最后客户端发送一个数据指令,发送实际的邮件,并结束会话。
要注意的是,即使一开始显示发送成功也不能就认为一定成功了。例如很多公司有一个特殊的邮件服务器接收来自外部的邮件,接着转寄给在公司防火墙内部的相关服务器,这个服务器可能会拒绝这个邮件,尽管开始的时候,第一个服务器是接收的。在这种情况下,即使邮件开始的时候是发送成功的,之后会把邮件退回给发件人。
二、从EHLO中得到信息
在最初的时候,客户端一开始会向服务器发送一个HELO指令作为初始的问候。后来SMTP得到扩展,发展除EHLO。支持EHLO的服务器在接收到这个请求时,会返回它支持的可选SMTP特性的信息。以下代码可以从服务器得到允许的最大邮件容量,并在发送过大邮件的时候返回一个错误。
#!/usr/bin/env python
import sys, smtplib, socket
if len(sys.argv)<4:
print 'Syntax: %s server fromaddr toaddr [toaddr...]' % sys.argv[0]
sys.exit(1)
server=sys.argv[1]
fromaddr=sys.argv[2]
toaddrs=sys.argv[3:]
message='''To: %s
From: %s
Subject: Test
Hello, this is a test'''%(', '.join(toaddrs), fromaddr)
try:
s=smtplib.SMTP(server)
code=s.ehlo()[0]
usesesmtp=1
if not (200<=code<=299):
usesesmtp=0
code=s.helo()[0]
if not (200<=code<=299):
raise SMTPHeloError(code, resp)
if usesesmtp and s.has_extn('size'):
print 'Maximum message size is', s.esmtp_features['size']
if len(message)>int(s.esmtp_features['size']):
print 'Message too large'
sys.exit(2)
s.sendmail(fromaddr, toaddrs, message)
except (socket.gaierror, socket.error, socket.herror, smtplib.SMTPException), e:
print '***Your message may not have been sent'
print e
sys.exit(1)
else:
print 'Message successfully sent to %d recipient(s)'%len(toaddrs)
三、认证
一些SMTP服务器在发送邮件之前需要认证。login()函数可以实现这一点。
#!/usr/bin/env python
import sys, smtplib, socket
from getpass import getpass
if len(sys.argv)<4:
print 'Syntax: %s server fromaddr toaddr' % sys.argv[0]
sys.exit(255)
server=sys.argv[1]
fromaddr=sys.argv[2]
toaddrs=sys.argv[3:]
message='''To: %s
From: %s
Subject: Test
Hello, this is a test'''%(', '.join(toaddrs), fromaddr)
sys.stdout.write('Enter username: ')
username=sys.stdin.readline().strip()
password=getpass('Enter password: ')
try:
s=smtplib.SMTP(server)
try:
s.login(username, password)
except smtplib.SMTPException, e:
print "authentication failed:", e
sys.exit(1)
s.sendmail(fromaddr, toaddrs, message)
except (socket.gaierror, socket.error, socket.herror, smtplib.SMTPException), e:
print '*** Your message may not have been sent'
print e
sys.exit(2)
else:
print 'Message successfully sent to %d recipient(s)'%len(toaddrs)
如果ehlo之后返回的SMTP服务器特性中有auth,则说明支持验证。若服务器不支持认证,会收到一条authentication failed错误,可以在调用s.ehlo()后使用s.has_extn('auth')来避免这个错误。
注:Python的smtplib模块并不能作为发送通常目地的邮件转播器。相反,应该把邮件发送到距离最近的一个SMTP服务器,让它进行实际的邮件递送。