Example Python app to send email through M365 using OAuth

Register or select an AzureAD application

In order to send email via OAuth, you must use an AzureAD registered application. You are welcome to use the following application, but there are no guarantees it will exist forever. 

Application (client) ID: b11940b4-4aed-4521-bacb-ae652a123eff
  Directory (tenant) ID: 995b0936-48d6-40e5-a31e-bf689ec9446f

If you prefer to create your own application, here are the steps used to create the above application:

  • Browse to the Microsoft Application Registration Portal at https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade
  • Log in with your Dartmouth account if prompted
  • Create a new application by clicking "+ New registration"
  • Fill in the following
    • Name: send-email (or any appropriate name for you app)
    • Check Accounts in this organizational directory only (Dartmouth College only - Single tenant)
    • Leave Redirect URI (optional) blank
  • Click "Register"
  • Note the "Application (client) ID"
  • Note the "Directory (tenant) ID"
  • On the left, expand "Manage" and then click "API permissions"
  • Click "..." on the right of the existing permission (User.Read) and click "Remove all permissions"
  • Confirm by clicking "Yes, remove"
  • On the left, under "Manage" and click "Branding & properties"
  • On the "Publisher domain" row, on the right, click "Update domain"
  • In the pull down menu, select "dartmouth.edu", and then click "Save"
  • On the left, under "Manage" click "Authentication (Preview)"
  • In the middle section, click the "Settings" sub tab
  • Toggle the "Allow public client flows" button to "On" and then click "Save"

Uploaded Image (Thumbnail)

Create a Python app

The below code is an example to be adapted.

import os
import msal

CLIENT_ID = 'b11940b4-4aed-4521-bacb-ae652a123eff'
TENANT_ID = '995b0936-48d6-40e5-a31e-bf689ec9446f'
AUTHORITY = f'https://login.microsoftonline.com/{TENANT_ID}'
SCOPES = ['https://graph.microsoft.com/Mail.Send']
CACHE_FILE = 'token_cache.bin'

MAIL_TO='elijah.w.gagne@dartmouth.edu'
MAIL_SUBJECT='Test sent via OAuth'
MAIL_BODY='''
Hi Recipient,

This is an example email sent from an example application.

Kind regards,
Sender
'''

cache = msal.SerializableTokenCache()
if os.path.exists(CACHE_FILE):
  cache.deserialize(open(CACHE_FILE, "r").read())

app = msal.PublicClientApplication(
  CLIENT_ID,
  authority=AUTHORITY,
  token_cache=cache
)

def save_cache():
  if cache.has_state_changed:
    with open(CACHE_FILE, "w") as f:
      f.write(cache.serialize())

def acquire_token():
  accounts = app.get_accounts()
  if accounts:
    result = app.acquire_token_silent(SCOPES, account=accounts[0])
    if result:
      return result
  flow = app.initiate_device_flow(scopes=SCOPES)
  if 'user_code' not in flow:
    raise ValueError("Failed to create device flow")
  print(flow['message'])
  result = app.acquire_token_by_device_flow(flow)
  save_cache()
  return result

def send_mail(access_token):
  import requests
  endpoint = 'https://graph.microsoft.com/v1.0/me/sendMail'
  email_msg = {
    "message": {
      "subject": MAIL_SUBJECT,
      "body": {
        "contentType": "Text",
        "content": MAIL_BODY
      },
      "toRecipients": [
        {"emailAddress": {"address": MAIL_TO}}
      ]
    },
    "saveToSentItems": "true"
  }
  headers = {
    'Authorization': 'Bearer ' + access_token,
    'Content-Type': 'application/json'
  }
  response = requests.post(endpoint, json=email_msg, headers=headers)
  if response.status_code == 202:
    print("Email sent successfully!")
  else:
    print("Failed to send email:", response.status_code, response.text)

def main():
  result = acquire_token()
  if 'access_token' in result:
    send_mail(result['access_token'])
  else:
    print("Authentication failed:", result.get("error"), result.get("error_description"))

if __name__ == '__main__':
  main()

The first time you run this Python code, it will direct you to open a web browser to https://microsoft.com/devicelogin and enter a code. We recommend that you use a private browser window so that you can control which user you authenticate as.

Uploaded Image (Thumbnail)

Uploaded Image (Thumbnail)

You will be asked to accept granting permissions necessary to send emails as the authenticated user. 

Uploaded Image (Thumbnail)

After accepting the request, your Python code should send the email and finish running. Additionally, a token_cache.bin file was generated with an access token and a refresh token. This will allow your Python code to run without needing to request permissions or reauthorize the access. 

IMPORTANT: The token_cache.bin file contains sensitive credentials that allows anyone with access to it to send emails as the account this was setup using. Protect this file appropriately by restricting access to it.