You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Python+Service Principal调用Microsoft Graph API发邮件报错求助

问题分析与解决方法

错误原因解析

1. /me 端点认证错误

服务主体采用应用权限认证流,而/me端点仅支持委派权限认证流(需代表具体用户登录),因此调用/me/sendMail会触发BadRequest错误。

2. 无效用户ID错误

users/{user_id}/sendMail中的user_id必须是Azure AD中已存在的Exchange Online邮箱用户标识,而非服务主体的client_id、tenant_id或组合值。服务主体本身没有邮箱,必须依托实际用户的邮箱完成邮件发送。


解决步骤

1. 获取合法用户标识

从Azure AD门户中获取一个拥有Exchange Online邮箱的用户的任意一种标识:

  • 用户的SMTP邮箱地址(如 sender@yourcompany.com)
  • 用户的Object ID(在用户详情页面的「概述」标签下)

2. 修改Graph API端点

将代码中的graph_url替换为:

# 替换为实际的发件用户邮箱或Object ID
sender_identity = "sender@yourcompany.com"
graph_url = f"https://graph.microsoft.com/v1.0/users/{sender_identity}/sendMail"

3. 确认权限配置

确保服务主体已被授予应用权限类型的Mail.Send权限(而非委派权限),且权限范围为租户级(已完成管理员同意)。


修正后的完整代码

import requests
import json
import msal

# 邮件详情
recipient_email = "receiver@example.com"
subject = "Email subject"
message_body = "Email message body"

# 服务主体信息
client_id = "90xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
client_secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
tenant_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx"

# 替换为实际的发件用户邮箱或Object ID
sender_identity = "sender@yourcompany.com"
graph_url = f"https://graph.microsoft.com/v1.0/users/{sender_identity}/sendMail"

# 初始化客户端
app = msal.ConfidentialClientApplication(
    client_id=client_id,
    authority=f"https://login.microsoftonline.com/{tenant_id}",
    client_credential=client_secret,
)

# 获取访问令牌
result = app.acquire_token_for_client(scopes=["https://graph.microsoft.com/.default"])
access_token = result.get("access_token")

# 构建邮件内容
email_message = {
    "message": {
        "subject": subject,
        "body": {
            "contentType": "Text",
            "content": message_body,
        },
        "toRecipients": [
            {
                "emailAddress": {
                    "address": recipient_email
                }
            }
        ]
    }
}

# 发送邮件
response = requests.post(
    graph_url,
    headers={
        "Authorization": "Bearer " + access_token,
        "Content-Type": "application/json",
    },
    json=email_message,  # 直接用json参数,无需手动序列化
)

# 结果判断
if response.status_code == 202:
    print("Email sent successfully!")
else:
    print(f"Failed to send email. Status code: {response.status_code}")
    print(response.text)

注:代码优化了请求参数,直接使用json=email_message替代手动dumps后传入data,更简洁且避免序列化错误。


内容的提问来源于stack exchange,提问作者Beginner

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 16:48:18