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

基于Gmail API与Node.js的多邮箱推送通知配置问题

解决方案:在Gmail Watch请求中添加自定义标识以关联用户与集成邮箱

核心结论

可以在Gmail的watch请求中嵌入自定义数据,让/gmail-notifications端点接收到通知时,准确匹配到数据库中对应的用户和集成邮箱记录。推荐两种实现方式:


方式一:利用Gmail API的自定义参数传递标识

Gmail API允许在watch请求的配置中添加自定义参数,这些参数会被包含在后续的通知请求里。可以通过transport.params字段传递键值对,或者用payload字段传递自定义字符串。

修改/watch-inbox/v2/端点代码

构造watch请求时,加入能唯一标识用户和集成邮箱的信息(比如用户ID+集成邮箱的组合):

app.post('/watch-inbox/v2/', async (req, res) => {
    const { userEmail, accountEmail } = req.query;
    const channel = {
        id: 'local-notifications',
        type: 'web_hook',
        address: `https://random-number.ngrok-free.app/gmail-notifications`,
    };

    const currUser = await User.findOne({ email: userEmail });
    // 修正原代码的查询逻辑:展平集成数据数组后查找对应邮箱
    const gmailIntegration = currUser.appIntegrations
        .flatMap(app => app.integrationData)
        .find(data => data.emailUsed === accountEmail);
    const tokensInDB = gmailIntegration.tokens;

    oAuth2Client.setCredentials({
        refresh_token: tokensInDB.refresh_token,
    });

    const gmail = google.gmail({ version: 'v1', auth: oAuth2Client });

    // 生成唯一标识:用户ID + 集成邮箱
    const customId = `${currUser._id}_${accountEmail}`;

    const response = await gmail.users.watch({
        userId: 'me',
        resource: {
            labelIds: ['SENT'],
            topicName: 'topicName/topics/gmail-notifications',
            labelFilterAction: 'exclude',
            notificationSettings: {
                transport: {
                    ...channel,
                    // 添加自定义参数到transport配置
                    params: { customId }
                },
                watchNewThread: true,
            },
            // 也可以用payload字段传递标识,两种方式二选一即可
            payload: customId
        },
    });

    // 更新数据库中的historyId
    gmailIntegration.historyId = response.data.historyId;
    await currUser.save();

    console.log('Watch response:', response.data);
    res.json({ response });
});

修改/gmail-notifications端点代码

从通知请求中提取自定义标识,精准查询数据库:

app.post('/gmail-notifications', async (req, res) => {
    console.log('notification received');
    const notification = req.body;
    
    // 提取自定义标识:从payload或params中获取
    const decodedData = notification.message.data ? 
        JSON.parse(Buffer.from(notification.message.data, 'base64').toString('utf-8')) :
        {};
    const customId = decodedData.payload || notification.params?.customId;

    if (!customId) {
        console.log('未获取到自定义标识');
        return res.status(400).json({ error: 'Missing identifier' });
    }

    // 拆分包含的用户ID和集成邮箱
    const [userId, accountEmail] = customId.split('_');

    // 精准查询用户和对应的集成记录
    const currUser = await User.findById(userId);
    const gmailIntegration = currUser.appIntegrations
        .flatMap(app => app.integrationData)
        .find(data => data.emailUsed === accountEmail);
    
    if (!gmailIntegration) {
        console.log('未找到对应的集成记录');
        return res.status(404).json({ error: 'Integration not found' });
    }

    const tokensInDB = gmailIntegration.tokens;
    const deviceTokens = currUser.deviceTokens[0];

    oAuth2Client.setCredentials({
        refresh_token: tokensInDB.refresh_token,
    });

    const gmail = google.gmail({ version: 'v1', auth: oAuth2Client });

    const response = await gmail.users.history.list({
        userId: 'me',
        startHistoryId: gmailIntegration.historyId
    }).catch(err => console.log('gmailnotification error >>> ', err));

    // 后续邮件获取、通知推送逻辑保持不变...

    if (response?.data?.history) {
        // 执行fetchData逻辑
    } else {
        console.log('no new email >> ', response?.data);
    }

    res.json({ res: response });
});

方式二:在Webhook地址中添加查询参数

如果不想依赖Gmail API的内置参数,直接在webhook的URL中拼接唯一标识:

修改/watch-inbox/v2/端点的channel配置

const channel = {
    id: 'local-notifications',
    type: 'web_hook',
    // 在地址中加入用户ID和集成邮箱作为查询参数
    address: `https://random-number.ngrok-free.app/gmail-notifications?userId=${currUser._id}&accountEmail=${accountEmail}`,
};

修改/gmail-notifications端点获取参数

app.post('/gmail-notifications', async (req, res) => {
    // 从URL查询参数中直接获取标识
    const { userId, accountEmail } = req.query;
    
    const currUser = await User.findById(userId);
    const gmailIntegration = currUser.appIntegrations
        .flatMap(app => app.integrationData)
        .find(data => data.emailUsed === accountEmail);
    
    // 后续逻辑同上...
});

额外优化建议

  1. 查询逻辑修正:原代码中appIntegrations.find的条件错误,因为integrationData是数组,需要用flatMap展平后再查找,避免匹配失败。
  2. 标识安全性:如果担心明文暴露用户信息,可以对userId和accountEmail进行哈希处理,比如用crypto.createHash('sha256').update(${userId}_${accountEmail}).digest('hex')生成唯一哈希值,再传递该哈希作为标识。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 10:52:35