一、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服务器,让它进行实际的邮件递送。