Nodemailer对接Outlook 365报535 5.7.139认证失败问题咨询
方案实现步骤
你遇到的535报错是因为微软企业Office365租户默认已禁用用户名密码方式的SMTP AUTH认证,即便手动开启也会被多因素认证、条件访问等安全策略拦截,直接使用账号密码的方式已经无法正常使用。此外企业Office365租户默认绑定Azure AD,即便你不使用Azure云服务,要实现无交互服务端发信必须通过Azure AD做应用权限授权,这个是微软的强制要求,没有绕开的办法,以下是最简配置步骤,全程只需操作一次。
1. Azure AD 应用一次性配置
你需要使用企业Office365全局管理员账号完成以下配置:
- 进入Azure Active Directory管理中心,左侧导航选择「应用注册」→「新注册」
- 应用名称自定义,受支持的账户类型选择「仅我的组织使用」,重定向URI留空,点击「注册」
- 记录生成的客户端ID、租户ID两个参数
- 左侧导航选择「证书和密码」→「新建客户端密码」,选择最长有效期,创建后立即记录客户端密码值(关闭页面后无法再次查看)
- 左侧导航选择「API权限」→「添加权限」→ 选择「Microsoft Graph」→「应用程序权限」,搜索
Mail.Send权限并勾选,点击「授予<你的租户名>管理员同意」完成权限激活 - (可选安全配置)如果需要限制该应用仅能使用指定共享邮箱B发信,可通过Exchange Online PowerShell执行以下命令,限制应用的发送范围:
New-ApplicationAccessPolicy -AppId <你刚才生成的客户端ID> -PolicyScopeGroupId <共享邮箱B的地址> -AccessRight RestrictAccess -Description "限制邮件应用仅能使用共享邮箱B发信"
2. Node.js 代码实现
推荐直接调用Microsoft Graph API发信,比SMTP协议更稳定,不受SMTP认证策略限制:
安装依赖
npm install @azure/msal-node @microsoft/microsoft-graph-client isomorphic-fetch
代码示例
import * as msal from "@azure/msal-node"; import { Client } from "@microsoft/microsoft-graph-client"; import "isomorphic-fetch"; // 配置参数建议存到环境变量,部署到Heroku时直接配置Config Vars即可 const config = { tenantId: process.env.O365_TENANT_ID, clientId: process.env.O365_CLIENT_ID, clientSecret: process.env.O365_CLIENT_SECRET, senderMail: process.env.O365_SENDER_MAIL // 共享邮箱B的地址 }; // 初始化MSAL客户端获取访问凭证 const msalClient = new msal.ConfidentialClientApplication({ auth: { clientId: config.clientId, authority: `https://login.microsoftonline.com/${config.tenantId}`, clientSecret: config.clientSecret } }); async function getGraphClient() { const tokenResponse = await msalClient.acquireTokenByClientCredential({ scopes: ["https://graph.microsoft.com/.default"] }); if (!tokenResponse) throw new Error("获取访问凭证失败"); return Client.initWithMiddleware({ authProvider: { getAccessToken: async () => tokenResponse.accessToken } }); } // 发信函数 async function sendMail(to: string, subject: string, content: string) { const graphClient = await getGraphClient(); const mailBody = { message: { subject: subject, body: { contentType: "Text", // 可以改为HTML支持富文本 content: content }, toRecipients: [ { emailAddress: { address: to } } ] }, saveToSentItems: true // 发送后保存到共享邮箱的已发送文件夹 }; await graphClient.api(`/users/${config.senderMail}/sendMail`).post(mailBody); }
可选:用nodemailer SMTP + OAuth2认证
如果你坚持要用nodemailer的SMTP方式,可以用以下配置替换原来的transporter:
import * as nodemailer from "nodemailer"; import * as msal from "@azure/msal-node"; const msalClient = new msal.ConfidentialClientApplication({ auth: { clientId: config.clientId, authority: `https://login.microsoftonline.com/${config.tenantId}`, clientSecret: config.clientSecret } }); const transporter = nodemailer.createTransport({ service: "Outlook365", auth: { type: "OAuth2", user: config.senderMail, clientId: config.clientId, clientSecret: config.clientSecret, accessToken: async () => { const res = await msalClient.acquireTokenByClientCredential({ scopes: ["https://outlook.office365.com/.default"] }); return res?.accessToken; } } });
不过这种方式还是可能受SMTP相关策略限制,不如直接调用Graph API稳定。
需求适配说明
- 要实现从共享邮箱B发信到B,直接在调用
sendMail时to参数填共享邮箱B的地址即可 - 该方案支持向任意外部邮箱地址发信,只要Office365租户没有限制外发权限即可
- 访问凭证由MSAL库自动刷新,无需手动定期更新,配置完成后服务端可全自动运行,无人工交互
常见问题排查
- 出现权限报错首先检查是否已经在API权限页面点击了「授予管理员同意」
- 发信失败检查共享邮箱地址是否正确,是否已经配置了应用访问策略允许该应用使用该邮箱发信
- 部署到Heroku时不要硬编码密钥,所有敏感参数都放到Heroku的Config Vars环境变量中
内容的提问来源于stack exchange,提问作者Nikola-Milovic
相关产品推荐
相关产品推荐

