Python SMTP 'unable to get local issuer certificate' in python virtualenv

Question:

Sorry if title is unclear.

I’m trying to send an email through python 3.10 through the standard library. It works locally on my machine with these default settings.

smtp_server = "smtp.office365.com"
port = 587  # For starttls
sender_email = os.environ.get("EMAIL")
password = os.environ.get("EMAIL_PASSWORD")

# Create a secure SSL context
context = ssl.create_default_context()
server = smtplib.SMTP(smtp_server,port)

try:
   server.starttls(context=context) # Secure the connection
   server.login(sender_email, password)
except Exception as e:
   print(e)

However when I run this code manually from my company’s server, I get a "unable to get local issuer certificate" error.

I’ve been able to remedy the issue by setting the ssl context to unverified:

context = ssl._create_unverified_context() # was ssl.create_default_context()

And this works when running the python file manually. However, this needs to run as a cronjob, and when the crontab runs the script with this ‘fix’ I get a different error.

Authentication unsuccessful, the user credentials were incorrect.
Which is ridiculous, because the same credentials worked with a different ssl context.

I’m using pythons virtualenv to run the script. I don’t know any networking or certificate specific things to the company ubuntu server, but there’s clearly something up. I just don’t know what specifically.

Yes, I have looked through the multitude of similar questions, though none of them seem to quite fit this set of circumstances to help me.

Thanks in advance.

Asked By: Fresh

||

Answers:

You are seeing two separate issues. TLS/certificate issues, and then an authentication issue which most likely has nothing to do with TLS.

There is no need for you to use unverified TLS. You can get the catrust from mozilla via the certifi package. Not sure if microsoft uses browser trust to sign their certificates.

>>> import certifi, ssl
>>> context = ssl.create_default_context()
>>> context.load_verify_locations(certifi.where())

That being said, a common mistake people make is assuming that cron jobs will load the same shell profile files that a login does. I would add error checking/debug loggin to make sure EMAIL_PASSWORD and EMAIL are set according to your expectations, since the error message is most likely coming from server.login line which has nothing to do with TLS.

Answered By: toppk
Categories: questions Tags: , , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.