2 """SMTP/ESMTP client class.
4 Author: The Dragon De Monsyne <dragondm@integral.org>
5 ESMTP support, test code and doc fixes added by
6 Eric S. Raymond <esr@thyrsus.com>
7 Better RFC 821 compliance (MAIL and RCPT, and CRLF in data)
8 by Carey Evans <c.evans@clear.net.nz>, for picky mail servers.
10 (This was modified from the Python 1.5 library HTTP lib.)
12 This should follow RFC 821 (SMTP) and RFC 1869 (ESMTP).
16 Please remember, when doing ESMTP, that the names of the SMTP service
17 extensions are NOT the same thing as the option keyords for the RCPT
23 >>> s=smtplib.SMTP("localhost")
25 This is Sendmail version 8.8.4
27 HELO EHLO MAIL RCPT DATA
28 RSET NOOP QUIT HELP VRFY
30 For more info use "HELP <topic>".
31 To report bugs in the implementation send email to
32 sendmail-bugs@sendmail.org.
33 For local information send email to Postmaster at your site.
35 >>> s.putcmd("vrfy","someone@here")
37 (250, "Somebody OverHere <somebody@here.my.org>")
51 SMTPServerDisconnected
="Server not connected"
52 SMTPSenderRefused
="Sender address refused"
53 SMTPRecipientsRefused
="All Recipients refused"
54 SMTPDataError
="Error transmitting message data"
57 """Quote a subset of the email addresses defined by RFC 821.
59 Should be able to handle anything rfc822.parseaddr can handle."""
63 m
=rfc822
.parseaddr(addr
)[1]
64 except AttributeError:
67 #something weird here.. punt -ddm
73 """Quote data for email.
75 Double leading '.', and change Unix newline '\n', or Mac '\r' into
76 Internet CRLF end-of-line."""
77 return re
.sub(r
'(?m)^\.', '..',
78 re
.sub(r
'(?:\r\n|\n|\r(?!\n))', CRLF
, data
))
81 """This class manages a connection to an SMTP or ESMTP server.
83 SMTP objects have the following attributes:
85 This is the message given by the server in responce to the
86 most recent HELO command.
89 This is the message given by the server in responce to the
90 most recent EHLO command. This is usually multiline.
93 This is a True value _after you do an EHLO command_, if the
94 server supports ESMTP.
97 This is a dictionary, which, if the server supports ESMTP,
98 will _after you do an EHLO command_, contain the names of the
99 SMTP service extentions this server supports, and their
101 Note, all extention names are mapped to lower case in the
104 For method docs, see each method's docstrings. In general, there is
105 a method of the same name to preform each SMTP comand, and there
106 is a method called 'sendmail' that will do an entiere mail
115 def __init__(self
, host
= '', port
= 0):
116 """Initialize a new instance.
118 If specified, `host' is the name of the remote host to which
119 to connect. If specified, `port' specifies the port to which
120 to connect. By default, smtplib.SMTP_PORT is used.
123 self
.esmtp_features
= {}
124 if host
: self
.connect(host
, port
)
126 def set_debuglevel(self
, debuglevel
):
127 """Set the debug output level.
129 A non-false value results in debug messages for connection and
130 for all messages sent to and received from the server.
133 self
.debuglevel
= debuglevel
135 def connect(self
, host
='localhost', port
= 0):
136 """Connect to a host on a given port.
138 If the hostname ends with a colon (`:') followed by a number,
139 and there is no port specified, that suffix will be stripped
140 off and the number interpreted as the port number to use.
142 Note: This method is automatically invoked by __init__,
143 if a host is specified during instantiation.
147 i
= string
.find(host
, ':')
149 host
, port
= host
[:i
], host
[i
+1:]
150 try: port
= string
.atoi(port
)
151 except string
.atoi_error
:
152 raise socket
.error
, "nonnumeric port"
153 if not port
: port
= SMTP_PORT
154 self
.sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
155 if self
.debuglevel
> 0: print 'connect:', (host
, port
)
156 self
.sock
.connect(host
, port
)
157 (code
,msg
)=self
.getreply()
158 if self
.debuglevel
>0 : print "connect:", msg
162 """Send `str' to the server."""
163 if self
.debuglevel
> 0: print 'send:', `
str`
168 raise SMTPServerDisconnected
170 raise SMTPServerDisconnected
172 def putcmd(self
, cmd
, args
=""):
173 """Send a command to the server.
175 str = '%s %s%s' % (cmd
, args
, CRLF
)
179 """Get a reply from the server.
181 Returns a tuple consisting of:
182 - server response code (e.g. '250', or such, if all goes well)
183 Note: returns -1 if it can't read response code.
184 - server response string corresponding to response code
185 (note : multiline responses converted to a single,
189 self
.file = self
.sock
.makefile('rb')
191 line
= self
.file.readline()
192 if self
.debuglevel
> 0: print 'reply:', `line`
193 resp
.append(string
.strip(line
[4:]))
195 #check if multiline resp
199 errcode
= string
.atoi(code
)
203 errmsg
= string
.join(resp
,"\n")
204 if self
.debuglevel
> 0:
205 print 'reply: retcode (%s); Msg: %s' % (errcode
,errmsg
)
206 return errcode
, errmsg
208 def docmd(self
, cmd
, args
=""):
209 """ Send a command, and return its response code """
211 self
.putcmd(cmd
,args
)
212 (code
,msg
)=self
.getreply()
216 def helo(self
, name
=''):
217 """ SMTP 'helo' command. Hostname to send for this command
218 defaults to the FQDN of the local host """
219 name
=string
.strip(name
)
221 name
=socket
.gethostbyaddr(socket
.gethostname())[0]
222 self
.putcmd("helo",name
)
223 (code
,msg
)=self
.getreply()
227 def ehlo(self
, name
=''):
228 """ SMTP 'ehlo' command. Hostname to send for this command
229 defaults to the FQDN of the local host. """
230 name
=string
.strip(name
)
232 name
=socket
.gethostbyaddr(socket
.gethostname())[0]
233 self
.putcmd("ehlo",name
)
234 (code
,msg
)=self
.getreply()
235 # According to RFC1869 some (badly written)
236 # MTA's will disconnect on an ehlo. Toss an exception if
238 if code
== -1 and len(msg
) == 0:
239 raise SMTPServerDisconnected
244 #parse the ehlo responce -ddm
245 resp
=string
.split(self
.ehlo_resp
,'\n')
248 m
=re
.match(r
'(?P<feature>[A-Za-z0-9][A-Za-z0-9\-]*)',each
)
250 feature
=string
.lower(m
.group("feature"))
251 params
=string
.strip(m
.string
[m
.end("feature"):])
252 self
.esmtp_features
[feature
]=params
255 def has_extn(self
, opt
):
256 """Does the server support a given SMTP service extension?"""
257 return self
.esmtp_features
.has_key(string
.lower(opt
))
259 def help(self
, args
=''):
260 """ SMTP 'help' command. Returns help text from server """
261 self
.putcmd("help", args
)
262 (code
,msg
)=self
.getreply()
266 """ SMTP 'rset' command. Resets session. """
267 code
=self
.docmd("rset")
271 """ SMTP 'noop' command. Doesn't do anything :> """
272 code
=self
.docmd("noop")
275 def mail(self
,sender
,options
=[]):
276 """ SMTP 'mail' command. Begins mail xfer session. """
278 if options
and self
.does_esmtp
:
279 optionlist
= string
.join(options
, ' ')
280 self
.putcmd("mail", "FROM:%s %s" % (quoteaddr(sender
) ,optionlist
))
281 return self
.getreply()
283 def rcpt(self
,recip
,options
=[]):
284 """ SMTP 'rcpt' command. Indicates 1 recipient for this mail. """
286 if options
and self
.does_esmtp
:
287 optionlist
= string
.join(options
, ' ')
288 self
.putcmd("rcpt","TO:%s %s" % (quoteaddr(recip
),optionlist
))
289 return self
.getreply()
292 """ SMTP 'DATA' command. Sends message data to server.
293 Automatically quotes lines beginning with a period per rfc821. """
295 (code
,repl
)=self
.getreply()
296 if self
.debuglevel
>0 : print "data:", (code
,repl
)
300 self
.send(quotedata(msg
))
301 self
.send("%s.%s" % (CRLF
, CRLF
))
302 (code
,msg
)=self
.getreply()
303 if self
.debuglevel
>0 : print "data:", (code
,msg
)
306 def vrfy(self
, address
):
307 return self
.verify(address
)
309 def verify(self
, address
):
310 """ SMTP 'verify' command. Checks for address validity. """
311 self
.putcmd("vrfy", quoteaddr(address
))
312 return self
.getreply()
314 def expn(self
, address
):
315 """ SMTP 'verify' command. Checks for address validity. """
316 self
.putcmd("expn", quoteaddr(address
))
317 return self
.getreply()
321 def sendmail(self
, from_addr
, to_addrs
, msg
, mail_options
=[],
323 """ This command performs an entire mail transaction.
325 - from_addr : The address sending this mail.
326 - to_addrs : a list of addresses to send this mail to
327 (a string will be treated as a list with 1 address)
328 - msg : the message to send.
329 - mail_options : list of ESMTP options (such as 8bitmime)
331 - rcpt_options : List of ESMTP options (such as DSN commands)
332 for all the rcpt commands
333 If there has been no previous EHLO or HELO command this session,
334 this method tries ESMTP EHLO first. If the server does ESMTP, message
335 size and each of the specified options will be passed to it.
336 If EHLO fails, HELO will be tried and ESMTP options suppressed.
338 This method will return normally if the mail is accepted for at least
339 one recipient. Otherwise it will throw an exception (either
340 SMTPSenderRefused, SMTPRecipientsRefused, or SMTPDataError)
341 That is, if this method does not throw an exception, then someone
342 should get your mail. If this method does not throw an exception,
343 it returns a dictionary, with one entry for each recipient that was
349 >>> s=smtplib.SMTP("localhost")
350 >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]
353 ... Subject: testin'...
355 ... This is a test '''
356 >>> s.sendmail("me@my.org",tolist,msg)
357 { "three@three.org" : ( 550 ,"User unknown" ) }
360 In the above example, the message was accepted for delivery to
361 three of the four addresses, and one was rejected, with the error
362 code 550. If all addresses are accepted, then the method
363 will return an empty dictionary.
365 if not self
.helo_resp
and not self
.ehlo_resp
:
366 if self
.ehlo() >= 400:
370 # Hmmm? what's this? -ddm
371 # self.esmtp_features['7bit']=""
372 if self
.has_extn('size'):
373 esmtp_opts
.append("size=" + `
len(msg
)`
)
374 for option
in mail_options
:
375 esmtp_opts
.append(option
)
377 (code
,resp
) = self
.mail(from_addr
, esmtp_opts
)
380 raise SMTPSenderRefused
382 if type(to_addrs
) == types
.StringType
:
383 to_addrs
= [to_addrs
]
384 for each
in to_addrs
:
385 (code
,resp
)=self
.rcpt(each
, rcpt_options
)
386 if (code
<> 250) and (code
<> 251):
387 senderrs
[each
]=(code
,resp
)
388 if len(senderrs
)==len(to_addrs
):
389 # the server refused all our recipients
391 raise SMTPRecipientsRefused
396 #if we got here then somebody got our mail
401 """Close the connection to the SMTP server."""
411 """Terminate the SMTP session."""
415 # Test the sendmail method, which tests most of the others.
416 # Note: This always sends to localhost.
417 if __name__
== '__main__':
421 sys
.stdout
.write(prompt
+ ": ")
422 return string
.strip(sys
.stdin
.readline())
424 fromaddr
= prompt("From")
425 toaddrs
= string
.splitfields(prompt("To"), ',')
426 print "Enter message, end with ^D:"
429 line
= sys
.stdin
.readline()
433 print "Message length is " + `
len(msg
)`
435 server
= SMTP('localhost')
436 server
.set_debuglevel(1)
437 server
.sendmail(fromaddr
, toaddrs
, msg
)