Nodemailer发送Gmail数日后出现invalid_grant报错如何解决
失效原因
- 核心触发原因是你的Google Cloud OAuth应用处于测试状态:未正式发布的测试类OAuth应用,生成的refresh token有效期固定为7天,到期后自动失效,就会返回
invalid_grant错误,这也是你替换新token后就能恢复正常的直接原因。 - 其他可能触发的场景:同一应用同一账号生成的refresh token超过50个的数量上限,最早生成的token会被自动失效;账号密码修改、授权被手动撤销也会触发该错误。
解决方案
- 调整OAuth应用发布状态:进入Google Cloud控制台的API和服务模块,打开OAuth同意屏幕配置,将发布状态从「测试」修改为「正式发布」。个人自用场景下调整为正式发布无需审核,调整后refresh token就不会7天自动过期。
- 控制refresh token生成次数:不要反复生成新的refresh token,确认应用配置正确后仅保留1个有效token使用即可,避免因数量上限导致旧token提前失效。
- 代码逻辑优化:你现有的代码可以复用transport实例,无需每次发邮件都重新创建,减少不必要的资源消耗,同时可以增加
invalid_grant错误的捕获逻辑,出现异常时主动告警便于及时处理,优化参考代码如下:
const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID const GOOGLE_CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET const GOOGLE_REDIRECT_URI = process.env.GOOGLE_REDIRECT_URI const GOOGLE_REFRESH_TOKEN = process.env.GOOGLE_REFRESH_TOKEN const GMAIL_USER = 'user@gmail.com' const oAuth2Client = new google.auth.OAuth2(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REDIRECT_URI) oAuth2Client.setCredentials({refresh_token: GOOGLE_REFRESH_TOKEN}) // 全局复用transport实例 let mailTransport: nodemailer.Transporter async function getTransport() { if (mailTransport) return mailTransport const accessToken = await oAuth2Client.getAccessToken() mailTransport = nodemailer.createTransport({ service: 'gmail', auth: { type: 'OAUTH2', user: GMAIL_USER, clientId: GOOGLE_CLIENT_ID, clientSecret: GOOGLE_CLIENT_SECRET, refreshToken: GOOGLE_REFRESH_TOKEN, accessToken, } }) return mailTransport } export async function sendMail(payload: IMailDTO): Promise<SMTPTransport.SentMessageInfo>{ const data = MailDTO.receiver(payload) const transport = await getTransport() const mailOptions = { from: 'no-reply <no-reply@gmail.com>', to: data.to, subject: data.subject, text: data.text, html: data.html } try { const result = await transport.sendMail(mailOptions) return result } catch (err) { // 捕获invalid_grant错误,可自行添加告警逻辑 if (err?.message?.includes('invalid_grant')) { // 重置transport,下次请求会重新获取access token mailTransport = null throw new Error('Refresh token失效,请检查OAuth应用配置') } throw new Error("Email not sent. Try again.") } }
内容的提问来源于stack exchange,提问作者Prasanga Thapaliya
相关产品推荐
相关产品推荐

