30

I am trying to send an email from my office365 corporate account using python. I am new to python. This code previously worked when using my hotmail account, however now that I have a need to send confidential information, I must use my corporate email.

I have tried a couple things.

  • Verified that my username and password is correct.
  • Used both python2 and python3. Both give the same error: 535 5.7.3 Authentication unsuccessful
  • I previously was using mailserver.starttls() when I got the above error, then after some research, I tried to pass a
    certificate.mailserver.starttls(certfile='office365.cer')

I am unclear on the certificate part, but my steps include, looking online to find out how to export a certificate. Using chrome browser, microsoftonline.com has a chain certificate. I can export the root and the level just below the root but not the last level. I dont know how to pass both of these files, so I have simply passed the root certificate. At this point I get the error: ssl.SSLError: [SSL] PEM lib (_ssl.c:3309)

i got stuck at this point. Any help is appreciated. Code used below

import smtplib

mailserver = smtplib.SMTP('smtp.office365.com',587)
mailserver.ehlo()
mailserver.starttls(certfile='office365.cer')
mailserver.ehlo()
mailserver.login('user@company.co', 'password')
mailserver.sendmail('user@company.co','user@company.co','python email')
mailserver.quit()
Gal Silberman
  • 3,756
  • 4
  • 31
  • 58
TKerr
  • 485
  • 1
  • 4
  • 7

6 Answers6

48

Well, you are almost there. The following code will do the trick:

import smtplib

mailserver = smtplib.SMTP('smtp.office365.com',587)
mailserver.ehlo()
mailserver.starttls()
mailserver.login('user@company.co', 'password')
#Adding a newline before the body text fixes the missing message body
mailserver.sendmail('user@company.co','user@company.co','\npython email')
mailserver.quit()

Use the following links for more information:

Python: Send Email via Office 365

https://docs.python.org/3/library/smtplib.html

https://gist.github.com/jasonjoh/3ec367594c3fa662ee983a617bdc7deb

Nuno André
  • 4,739
  • 1
  • 33
  • 46
Gal Silberman
  • 3,756
  • 4
  • 31
  • 58
  • 6
    NOTICE: the use of smtplib.SMTP() instead of smtplib.SMTP_SSL() ! With smtplib.SMTP_SSL() you will still get the "[SSL: WRONG_VERSION_NUMBER]" error. replacing it with smtplib.SMTP() worked for me. – Jan Wilmans Jan 14 '19 at 13:32
  • the row `mailserver.ehlo()` is not necessary. From `starttls()` documentation: "If there has been no previous EHLO or HELO command this session, this method tries ESMTP EHLO first." – tomatoeshift May 29 '20 at 04:36
  • 4
    Perhaps this is obvious, but if your corporate account requires multi-factor authentication, username and pw are inadequate to authenticate. – zhark Jul 07 '20 at 17:12
  • Hi, I have a strange issue, the above sequence of code works perfectly when I am executing it on the Python interpreter terminal. However, if I put the code into a .PY file and run it on VS Code or the Mac terminal, it hangs! :( Any pointers, please? – Deepak V Oct 20 '21 at 06:17
  • @DeepakV It is probably something else in your code. This is just a fix for the original question. You should post a new thread with your debuging results. – Gal Silberman Oct 20 '21 at 06:21
  • No @GalSilberman I promise, it is the same code copy-pasted into a .py file :( That is why I am splitting my hair. When I run it as a python program, the code is stuck at the following place: `mailserver = smtplib.SMTP('smtp.office365.com',587)` the terminal continues to put out a blank line and the execution waits... – Deepak V Oct 20 '21 at 06:27
  • Since this is the first line, do you have any pointers as to if I can enable some option in Python like `python3 -debug send_email.py` to print what is happening when I call `mailserver = smtplib.SMTP('smtp.office365.com',587)` ? – Deepak V Oct 20 '21 at 06:31
  • It has been a few years since the original answer. Things might have changed in the package. Try to follow the examples from https://docs.python.org/3/library/smtplib.html on how to use the package. – Gal Silberman Oct 20 '21 at 06:35
  • It got interesting, I found that a Python program can be debugged using `python3 -m pdb ` I did that and did evaluation step by step, then it is working if I step inside the function `smtplib.SMTP('smtp.office365.com',587)`, however, if I use the command 'n' to step over the function, it still has the same behavior - it gets stuck. – Deepak V Oct 20 '21 at 06:49
15

I found a library that it's working for me:

https://github.com/O365/python-o365

https://pypi.python.org/pypi/O365

Install it using PIP and then:

from O365 import Message
o365_auth = ('YourAccount@office365.com','YourPassword')
m = Message(auth=o365_auth)
m.setRecipients('reciving@office365.com')
m.setSubject('I made an email script.')
m.setBody('Talk to the computer, cause the human does not want to hear it any more.')
m.sendMessage()
micycle
  • 3,328
  • 14
  • 33
Nacho Parra
  • 159
  • 1
  • 4
  • 4
    I just want to point out for anyone seeing this in the future; while this library works, it does not use SMTP. It uses the Microsoft Outlook/Graph API in order to access your Office 365 account and send emails. See Gal's answer below if you want to use SMTP. – BigGerman Aug 10 '18 at 12:29
  • 1
    @BigGerman for us mere mortals, can you explain the difference, what will the use of this code do differently from the above? – Umar.H Jun 05 '19 at 09:55
  • 6
    @Datanovice [SMTP](https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol) is a protocol for sending emails, with smtplib you are sending the email directly to the Office365 mail server using the SMTP protocol. With nacho-parra's answer you are using a Python module (O365) which uses sends an HTTP request to the Microsoft Graph API which then sends the email. It's basically same result (email gets sent) but different methods. The original question was asking about sending using SMTP so gal-silberman's answer is the one that actually shows how to accomplish that. – BigGerman Jun 05 '19 at 15:19
  • @BigGerman you are the man, very succinct and to the point edition, cheers. I wonder which is more expensive resource wise, or if the api has a limit on emails. – Umar.H Jun 05 '19 at 17:25
  • 2
    @Datanovice SMTP is definitely simpler for sending a basic email, the graph API allows you to do a lot more besides just send email though. As far as rate limits for the API, they are pretty generous but they do have some [throttling](https://learn.microsoft.com/en-us/graph/throttling). Only time I ran into throttling was when retrieving like 10,000+ messages from an email inbox. SMTP servers can also have their own limits set by the provider though, see [here](https://support.microsoft.com/en-us/help/4458479/improvements-in-smtp-authenticated-submission-client-protocol). – BigGerman Jun 05 '19 at 20:05
  • @BigGerman that is very relevant for my use case, I'm looking at a way to automate compliance via a few python scripts and email this out to my store managers, around 450 in total. I think I'll be fine with the limits. – Umar.H Jun 07 '19 at 01:53
8

For me the answer provided by @Prometheus was giving "RuntimeError: No auth token found . Authentication Flow needed" as mentioned by a comment. It maybe because of my company email with 2fa enabled. So I had to follow steps provided in https://github.com/janscas/pyo365#authentication and https://pypi.org/project/O365/#authentication

To work with oauth you first need to register your application at Microsoft Application Registration Portal.

  1. Login at Microsoft Application Registration Portal
  2. Create an app, note your app id (client_id)
  3. Generate a new password (client_secret) under "Application Secrets" section Under the "Platform" section, add a new Web platform and set https://login.microsoftonline.com/common/oauth2/nativeclient" as the redirect URL
  4. Goto API permissions > Add permission > Microsoft Graph > Delegated permissions, add the permissions below:
    • IMAP.AccessAsUser.All

    • Mail.Send

    • POP.AccessAsUser.All

    • User.Read

    • SMTP.Send

    • offline_access # If you want refresh token to be avaialble in o365_token.txt. Otherwise you need to get access token every 1hr. enter image description here

  5. Run below python script to obtain access token which will be stored in file called o365_token.txt in current directory
from O365 import Account

scopes =  ["IMAP.AccessAsUser.All", "POP.AccessAsUser.All", "SMTP.Send", "Mail.Send", "offline_access"]

account = Account(credentials=('client_id_of_azureapp', 'client_secret_of_azureapp'))
result = account.authenticate(scopes=scopes)  # request a token for this scopes
  1. Use below script to send email by providing the authtoken file generated in previous step.
from O365 import Account
from O365.utils.token import FileSystemTokenBackend
tk = FileSystemTokenBackend(token_path=".", token_filename="o365_token.txt")

credentials = ('client_id', 'client_secret')

account = Account(credentials, auth_flow_type = 'public',token_backend=tk)
m = account.new_message()
m.to.add('user@company.com')
m.subject = 'Testing!'
m.body = "George Best quote: I've stopped drinking, but only while I'm asleep."
m.send()

Note: If you have admin consent or you are administrator or azure account you can skip the step 4,5,6 and use below code directly.

from O365 import Account
from O365.utils.token import FileSystemTokenBackend
tk = FileSystemTokenBackend(token_path=".", token_filename="o365_token.txt")

credentials = ('client_id', 'client_secret') # from step 2,3
account = Account(credentials, auth_flow_type = 'credentials', tenant_id="your_app_tenant_id") # tenant_id (required) available just below client_id in azure

if account.authenticate():
    print('Authenticated!')

mailbox = account.mailbox("user@company.com") # Your email (required) from which you want to send email (your app should have permission to this email)
m = mailbox.new_message()
m.to.add('touser@company.com')
m.subject = 'Testing!'
m.body = "George Best quote: I've stopped drinking, but only while I'm asleep."
m.send()
A Yashwanth
  • 318
  • 4
  • 12
3

The code has slightly changed. The above code won't work. Please use the below code. Reference

from O365 import Account

credentials = ('client_id', 'client_secret')

account = Account(credentials)
m = account.new_message()
m.to.add('to_example@example.com')
m.subject = 'Testing!'
m.body = "George Best quote: I've stopped drinking, but only while I'm asleep."
m.send()
Prometheus
  • 1,148
  • 14
  • 21
3

Most likely, the problem is not in your code, but in the Exchange Online configuration.

I bet 535 5.7.3 Authentication unsuccessful is thrown because authenticated SMTP (SMTP AUTH protocol) is disabled in your Exchange Online organization.

Here you can find my answer explaining how you can enable SMTP AUTH for the user you are sending emails from. You have to be an Office 365 org admin to do that, or you can ask your administrator for help.

After that mailserver.starttls() should work. Notice that you don't need to specify a certificate in that call.

wombatonfire
  • 4,585
  • 28
  • 36
1

The following is a more concise alternative to @Gal's answer that worked for me:

    msg = MIMEText(f'Pubblicati i risultati per la ricerca: `{search_result}`\n\nPer maggiori dettagli, visita: {url}')  # replace with your email message
    msg['Subject'] = f"Pubblicazione criteri di valutazione {search_result.split(' ')[2]}"
    msg['From'] = from_email
    msg['To'] = to_email
    with smtplib.SMTP('smtp.office365.com', 587) as smtp:
        smtp.starttls()
        smtp.login(from_email, password)
        smtp.send_message(msg)
Luca Clissa
  • 810
  • 2
  • 7
  • 27