[MSGRAPH API][Outlook] Deamon for send email python script [SOLVED]

Jonathan G 30 Reputation points
2025-06-01T09:48:19.4233333+00:00

(initial context posted here: https://learn.microsoft.com/en-us/answers/questions/2259094/msgraph-api-sending-email-using-python-app?page=1&orderby=helpful&comment=answer-2023775&translated=false#newest-answer-comment)

Hi,

I'm trying to create a daemon python script to send emails using the MSGraph API. Since this would be a recurring task, i do not wish to login every time I have to send an email.

which lead me to this error:

$ python3 test.py 
To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code XXXXX to authenticate.
ERROR:root:Failed to send email from ******@mail.com to ******@mail.com:         APIError         Code: 403         message: None         error: MainError(additional_data={}, code='ErrorAccessDenied', details=None, inner_error=None, message='Access is denied. Check credentials and try again.', target=None)

My code looks like this:

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
    def __init__(self) -> None:
class NotificationManager:
        self.client_id = "1234"
        self.tenant_id = "5678"
        self.client_secret = "8526"
        self.sender = "******@mail.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="******@mail.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())

These I the rights i setup so far:

User's image

image-1

A comment from @SrideviM mentioned:

Make sure to grant admin consent to Mail.Send permission of Delegated type and then generate the token by signing in with user account.

This seems to be the case on the first screenshot. "Send mail as any user" has a "yes" on the admin consent requirements and got it granted by the org.

Is the copied code not correct approach ? or/and am i missing something in my app authorization setup ?

I also tried following the instructions here: https://learn.microsoft.com/en-us/graph/api/user-sendmail?view=graph-rest-1.0&tabs=python and here: https://learn.microsoft.com/en-us/graph/sdks/create-client?from=snippets&tabs=python with no success (no error but also no email sent)

Microsoft Security | Microsoft Entra | Microsoft Entra ID
0 comments No comments
{count} votes

Accepted answer
  1. SrideviM 5,630 Reputation points Microsoft External Staff Moderator
    2025-06-02T02:30:08.3566667+00:00

    Hello Jonathan G,

    If you are using delegated flows like device code flow, user need to login every time and you must add Delegated type permissions with admin consent.

    But if the use case is with daemon setup where user login must be avoided, you need to switch to client credentials flow by granting Application type permissions with admin consent.

    Initially, I registered one application and granted Mail.Send permission of Application type with admin consent as below:

    User's image

    Now, I used below sample Microsoft Graph Python SDK code to send mail using client credentials flow:

    import asyncio
    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
    
    TENANT_ID = "tenantId"
    CLIENT_ID = "appId"
    CLIENT_SECRET = "secret"
    SENDER = "******@xxxxxxxx.onmicrosoft.com"
    RECIPIENT = "******@xxxxxxx.onmicrosoft.com"
    
    async def send_mail():
        credential = ClientSecretCredential(
            tenant_id=TENANT_ID,
            client_id=CLIENT_ID,
            client_secret=CLIENT_SECRET
        )
    
        client = GraphServiceClient(credential, ["https://graph.microsoft.com/.default"])
    
        message = Message(
            subject="Graph SDK Mail Test",
            body=ItemBody(
                content_type=BodyType.Text,
                content="This is a test email sent using the Graph SDK"
            ),
            to_recipients=[
                Recipient(
                    email_address=EmailAddress(
                        address=RECIPIENT
                    )
                )
            ]
        )
    
        mail_body = SendMailPostRequestBody(
            message=message,
            save_to_sent_items=True
        )
    
        await client.users.by_user_id(SENDER).send_mail.post(body=mail_body)
        print("Email sent successfully.")
    
    if __name__ == "__main__":
        asyncio.run(send_mail())
    

    Response:

    User's image

    To confirm that, I checked Sent Items of sender user where mail sent successfully as below:

    User's image

    Let me know if you have further queries on this. I'll be happy to assist.

    Hope this helps!


    If this answers your query, do click Accept Answer and Yes for was this answer helpful, which may help members with similar questions.

    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.


1 additional answer

Sort by: Most helpful
  1. Jonathan G 30 Reputation points
    2025-06-01T18:35:50.7+00:00

    Solved: https://github.com/NegaoDoTi/Send-Email-Outlook-Graph-API/blob/main/enviar_email_api.py

    from msal import ConfidentialClientApplication
    from requests import post
    from json import dumps
    from config.config import config_env
    class SendEmailOutlook():
        def __init__(self) -> None:
            self.__client_secret = config_env["ms_graph"]["client_secret"]
            self.__client_id = config_env["ms_graph"]["client_id"]
            self.__scopes = ["https://graph.microsoft.com/.default"]
            self.__tenant_id = config_env["ms_graph"]["tenant_id"]
            self.__email = config_env["ms_graph"]["email"]
            self.__authority = f"https://login.microsoftonline.com/{self.__tenant_id}"
        def __construct_msal_app(self) -> ConfidentialClientApplication:
            return ConfidentialClientApplication(
                client_id=self.__client_id,
                authority=self.__authority,
                client_credential=self.__client_secret
            )
            
        def get_access_token(self) -> str:
            msal_app = self.__construct_msal_app()
            result = msal_app.acquire_token_for_client(scopes=self.__scopes)
            
            if "access_token" in result:
                return result["access_token"]
            else:
                raise Exception(f"Erro ao obter o token de acesso: {result.get('error_description')}")
            
        def send_email(self, text:str, email:str, access_token:str ) -> str:
            graph_endpoint = f"https://graph.microsoft.com/v1.0/users/{self.__email}/sendMail"
            email_data = {
                "message" : {
                    "subject" : "Este é um email de teste",
                    "body" : {
                        "contentType" : "Text",
                        "content" : f"{text}"
                    },
                    "toRecipients" : [
                        {
                            "emailAddress" : {
                                "address" : "******@hotmail.com"
                            }
                        }
                    ]
                },
                "saveToSentItems": "true"
            }
            
            headers = {
                "Authorization" : f'Bearer {access_token}',
                "Content-Type" : "application/json"
            }
            
            response = post(graph_endpoint, headers=headers, data=dumps(email_data))
            
            if response.status_code == 202:
                return "Sucesso ao enviar email!"
            else:
                return f"Falha ao enviar email {response.text}"
            
        
    if __name__ == "__main__":
        
        try:
            send_email = SendEmailOutlook()
            token = send_email.get_access_token()
            result = send_email.send_email("Test", "******@test.com.br", token)
            
            print(result)
        except Exception as e:
            print(e)
    
    0 comments No comments

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.