3 '''SMTP/ESMTP client class.
5 This should follow RFC 821 (SMTP), RFC 1869 (ESMTP), RFC 2554 (SMTP
6 Authentication) and RFC 2487 (Secure SMTP over TLS).
10 Please remember, when doing ESMTP, that the names of the SMTP service
11 extensions are NOT the same thing as the option keywords for the RCPT
17 >>> s=smtplib.SMTP("localhost")
19 This is Sendmail version 8.8.4
21 HELO EHLO MAIL RCPT DATA
22 RSET NOOP QUIT HELP VRFY
24 For more info use "HELP <topic>".
25 To report bugs in the implementation send email to
26 sendmail-bugs@sendmail.org.
27 For local information send email to Postmaster at your site.
29 >>> s.putcmd("vrfy","someone@here")
31 (250, "Somebody OverHere <somebody@here.my.org>")
51 __all__ = [
"SMTPException",
"SMTPServerDisconnected",
"SMTPResponseException",
52 "SMTPSenderRefused",
"SMTPRecipientsRefused",
"SMTPDataError",
53 "SMTPConnectError",
"SMTPHeloError",
"SMTPAuthenticationError",
54 "quoteaddr",
"quotedata",
"SMTP"]
61 """Base class for all exceptions raised by this module."""
64 """Not connected to any SMTP server.
66 This exception is raised when the server unexpectedly disconnects,
67 or when an attempt is made to use the SMTP instance before
68 connecting it to a server.
72 """Base class for all exceptions that include an SMTP error code.
74 These exceptions are generated in some instances when the SMTP
75 server returns an error code. The error code is stored in the
76 `smtp_code' attribute of the error, and the `smtp_error' attribute
77 is set to the error message.
86 """Sender address refused.
88 In addition to the attributes set by on all SMTPResponseException
89 exceptions, this sets `sender' to the string that the SMTP refused.
96 self.
args = (code, msg, sender)
99 """All recipient addresses refused.
101 The errors for each recipient are accessible through the attribute
102 'recipients', which is a dictionary of exactly the same sort as
103 SMTP.sendmail() returns.
112 """The SMTP server didn't accept the data."""
114 class SMTPConnectError(SMTPResponseException):
115 """Error during connection establishment."""
118 """The server refused our HELO reply."""
121 """Authentication error.
123 Most probably the server didn't accept the username/password
124 combination provided.
128 """A fake socket object that really wraps a SSLObject.
130 It only supports what is needed in smtplib.
137 self.sslobj.write(str)
141 self.realsock.close()
144 """A fake file like object that really wraps a SSLObject.
146 It only supports what is needed in smtplib.
155 chr = self.sslobj.read(1)
163 """Quote a subset of the email addresses defined by RFC 821.
165 Should be able to handle anything rfc822.parseaddr can handle.
170 except AttributeError:
179 """Quote data for email.
181 Double leading '.', and change Unix newline '\\n', or Mac '\\r' into
182 Internet CRLF end-of-line.
184 return re.sub(
r'(?m)^\.',
'..',
185 re.sub(
r'(?:\r\n|\n|\r(?!\n))', CRLF, data))
189 """This class manages a connection to an SMTP or ESMTP server.
191 SMTP objects have the following attributes:
193 This is the message given by the server in response to the
194 most recent HELO command.
197 This is the message given by the server in response to the
198 most recent EHLO command. This is usually multiline.
201 This is a True value _after you do an EHLO command_, if the
202 server supports ESMTP.
205 This is a dictionary, which, if the server supports ESMTP,
206 will _after you do an EHLO command_, contain the names of the
207 SMTP service extensions this server supports, and their
210 Note, all extension names are mapped to lower case in the
213 See each method's docstrings for details. In general, there is a
214 method of the same name to perform each SMTP command. There is also a
215 method called 'sendmail' that will do an entire mail transaction.
224 """Initialize a new instance.
226 If specified, `host' is the name of the remote host to which to
227 connect. If specified, `port' specifies the port to which to connect.
228 By default, smtplib.SMTP_PORT is used. An SMTPConnectError is raised
229 if the specified `host' doesn't respond correctly.
234 (code, msg) = self.
connect(host, port)
239 """Set the debug output level.
241 A non-false value results in debug messages for connection and for all
242 messages sent to and received from the server.
247 def connect(self, host='localhost', port = 0):
248 """Connect to a host on a given port.
250 If the hostname ends with a colon (`:') followed by a number, and
251 there is no port specified, that suffix will be stripped off and the
252 number interpreted as the port number to use.
254 Note: This method is automatically invoked by __init__, if a host is
255 specified during instantiation.
258 if not port
and (host.find(
':') == host.rfind(
':')):
261 host, port = host[:i], host[i+1:]
262 try: port = int(port)
264 raise socket.error,
"nonnumeric port"
265 if not port: port = SMTP_PORT
266 if self.
debuglevel > 0:
print 'connect:', (host, port)
267 msg =
"getaddrinfo returns an empty list"
269 for res
in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
270 af, socktype, proto, canonname, sa = res
273 if self.
debuglevel > 0:
print 'connect:', (host, port)
274 self.sock.connect(sa)
275 except socket.error, msg:
276 if self.
debuglevel > 0:
print 'connect fail:', (host, port)
283 raise socket.error, msg
289 """Send `str' to the server."""
293 self.sock.sendall(str)
301 """Send a command to the server."""
303 str =
'%s%s' % (cmd, CRLF)
305 str =
'%s %s%s' % (cmd, args, CRLF)
309 """Get a reply from the server.
311 Returns a tuple consisting of:
313 - server response code (e.g. '250', or such, if all goes well)
314 Note: returns -1 if it can't read response code.
316 - server response string corresponding to response code (multiline
317 responses are converted to a single, multiline string).
319 Raises SMTPServerDisconnected if end-of-file is reached.
322 if self.
file is None:
323 self.
file = self.sock.makefile(
'rb')
325 line = self.file.readline()
329 if self.
debuglevel > 0:
print 'reply:', `line`
330 resp.append(line[4:].
strip())
343 errmsg =
"\n".
join(resp)
345 print 'reply: retcode (%s); Msg: %s' % (errcode,errmsg)
346 return errcode, errmsg
349 """Send a command, and return its response code."""
355 """SMTP 'helo' command.
356 Hostname to send for this command defaults to the FQDN of the local
368 """ SMTP 'ehlo' command.
369 Hostname to send for this command defaults to the FQDN of the local
381 if code == -1
and len(msg) == 0:
389 resp=self.ehlo_resp.split(
'\n')
392 m=re.match(
r'(?P<feature>[A-Za-z0-9][A-Za-z0-9\-]*)',each)
394 feature=m.group(
"feature").
lower()
395 params=m.string[m.end(
"feature"):].
strip()
400 """Does the server support a given SMTP service extension?"""
401 return self.esmtp_features.has_key(opt.lower())
404 """SMTP 'help' command.
405 Returns help text from server."""
410 """SMTP 'rset' command -- resets session."""
411 return self.
docmd(
"rset")
414 """SMTP 'noop' command -- doesn't do anything :>"""
415 return self.
docmd(
"noop")
417 def mail(self,sender,options=[]):
418 """SMTP 'mail' command -- begins mail xfer session."""
421 optionlist =
' ' +
' '.
join(options)
425 def rcpt(self,recip,options=[]):
426 """SMTP 'rcpt' command -- indicates 1 recipient for this mail."""
429 optionlist =
' ' +
' '.
join(options)
434 """SMTP 'DATA' command -- sends message data to server.
436 Automatically quotes lines beginning with a period per rfc821.
437 Raises SMTPDataError if there is an unexpected reply to the
438 DATA command; the return value from this method is the final
439 response code received when the all data is sent.
443 if self.
debuglevel >0 :
print "data:", (code,repl)
453 if self.
debuglevel >0 :
print "data:", (code,msg)
457 """SMTP 'verify' command -- checks for address validity."""
464 """SMTP 'verify' command -- checks for address validity."""
471 """Log in on an SMTP server that requires authentication.
474 - user: The user name to authenticate with.
475 - password: The password for the authentication.
477 If there has been no previous EHLO or HELO command this session, this
478 method tries ESMTP EHLO first.
480 This method will return normally if the authentication was successful.
482 This method may raise the following exceptions:
484 SMTPHeloError The server didn't reply properly to
486 SMTPAuthenticationError The server didn't accept the username/
487 password combination.
488 SMTPException No suitable authentication method was
492 def encode_cram_md5(challenge, user, password):
497 def encode_plain(user, password):
499 (user, user, password))[:-1]
502 AUTH_CRAM_MD5 =
"CRAM-MD5"
505 if not (200 <= self.
ehlo()[0] <= 299):
506 (code, resp) = self.
helo()
507 if not (200 <= code <= 299):
511 raise SMTPException(
"SMTP AUTH extension not supported by server.")
519 preferred_auths = [AUTH_CRAM_MD5, AUTH_PLAIN]
524 for method
in preferred_auths:
525 if method
in authlist:
528 if self.
debuglevel > 0:
print "AuthMethod:", authmethod
530 if authmethod == AUTH_CRAM_MD5:
531 (code, resp) = self.
docmd(
"AUTH", AUTH_CRAM_MD5)
535 (code, resp) = self.
docmd(encode_cram_md5(resp, user, password))
536 elif authmethod == AUTH_PLAIN:
537 (code, resp) = self.
docmd(
"AUTH",
538 AUTH_PLAIN +
" " + encode_plain(user, password))
539 elif authmethod ==
None:
540 raise SMTPException(
"No suitable authentication method found.")
541 if code
not in [235, 503]:
547 def starttls(self, keyfile = None, certfile = None):
548 """Puts the connection to the SMTP server into TLS mode.
550 If the server supports TLS, this will encrypt the rest of the SMTP
551 session. If you provide the keyfile and certfile parameters,
552 the identity of the SMTP server and client can be checked. This,
553 however, depends on whether the socket module really checks the
556 (resp, reply) = self.
docmd(
"STARTTLS")
563 def sendmail(self, from_addr, to_addrs, msg, mail_options=[],
565 """This command performs an entire mail transaction.
568 - from_addr : The address sending this mail.
569 - to_addrs : A list of addresses to send this mail to. A bare
570 string will be treated as a list with 1 address.
571 - msg : The message to send.
572 - mail_options : List of ESMTP options (such as 8bitmime) for the
574 - rcpt_options : List of ESMTP options (such as DSN commands) for
575 all the rcpt commands.
577 If there has been no previous EHLO or HELO command this session, this
578 method tries ESMTP EHLO first. If the server does ESMTP, message size
579 and each of the specified options will be passed to it. If EHLO
580 fails, HELO will be tried and ESMTP options suppressed.
582 This method will return normally if the mail is accepted for at least
583 one recipient. It returns a dictionary, with one entry for each
584 recipient that was refused. Each entry contains a tuple of the SMTP
585 error code and the accompanying error message sent by the server.
587 This method may raise the following exceptions:
589 SMTPHeloError The server didn't reply properly to
591 SMTPRecipientsRefused The server rejected ALL recipients
593 SMTPSenderRefused The server didn't accept the from_addr.
594 SMTPDataError The server replied with an unexpected
595 error code (other than a refusal of
598 Note: the connection will be open even after an exception is raised.
603 >>> s=smtplib.SMTP("localhost")
604 >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]
607 ... Subject: testin'...
609 ... This is a test '''
610 >>> s.sendmail("me@my.org",tolist,msg)
611 { "three@three.org" : ( 550 ,"User unknown" ) }
614 In the above example, the message was accepted for delivery to three
615 of the four addresses, and one was rejected, with the error code
616 550. If all addresses are accepted, then the method will return an
621 if not (200 <= self.
ehlo()[0] <= 299):
622 (code,resp) = self.
helo()
623 if not (200 <= code <= 299):
630 esmtp_opts.append(
"size=" + `len(msg)`)
631 for option
in mail_options:
632 esmtp_opts.append(option)
634 (code,resp) = self.
mail(from_addr, esmtp_opts)
639 if isinstance(to_addrs, types.StringTypes):
640 to_addrs = [to_addrs]
641 for each
in to_addrs:
642 (code,resp)=self.
rcpt(each, rcpt_options)
643 if (code != 250)
and (code != 251):
644 senderrs[each]=(code,resp)
645 if len(senderrs)==len(to_addrs):
649 (code,resp) = self.
data(msg)
658 """Close the connection to the SMTP server."""
668 """Terminate the SMTP session."""
675 if __name__ ==
'__main__':
679 sys.stdout.write(prompt +
": ")
680 return sys.stdin.readline().
strip()
684 print "Enter message, end with ^D:"
687 line = sys.stdin.readline()
691 print "Message length is " + `len(msg)`
694 server.set_debuglevel(1)
695 server.sendmail(fromaddr, toaddrs, msg)