Googling the title of this post results in quite a few hits. Suffering from the same problem long running frameworks like Spring have, you are not only trying to sift through right and wrong answers, but out of date or differing versioning answers.

I had been using port 25 to send plaintext status emails for an elastic cluster, but was asked to TLS the email. This server was running port 587 for TLS connections. Seemed simple enough.

Change

s = smtplib.SMTP(host=host, port=25)
s.sendmail(...)

to

s = smtplib.SMTP_SSL(host=host, port=587, keyfile=key, certfile=cert)
s.sendmail(...)

Uh oh, using this code results in an opaque SSL error saying the wrong protocol is being used...Why?

I was perplexed for a good long while, until I came across this article.

SMTP was originally designed for message transfer. Message transfer through SMTP occurs between different servers that are not designed for direct client interaction. For this reason it was not necessary in the early design to account for the problems with mail clients like transmitting user password information in cleartext.

It was only later that SMTP began to be used for message submission as well as message transfer. In the early days of email clients set up to send using SMTP, these used port 25, the same port that was used inside mail systems themselves for transfer. In larger mail transfer systems, it was possible to lock down port 25 so it could only be used by trusted IP addresses. Of course, it was not possible to do this for the many thousands of home IP addresses using SMTP on their email clients for email submission, and so port 587 was defined for message submission.

Using port 587 instead of 25 for message submission became popular at around the same time that the importance of using encryption to protect sensitive data became well known and encryption extensions were being defined for SMTP. Port 587, defined specifically for message submission, supported upgrading to a secure connection with STARTTLS.

Port 465 was also defined for SMTP submission, and unlike port 587, 465 specifically supported implicit TLS just like port 993 for IMAP and 995 for POP. At this time, however, the industry had moved on to the expectation that all connections for IMAP, POP, and SMTP would be upgraded securely using STARTTLS instead of the preferred implicit TLS today. For this reason, shortly after port 465 was defined, it was revoked. All clients were expected to move over to use STARTTLS on port 587.

Executive summary: if your smtp server is using port 587 instead of 465 it is most likely expecting you to connect plaintext and then "upgrade" your connection to TLS.

So in python this now looks like this:

s = smtplib.SMTP(host=host, port=587)
s.starttls(keyfile=key, certfile=cert)
s.sendmail(...)