Search code examples
python-3.xazureazure-active-directorymicrosoft-graph-apiazure-service-principal

Send email via Microsoft Graph API using Python with service principal


I have the service principal set up in Azure portal with Mail.Send access, I have client_id, tenant_id, client_secret information of that service principal. I am using the below code to send email, but I am getting errors.

So, in the graph url, if I use https://graph.microsoft.com/v1.0/me. I am getting authentication error. If use this url https://graph.microsoft.com/v1.0/users/{user_id}/sendMail, in place of user_id, I have tried the below options, but everything failed and given Invalid user error. Help me to resolve this.

for user_id, used the below options,

  1. client_id
  2. tenant_id
  3. app:client_id@tenant_id
  4. app:tenant_id@client_id
import requests
import json
import msal

# Define your Microsoft 365 email details
recipient_email = "receiver@example.com"
subject = "Email subject"
message_body = "Email message body"

# Your application (client) ID and client secret obtained from Azure AD
client_id = "90xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
client_secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# Tenant ID (directory ID)
tenant_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx"

# Microsoft Graph API endpoint to send an email as the application (service principal)
graph_url = f"https://graph.microsoft.com/v1.0/users/{client_id}/sendMail"

# Create a confidential client application
app = msal.ConfidentialClientApplication(
    client_id=client_id,
    authority=f"https://login.microsoftonline.com/{tenant_id}",
    client_credential=client_secret,
)

# Get an access token
result = app.acquire_token_for_client(scopes=["https://graph.microsoft.com/.default"])
access_token = result.get("access_token")


# Create the email message
email_message = {
    "message": {
        "subject": subject,
        "body": {
            "contentType": "Text",
            "content": message_body,
        },
        "toRecipients": [
            {
                "emailAddress": {
                    "address": recipient_email
                }
            }
        ]
    }
}

# Convert the message to JSON format
email_data = json.dumps(email_message)

# Send the email
response = requests.post(
    graph_url,
    headers={
        "Authorization": "Bearer " + access_token,
        "Content-Type": "application/json",
    },
    data=email_data,
)

# Check the response status code
if response.status_code == 202:
    print("Email sent successfully!")
else:
    print(f"Failed to send email. Status code: {response.status_code}")
    print(response.text)

Invalid user error I got for using client_id or tenant_id or both combined,

**For client_id or tenant_id:**
Failed to send email. Status code: 404
{"error":{"code":"ErrorInvalidUser","message":"The requested user 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx' is invalid."}}

**For both combined:**
Failed to send email. Status code: 404
{"error":{"code":"ErrorInvalidUser","message":"The requested user 'app:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx@xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx' is invalid."}}

**For using graph url with /me:**
Failed to send email. Status code: 400
{"error":{"code":"BadRequest","message":"/me request is only valid with delegated authentication flow.","innerError":{"date":"2023-09-29T15:44:45","request-id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx","client-request-id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx"}}}

Solution

  • The error occurred as you are passing client_id or tenant_id in graph url instead of user ID or UPN.

    I registered one Azure AD application and granted Mail.Send API permission of Application type as below:

    enter image description here

    When I ran your code in my environment by passing client_id in graph url, I too got same error as below:

    import requests
    import json
    import msal
    
    # Define your Microsoft 365 email details
    recipient_email = "receiver@example.com"
    subject = "Email subject"
    message_body = "Email message body"
    
    # Your application (client) ID and client secret obtained from Azure AD
    client_id = "xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    client_secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    
    # Tenant ID (directory ID)
    tenant_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx"
    
    # Microsoft Graph API endpoint to send an email as the application (service principal)
    graph_url = f"https://graph.microsoft.com/v1.0/users/{client_id}/sendMail"
    
    # Create a confidential client application
    app = msal.ConfidentialClientApplication(
        client_id=client_id,
        authority=f"https://login.microsoftonline.com/{tenant_id}",
        client_credential=client_secret,
    )
    
    # Get an access token
    result = app.acquire_token_for_client(scopes=["https://graph.microsoft.com/.default"])
    access_token = result.get("access_token")
    
    
    # Create the email message
    email_message = {
        "message": {
            "subject": subject,
            "body": {
                "contentType": "Text",
                "content": message_body,
            },
            "toRecipients": [
                {
                    "emailAddress": {
                        "address": recipient_email
                    }
                }
            ]
        }
    }
    
    # Convert the message to JSON format
    email_data = json.dumps(email_message)
    
    # Send the email
    response = requests.post(
        graph_url,
        headers={
            "Authorization": "Bearer " + access_token,
            "Content-Type": "application/json",
        },
        data=email_data,
    )
    
    # Check the response status code
    if response.status_code == 202:
        print("Email sent successfully!")
    else:
        print(f"Failed to send email. Status code: {response.status_code}")
        print(response.text)
    

    Response:

    enter image description here

    To resolve the error, you need to pass either ID or UPN of user having active Office 365 license.

    When I ran the same code by passing user's UPN in graph url, I got response like this:

    import requests
    import json
    import msal
    
    # Define your Microsoft 365 email details
    recipient_email = "sri@xxxxx.onmicrosoft.com"
    subject = "Demo mail"
    message_body = "Hello Sri!"
    
    # Your application (client) ID and client secret obtained from Azure AD
    client_id = "xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    client_secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    
    # Tenant ID (directory ID)
    tenant_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx"
    
    # User's UPN
    user_upn  =  "xxxx_rk@M3xxxxxxx.onmicrosoft.com"
    
    # Microsoft Graph API endpoint to send an email as the application (service principal)
    graph_url = f"https://graph.microsoft.com/v1.0/users/{user_upn}/sendMail"
    
    # Create a confidential client application
    app = msal.ConfidentialClientApplication(
        client_id=client_id,
        authority=f"https://login.microsoftonline.com/{tenant_id}",
        client_credential=client_secret,
    )
    
    # Get an access token
    result = app.acquire_token_for_client(scopes=["https://graph.microsoft.com/.default"])
    access_token = result.get("access_token")
    
    
    # Create the email message
    email_message = {
        "message": {
            "subject": subject,
            "body": {
                "contentType": "Text",
                "content": message_body,
            },
            "toRecipients": [
                {
                    "emailAddress": {
                        "address": recipient_email
                    }
                }
            ]
        }
    }
    
    # Convert the message to JSON format
    email_data = json.dumps(email_message)
    
    # Send the email
    response = requests.post(
        graph_url,
        headers={
            "Authorization": "Bearer " + access_token,
            "Content-Type": "application/json",
        },
        data=email_data,
    )
    
    # Check the response status code
    if response.status_code == 202:
        print("Email sent successfully!")
    else:
        print(f"Failed to send email. Status code: {response.status_code}")
        print(response.text)
    

    Response:

    enter image description here

    To confirm that, I checked the same in user's Outlook where mail sent successfully as below:

    enter image description here

    Here, you are authenticating as a service principal by generating access token using client credentials flow where there is no user interaction.

    With this token, service principal will send mail on-behalf of specified user.