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

Google Cloud Function V2升级后OAuth2认证SMTP登录失败

问题

将使用4年的Cloud Function从Gen 1升级到Gen 2,改用defineSecret替代functions.config()获取环境变量,并用OAuth2认证替代Gmail的“不安全应用”授权。函数部署成功,但运行时出现invalid user or password错误,该函数用于将联系表单数据通过Gmail发送给自己。

已完成配置:

  • OAuth consent screen设为测试模式,自身邮箱已添加为测试用户
  • 通过OAuth 2.0 Playground生成了带有https://www.googleapis.com/auth/gmail.send权限的refresh token

日志显示“Access Token obtained successfully”,但随后报错:

Error creating transport: Error: Invalid login: 535-5.7.8 Username and Password not accepted.

相关函数代码:

const clientId = defineSecret("CLIENT_ID");
const clientSecret = defineSecret("CLIENT_SECRET");
const refreshToken = defineSecret("REFRESH_TOKEN");

async function getAccessToken(oauth2Client) {
  try {
    const { token } = await oauth2Client.getAccessToken();
    return token;
  } catch (error) {
    console.error('Error getting access token:', error);
    throw new Error(`Failed to get access token: ${error.message}`);
  }
}

const createTransport = async () => {
  try {
    const oauth2Client = new google.auth.OAuth2(
      await clientId.value(),
      await clientSecret.value(),
      "https://developers.google.com/oauthplayground"
    );

    oauth2Client.setCredentials({
      refresh_token: await refreshToken.value()
    });

    const accessToken = await getAccessToken(oauth2Client);
    if (!accessToken) {
      throw new Error('Failed to obtain access token');
    }

    console.log('Access Token obtained successfully');
    
    const transportConfig = {
      host: 'smtp.gmail.com',
      port: 465,
      secure: true,
      auth: {
        type: 'OAuth2',
        user: await emailSender.value(),
        clientId: await clientId.value(),
        clientSecret: await clientSecret.value(),
        refreshToken: await refreshToken.value(),
        accessToken,
      },
    };

    const transporter = nodemailer.createTransport(transportConfig);
    
    await transporter.verify();
    console.log('SMTP connection verified successfully');
    
    return transporter;
  } catch (error) {
    console.error('Error creating transport:', error);
    throw new Error(`Failed to create transport: ${error.message}`);
  }
};
排查与解决方案
  1. 核对发送邮箱一致性
    user: await emailSender.value()对应的邮箱必须和生成refresh token时使用的测试用户邮箱完全一致,且该邮箱已在OAuth consent screen的测试用户列表中。

  2. 校验OAuth Playground配置

    • 必须使用自己Google Cloud项目的Client ID和Client Secret,而非Playground默认的测试ID
    • 授权时需明确勾选https://www.googleapis.com/auth/gmail.send权限,测试模式下refresh token有效期为7天,若过期需重新生成
  3. 简化Nodemailer OAuth2配置
    Nodemailer会自动用refresh token刷新access token,无需手动获取。移除手动传入的accessToken,避免过期或格式问题:

    const transportConfig = {
      host: 'smtp.gmail.com',
      port: 465,
      secure: true,
      auth: {
        type: 'OAuth2',
        user: await emailSender.value(),
        clientId: await clientId.value(),
        clientSecret: await clientSecret.value(),
        refreshToken: await refreshToken.value()
      },
    };
    
  4. 验证Secret Manager配置
    确认Cloud Function的服务账号拥有访问Secret Manager的权限,且CLIENT_ID、CLIENT_SECRET、REFRESH_TOKEN、emailSender的Secret值已正确存入,取值逻辑无错误。

  5. 检查Gmail账户状态
    确保目标Gmail账户未开启两步验证(OAuth2模式下建议关闭,若开启需使用应用专用密码),同时确认账户未被Google限制邮件发送权限。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 01:27:09