logging.handlers.SMTPHandler raises smtplib.SMTPAuthenticationError

5.1k views Asked by At

I tried this with Verizon and Gmail. Both servers denied authentication. Gmail emailed me that it denied a login attempt because the connection was not using "modern security".
I would like to know how I can use modern security with this logging handler.

logging.handlers.SMTPHandler(mailhost=('', 25),
                             fromaddr='',
                             toaddrs='',
                             subject='',
                             credentials=('username','password'),
                             secure=())
2

There are 2 answers

0
user193661 On BEST ANSWER

Gmail problem:

  • Not addressed here. See other answers about Gmail's app authentication.

Verizon problem:

  • Some mail submission servers may only accept SMTPS on port 465. See What is the difference between ports 465 and 587? for elaboration. The Verizon mail submission server smtp.verizon.net, is one such example.
  • SMTPHandler does not support SMTPS by default. You can monkeypatch the functionality.

Solution:

  • This forces any server to use SMTPS.
  • A more thorough fix would be to edit the class with a flag to enable SMTPS.

  1. Paste the edited emit function from below, into the relevant file.
  2. Then set it

    logging.handlers.SMTPHandler.emit = emit
    

 

The default /logging/handlers.py logging.handlers.SMTPHandler.emit function

# Class SMTPHandler...
def emit(self, record):
    """
    Emit a record.

    Format the record and send it to the specified addressees.
    """
    try:
        import smtplib
        from email.utils import formatdate
        port = self.mailport
        if not port:
            port = smtplib.SMTP_PORT
        smtp = smtplib.SMTP(self.mailhost, port, timeout=self._timeout)
        msg = self.format(record)
        msg = "From: %s\r\nTo: %s\r\nSubject: %s\r\nDate: %s\r\n\r\n%s" % (
                        self.fromaddr,
                        ",".join(self.toaddrs),
                        self.getSubject(record),
                        formatdate(), msg)
        if self.username:
            if self.secure is not None:
                smtp.ehlo()
                smtp.starttls(*self.secure)
                smtp.ehlo()
            smtp.login(self.username, self.password)
        smtp.sendmail(self.fromaddr, self.toaddrs, msg)
        smtp.quit()
    except (KeyboardInterrupt, SystemExit):
        raise
    except:
        self.handleError(record)

 

The edited emit function

def emit(self, record):
    """
    Overwrite the logging.handlers.SMTPHandler.emit function with SMTP_SSL.
    Emit a record.
    Format the record and send it to the specified addressees.
    """
    try:
        import smtplib
        from email.utils import formatdate
        port = self.mailport
        if not port:
            port = smtplib.SMTP_PORT
        smtp = smtplib.SMTP_SSL(self.mailhost, port, timeout=self._timeout)
        msg = self.format(record)
        msg = "From: %s\r\nTo: %s\r\nSubject: %s\r\nDate: %s\r\n\r\n%s" % (self.fromaddr, ", ".join(self.toaddrs), self.getSubject(record), formatdate(), msg)
        if self.username:
            smtp.ehlo()
            smtp.login(self.username, self.password)
        smtp.sendmail(self.fromaddr, self.toaddrs, msg)
        smtp.quit()
    except (KeyboardInterrupt, SystemExit):
        raise
    except:
        self.handleError(record)
2
eatonphil On

For anyone coming back to this, here is how I got the SMTPHandler working with Gmail:

eh = SMTPHandler(mailhost=('smtp.gmail.com', 587),
                fromaddr=from_addr,
                toaddrs=to_addrs,
                subject=subject,
                credentials=(username, password),
                secure=())

The to_addrs variable is a string in my example. I am not sure if it can be an array or is supposed to be a space or comma-delimited string. The username variable includes the domain, like this: [email protected].

Most guides said to use port 465, here is the difference if you are curious. However, when I tried to use port 465, I got a SMTP timeout error:

Traceback (most recent call last):
File "/usr/local/lib/python3.5/smtplib.py", line 386, in getreply
  line = self.file.readline(_MAXLINE + 1)
File "/usr/local/lib/python3.5/socket.py", line 571, in readinto
  return self._sock.recv_into(b)
socket.timeout: timed out

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "/usr/local/lib/python3.5/logging/handlers.py", line 972, in emit
  smtp = smtplib.SMTP(self.mailhost, port, timeout=self.timeout)
File "/usr/local/lib/python3.5/smtplib.py", line 251, in __init__
  (code, msg) = self.connect(host, port)
File "/usr/local/lib/python3.5/smtplib.py", line 337, in connect
  (code, msg) = self.getreply()
File "/usr/local/lib/python3.5/smtplib.py", line 390, in getreply
  + str(e))
smtplib.SMTPServerDisconnected: Connection unexpectedly closed: timed out

I switched to port 587 and Google successfully authenticated. The messages were sent.