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:
When I tried your code without using await
, I too got the same error in my environment:
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:
Make sure to sign in with user account from which you want to send mail:
Output:
Mail sent with Device Code flow:
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:
Mail sent with client credentials flow:
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.
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.