MSGraph API: sending email using Python app

Francesco Castellani 60 Reputation points
2025-04-17T15:58:17.4066667+00:00

I am new to Microsoft Graph. I need to send an email automatically internally, and I already have a valid client_id which has the following API permissions:

  1. Mail.Send (Delegated)
  2. Mail.Send (Application)
  3. User.Read (Delegated)
  4. Application.Read.All (Delegated)

However, when I run the code below, trying to send an email to myself (valid Company address), I don't receive any email and if I look at the result object I see that it is the following: <coroutine object NotificationManager.send_email at 0x0000012BC81452A0>

Instead, I was expecting the typical JSON response from an API, with 4xx if errored.

This is my code: what am I doing wrong?

import msal from Utils.utils import Utils # Custom utility class
from azure.identity import DeviceCodeCredential
from msgraph.graph_service_client import GraphServiceClient
from typing import Optional 
from msgraph.generated.models.message import Message
from msgraph.generated.users.item.send_mail.send_mail_post_request_body import SendMailPostRequestBody
from msgraph.generated.models.importance import Importance
from msgraph.generated.models.item_body import ItemBody
from msgraph.generated.models.body_type import BodyType
from msgraph.generated.models.recipient import Recipient
from msgraph.generated.models.email_address import EmailAddress


class NotificationManager:

    def __init__(self) -> None:
        '''Class constructor.'''

        self.ms_graph_base_url = "https://graph.microsoft.com/v1.0"
        self.client_id = Utils.load_config('client_id')
        self.client_secret = Utils.load_config('client_secret')
        self.tenant_id = Utils.load_config("tenant_id")
        self.authority = f"https://login.microsoftonline.com/{self.tenant_id}"
        self.scopes = ["https://graph.microsoft.com/.default"]
        self.sender = "<sender_email>"
        

    def get_client(self) -> GraphServiceClient:
        '''Get a requests session with the access token.'''

        try:
            credential = DeviceCodeCredential(self.client_id)
            graph_client = GraphServiceClient(credential, self.scopes)
            return graph_client
        
        except Exception as e:
            logging.error(f"An error occurred: {e}")
            raise Exception("Failed to get Graph client.")
        

    async def send_email(self, graph_client: GraphServiceClient, recipient: str, subject: str, content: str) -> None:
        '''Send an email using the Graph API.'''

        try:
            request_body = SendMailPostRequestBody(
                message = Message(
                    subject = subject,
                    importance = Importance.Low,
                    body = ItemBody(
                        content_type = BodyType.Html,
                        content = self.form_html_message(content),
                    ),
                    sender = EmailAddress(
                        address = self.sender,
                    ),
                    to_recipients = [
                        Recipient(
                            email_address = EmailAddress(
                                address = recipient,
                            ),
                        ),
                    ],
                ),
                save_to_sent_items = True
            )

            #result = await graph_client.me.messages.post(request_body)
            result = await graph_client.me.send_mail.post(request_body)
            return result
        
        except Exception as e:
            logging.error(f"An error occurred: {e}")
            raise Exception(f"Failed to send email from {self.sender} to {recipient}.")
        

    def form_html_message(self, content: str) -> str:
        '''Format the message to be sent in HTML.'''

        try:
            html_content = f"""
            <html>
                <head>
                    <style>
                        body {{
                            font-family: Arial, sans-serif;
                            font-size: 14px;
                            color: #333;
                        }}
                        h1 {{
                            color: #0078d4;
                        }}
                    </style>
                </head>
                <body>
                    <h1>Header</h1>
                    <p>{content}</p>
                </body>
            </html>
            """
            return html_content
        
        except Exception as e:
            logging.error(f"An error occurred: {e}")
            raise Exception("Failed to format HTML message.")

Microsoft Graph
Microsoft Graph
A Microsoft programmability model that exposes REST APIs and client libraries to access data on Microsoft 365 services.
13,541 questions
{count} votes

1 answer

Sort by: Most helpful
  1. SrideviM 2,785 Reputation points Microsoft External Staff
    2025-04-19T07:22:15.6566667+00:00

    Hello Francesco Castellani,

    I understand you're using device code flow with Microsoft Graph. In this method, the app can only send emails from the user who logs in. So, the sender must be the same as the signed-in user.

    Also, the error line showing <coroutine object ...> is happening because the send function is async, and it needs to be called with await.

    I registered one application and added same API permissions with admin consent:

    enter image description here

    When I tried your code without using await, I too got the same error in my environment:

    enter image description here

    After fixing that, the email was sent successfully. Here’s the working code using device code flow:

    import asyncio
    import logging
    from azure.identity import DeviceCodeCredential
    from msgraph.graph_service_client import GraphServiceClient
    from msgraph.generated.models.message import Message
    from msgraph.generated.models.item_body import ItemBody
    from msgraph.generated.models.body_type import BodyType
    from msgraph.generated.models.recipient import Recipient
    from msgraph.generated.models.email_address import EmailAddress
    from msgraph.generated.users.item.send_mail.send_mail_post_request_body import SendMailPostRequestBody
    
    class NotificationManager:
        def __init__(self) -> None:
            self.client_id = "appID"
            self.tenant_id = "tenantId"
            self.scopes = ["https://graph.microsoft.com/.default"]
            self.sender = "******@xxxxxxxx.onmicrosoft.com"
    
        def get_client(self) -> GraphServiceClient:
            try:
                credential = DeviceCodeCredential(client_id=self.client_id, tenant_id=self.tenant_id)
                return GraphServiceClient(credential, self.scopes)
            except Exception as e:
                logging.error(f"Failed to get Graph client: {e}")
                raise
    
        async def send_email(self, graph_client: GraphServiceClient, recipient: str, subject: str, content: str) -> None:
            try:
                message = Message(
                    subject=subject,
                    importance="low",
                    body=ItemBody(
                        content_type=BodyType.Html,
                        content=self.form_html_message(content),
                    ),
                    to_recipients=[
                        Recipient(
                            email_address=EmailAddress(address=recipient),
                        )
                    ]
                )
    
                request_body = SendMailPostRequestBody(
                    message=message,
                    save_to_sent_items=True
                )
    
                response = await graph_client.me.send_mail.post(request_body)
    
                if response is None:
                    print("Mail sent successfully.")
                else:
                    print("Unexpected response:", response)
            except Exception as e:
                logging.error(f"Failed to send email from {self.sender} to {recipient}: {e}")
                raise
    
        def form_html_message(self, content: str) -> str:
            return f"""
            <html>
                <head>
                    <style>
                        body {{
                            font-family: Arial, sans-serif;
                            font-size: 14px;
                            color: #333;
                        }}
                        h1 {{
                            color: #0078d4;
                        }}
                    </style>
                </head>
                <body>
                    <h1>Test mail</h1>
                    <p>{content}</p>
                </body>
            </html>
            """
    async def main():
        manager = NotificationManager()
        client = manager.get_client()
        await manager.send_email(
            graph_client=client,
            recipient="******@xxxxxxxxx.onmicrosoft.com",
            subject="Test Subject",
            content="This is a test email sent using Microsoft Graph SDK."
        )
    
    if __name__ == "__main__":
        asyncio.run(main())
    

    When you run the code, it shows a message with a link and device code—clicking the link opens a browser where you enter the code to sign in:

    enter image description here

    Make sure to sign in with user account from which you want to send mail:

    enter image description here

    Output:

    enter image description here

    Mail sent with Device Code flow:

    enter image description here

    If you want to send mail from any account without someone logging in, you can use client credentials flow. This method works with Application type permissions and lets the app send mail from any user with a mailbox.

    Here’s the working code for that:

    import asyncio
    import logging
    from azure.identity import ClientSecretCredential
    from msgraph.graph_service_client import GraphServiceClient
    from msgraph.generated.models.message import Message
    from msgraph.generated.models.item_body import ItemBody
    from msgraph.generated.models.body_type import BodyType
    from msgraph.generated.models.recipient import Recipient
    from msgraph.generated.models.email_address import EmailAddress
    from msgraph.generated.users.item.send_mail.send_mail_post_request_body import SendMailPostRequestBody
    
    class NotificationManager:
        def __init__(self) -> None:
            self.client_id = "appId"
            self.tenant_id = "tenantId"
            self.client_secret = "secret"
            self.sender = "******@xxxxxxxxxx.onmicrosoft.com"
            self.scopes = ["https://graph.microsoft.com/.default"]
    
        def get_client(self) -> GraphServiceClient:
            try:
                credential = ClientSecretCredential(
                    tenant_id=self.tenant_id,
                    client_id=self.client_id,
                    client_secret=self.client_secret
                )
                return GraphServiceClient(credential, self.scopes)
            except Exception as e:
                logging.error(f"Failed to get Graph client: {e}")
                raise
    
        async def send_email(self, graph_client: GraphServiceClient, recipient: str, subject: str, content: str) -> None:
            try:
                message = Message(
                    subject=subject,
                    importance="low",
                    body=ItemBody(
                        content_type=BodyType.Html,
                        content=self.form_html_message(content),
                    ),
                    to_recipients=[
                        Recipient(email_address=EmailAddress(address=recipient))
                    ]
                )
    
                request_body = SendMailPostRequestBody(
                    message=message,
                    save_to_sent_items=True
                )
    
                response = await graph_client.users.by_user_id(self.sender).send_mail.post(request_body)
    
                if response is None:
                    print("Mail sent successfully.")
                else:
                    print("Unexpected response:", response)
            except Exception as e:
                logging.error(f"Failed to send email from {self.sender} to {recipient}: {e}")
                raise
    
        def form_html_message(self, content: str) -> str:
            return f"""
            <html>
                <head>
                    <style>
                        body {{
                            font-family: Arial, sans-serif;
                            font-size: 14px;
                            color: #333;
                        }}
                        h1 {{
                            color: #0078d4;
                        }}
                    </style>
                </head>
                <body>
                    <h1>Test mail</h1>
                    <p>{content}</p>
                </body>
            </html>
            """
    async def main():
        manager = NotificationManager()
        client = manager.get_client()
        await manager.send_email(
            graph_client=client,
            recipient="******@xxxxxxxx.onmicrosoft.com",
            subject="Test Subject (App Auth)",
            content="This is a test email sent using Microsoft Graph SDK via client credentials."
        )
    
    if __name__ == "__main__":
        asyncio.run(main())
    

    Output:

    enter image description here

    Mail sent with client credentials flow:

    enter image description here

    Hope this helps clarify things.


    If this answer was helpful, please click "Accept the answer" and mark Yes, as this can help other community members.

    User's image

    If you have any other questions or are still experiencing issues, feel free to ask in the "comments" section, and I'd be happy to help.


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.