3 Author: The Dragon De Monsyne <dragondm@integral.org>
5 (This was modified from the Python 1.5 library HTTP lib.)
7 This should follow RFC 821. (it dosen't do esmtp (yet))
12 >>> s=smtplib.SMTP("localhost")
14 This is Sendmail version 8.8.4
16 HELO EHLO MAIL RCPT DATA
17 RSET NOOP QUIT HELP VRFY
19 For more info use "HELP <topic>".
20 To report bugs in the implementation send email to
21 sendmail-bugs@sendmail.org.
22 For local information send email to Postmaster at your site.
24 >>> s.putcmd("vrfy","someone@here")
26 (250, "Somebody OverHere <somebody@here.my.org>")
38 SMTPServerDisconnected
="Server not connected"
39 SMTPSenderRefused
="Sender address refused"
40 SMTPRecipientsRefused
="All Recipients refused"
41 SMTPDataError
="Error transmoitting message data"
44 """This class manages a connection to an SMTP server."""
46 def __init__(self
, host
= '', port
= 0):
47 """Initialize a new instance.
49 If specified, `host' is the name of the remote host to which
50 to connect. If specified, `port' specifies the port to which
51 to connect. By default, smtplib.SMTP_PORT is used.
57 if host
: self
.connect(host
, port
)
59 def set_debuglevel(self
, debuglevel
):
60 """Set the debug output level.
62 A non-false value results in debug messages for connection and
63 for all messages sent to and received from the server.
66 self
.debuglevel
= debuglevel
68 def connect(self
, host
='localhost', port
= 0):
69 """Connect to a host on a given port.
71 Note: This method is automatically invoked by __init__,
72 if a host is specified during instantiation.
76 i
= string
.find(host
, ':')
78 host
, port
= host
[:i
], host
[i
+1:]
79 try: port
= string
.atoi(port
)
80 except string
.atoi_error
:
81 raise socket
.error
, "nonnumeric port"
82 if not port
: port
= SMTP_PORT
83 self
.sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
84 if self
.debuglevel
> 0: print 'connect:', (host
, port
)
85 self
.sock
.connect(host
, port
)
86 (code
,msg
)=self
.getreply()
87 if self
.debuglevel
>0 : print "connect:", msg
91 """Send `str' to the server."""
92 if self
.debuglevel
> 0: print 'send:', `
str`
96 raise SMTPServerDisconnected
98 def putcmd(self
, cmd
, args
=""):
99 """Send a command to the server.
101 str = '%s %s%s' % (cmd
, args
, CRLF
)
105 """Get a reply from the server.
107 Returns a tuple consisting of:
108 - server response code (e.g. '250', or such, if all goes well)
109 Note: returns -1 if it can't read responce code.
110 - server response string corresponding to response code
111 (note : multiline responces converted to a single,
115 self
.file = self
.sock
.makefile('rb')
117 line
= self
.file.readline()
118 if self
.debuglevel
> 0: print 'reply:', `line`
119 resp
.append(string
.strip(line
[4:]))
121 #check if multiline resp
125 errcode
= string
.atoi(code
)
129 errmsg
= string
.join(resp
,"\n")
130 if self
.debuglevel
> 0:
131 print 'reply: retcode (%s); Msg: %s' % (errcode
,errmsg
)
132 return errcode
, errmsg
134 def docmd(self
, cmd
, args
=""):
135 """ Send a command, and return it's responce code """
137 self
.putcmd(cmd
,args
)
138 (code
,msg
)=self
.getreply()
142 def helo(self
, name
=''):
143 """ SMTP 'helo' command. Hostname to send for this command
144 defaults to the FQDN of the local host """
145 name
=string
.strip(name
)
147 name
=socket
.gethostbyaddr(socket
.gethostname())[0]
148 self
.putcmd("helo",name
)
149 (code
,msg
)=self
.getreply()
153 def help(self
, args
=''):
154 """ SMTP 'help' command. Returns help text from server """
155 self
.putcmd("help", args
)
156 (code
,msg
)=self
.getreply()
160 """ SMTP 'rset' command. Resets session. """
161 code
=self
.docmd("rset")
165 """ SMTP 'noop' command. Dosen't do anything :> """
166 code
=self
.docmd("noop")
169 def mail(self
,sender
):
170 """ SMTP 'mail' command. Begins mail xfer session. """
171 self
.putcmd("mail","from: %s" % sender
)
172 return self
.getreply()
174 def rcpt(self
,recip
):
175 """ SMTP 'rcpt' command. Indicates 1 recipient for this mail. """
176 self
.putcmd("rcpt","to: %s" % recip
)
177 return self
.getreply()
180 """ SMTP 'DATA' command. Sends message data to server.
181 Automatically quotes lines beginning with a period per rfc821 """
182 #quote periods in msg according to RFC821
183 # ps, I don't know why I have to do it this way... doing:
184 # quotepat=re.compile(r"^[.]",re.M)
185 # msg=re.sub(quotepat,"..",msg)
186 # should work, but it dosen't (it doubles the number of any
187 # contiguous series of .'s at the beginning of a line,
188 #instead of just adding one. )
189 quotepat
=re
.compile(r
"^[.]+",re
.M
)
191 return "."+pat
.group(0)
192 msg
=re
.sub(quotepat
,m
,msg
)
194 (code
,repl
)=self
.getreply()
195 if self
.debuglevel
>0 : print "data:", (code
,repl
)
201 (code
,msg
)=self
.getreply()
202 if self
.debuglevel
>0 : print "data:", (code
,msg
)
205 #some usefull methods
206 def sendmail(self
,from_addr
,to_addrs
,msg
):
207 """ This command performs an entire mail transaction.
209 - from_addr : The address sending this mail.
210 - to_addrs : a list of addresses to send this mail to
211 - msg : the message to send.
213 This method will return normally if the mail is accepted for at least
215 Otherwise it will throw an exception (either SMTPSenderRefused,
216 SMTPRecipientsRefused, or SMTPDataError)
218 That is, if this method does not throw an exception, then someone
219 should get your mail.
221 It returns a dictionary , with one entry for each recipient that was
227 >>> s=smtplib.SMTP("localhost")
228 >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]
231 ... Subject: testin'...
233 ... This is a test '''
234 >>> s.sendmail("me@my.org",tolist,msg)
235 { "three@three.org" : ( 550 ,"User unknown" ) }
238 In the above example, the message was accepted for delivery to
239 three of the four addresses, and one was rejected, with the error
240 code 550. If all addresses are accepted, then the method
241 will return an empty dictionary.
244 if not self
.helo_resp
:
246 (code
,resp
)=self
.mail(from_addr
)
249 raise SMTPSenderRefused
251 for each
in to_addrs
:
252 (code
,resp
)=self
.rcpt(each
)
253 if (code
<> 250) and (code
<> 251):
254 senderrs
[each
]=(code
,resp
)
255 if len(senderrs
)==len(to_addrs
):
256 #th' server refused all our recipients
258 raise SMTPRecipientsRefused
263 #if we got here then somebody got our mail
268 """Close the connection to the SMTP server."""