简体   繁体   中英

How to send python email.message.EmailMessage with Microsoft Graph API

Our web application sends nicely formatted messages (including some embedded images) through python's smtplib. The messages are generated with python's email.message.EmailMessage class, as shown in the code below.

At the start of October, Microsoft deprecated support for Basic Auth in their Office365 Email accounts. We had been using Basic Auth and now needed to find a new solution. After struggling with getting OAuth2 working for some time , we decided to refactor and use the Microsoft Graph API instead.

Oddly, even though most of the examples on the Graph API site includes multi-lingual (HTTP / C# / Javascript / PHP etc) examples, the one for sending an email with a MIME format (Examlpe 4) only has an HTTP example.

We would like to know if it's possible to send the email we had built using python.email.EmailMessage using the Graph API, and if yes, how to do it.

Below is some example code, showing what we did before and what we're trying to get right now.

when we run the code, we get the error

'{"error":{"code":"RequestBodyRead","message":"Requested value \'text/plain\' was not found."}}'

import smtplib
from email.utils import formatdate
from email.message import EmailMessage
import requests


server = 'smtp.office365.com'  # for exampler
port = 587  # for example
from_mail = 'levrai@ninjane.er'
to_mail = 'desti@nati.on'
subject = 'Demo sending the old way!'
password = 'not_so_Secur3!'

message_parts = ['Hi sir', 'This is a demo message.', 'It could help others to help me, and possbily others too.']

# the below function builds up the nice message based on an html template
text_msg, html_msg, cids, locs = doc_mail_from_template(message_parts)  

msg = EmailMessage()
msg.set_content(text_message)
msg.add_alternative(html_msg, subtype='html')
msg['From'] = from_mail
msg['To'] = to_mail
msg['Date'] = formatdate(localtime=True)
msg['Subject'] = subject

# now embed images to the email
for loc, cid in zip(locs, cids):
    with open(loc, 'rb') as img:
        maintype, subtype = guess_type(img.name)[0].split('/')  # know the Content-Type of the image
        msg.get_payload()[1].add_related(img.read(), maintype=maintype, subtype=subtype, cid=cid) # attach it


if date_now < '2022-10-01':   # before, we could do this
    with smtplib.SMTP(server, port) as smtp:
        smtp.starttls(context=context)
        smtp.login(from_mail, password)
        smtp.sendmail(from_mail, [to_mail, ], msg.as_string())
        
else:  # now we must do this
    client_id = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx8c5'
    client_secret = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-xxb96'
    tenant_id = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx973'
    userId = "levrai@ninjane.er"        
    authority = f"https://login.microsoftonline.com/{tenant_id}"
    scopes = ["https://graph.microsoft.com/.default"]
    
    app = msal.ConfidentialClientApplication(client_id=client_id, client_credential=client_secret, authority=authority)
    result = app.acquire_token_silent(scopes, account=None)
    if not result:
        result = app.acquire_token_for_client(scopes=scopes)
    
    # setup message:
    email_msg = {'Message': {'Subject': subject,
                             'Body': {
                                 'ContentType': 'text/plain', 'Content': e_message.as_string() },   # what do i put here?
                             'ToRecipients': [{'EmailAddress': {'Address': to_mail}}]
                             },
                'SaveToSentItems': 'true'}
    endpoint = f'https://graph.microsoft.com/v1.0/users/{from_user}/sendMail'
    r = requests.post(endpoint, json=email_msg,
                      headers={'Authorization': 'Bearer ' + result['access_token'], "Content-Type": "application/json"})

You seem to be mixing in your example sending as a JSON message using the Graph with sending as a MIME message. Sending as MIME has a few restrictions eg your email has to be under 4 mb(or because of MIME bloat 2.5-3 mb). You need to make sure you encode the message correctly but the following works okay for me in phython built from EmailMessage. (the parser on the Microsoft side can be a little restrictive so start simple with your formatting and see if that works then increase the complexity of the message).

eg

 from email.message import EmailMessage import base64 import sys import json import logging import msal import requests config = { "authority": "https://login.microsoftonline.com/eb8db77e-65e0-4fc3-b967-.......", "client_id": "18bb3888-dad0-4997-96b1-.........", "scope": ["https://graph.microsoft.com/.default"], "secret": ".............", "tenant-id": "eb8db77e-65e0-4fc3-b967-........" } app = msal.ConfidentialClientApplication(config['client_id'], authority=config['authority'], client_credential=config['secret']) result = app.acquire_token_silent(config["scope"], account=None) if not result: logging.info("No suitable token exists in cache. Let's get a new one from AAD.") result = app.acquire_token_for_client(scopes=config["scope"]) sender_email = "gscales@gbbbbb.onmicrosoft.com" receiver_email = "gscales@ccccc.onmicrosoft.com" def create_message(sender, to, subject, message_text): message = EmailMessage() message.set_content(message_text) message['to'] = to message['from'] = sender message['subject'] = subject raw = base64.urlsafe_b64encode(message.as_bytes()) return raw.decode() messageToSend = create_message(sender_email,receiver_email,"test subject","test 123") print(messageToSend) endpoint = f'https://graph.microsoft.com/v1.0/users/{sender_email}/sendMail' r = requests.post(endpoint, data=messageToSend, headers={'Authorization': 'Bearer ' + result['access_token'], "Content-Type": "text/plain"}) print(r.status_code) print(r.text)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM