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

Firebase Cloud Function发送会议请求邮件遇权限拒绝问题求助

问题:Cloud Functions触发邮件发送时出现PERMISSION_DENIED错误

我正在开发一款Flutter应用,支持用户发起会议请求。当A用户向B用户发起请求时,B用户应收到提醒邮件。每次创建新请求时,meeting_requests集合会新增一则文档。我使用Google Cloud Functions结合nodemailer编写了以下代码,用于在请求创建时发送邮件:

const transporter = nodemailer.createTransport({  
  service: "gmail",  
  auth: {    
    user: <MY EMAIL HERE>,    
    pass: <MY APPS PASSWORD HERE>,  
  },
});

exports.sendMeetingNotification = functions.firestore    
  .document("meeting_requests/{docId}")    
  .onCreate(async (snap, context) => {      
    const data = snap.data();      
    const receiverId = data.receiverId;

    console.log("Fetching user data for receiverId:", receiverId);

    try {        
      const userDoc = await admin.firestore().collection("users").doc(receiverId).get();

      if (!userDoc.exists) {          
        console.log("No user found with the given receiverId:", receiverId);          
        return;        
      }

      const email = userDoc.data().email;

      console.log("Found user with email:", email);

      const mailOptions = {          
        from: "\"ConnectEd\" connected.app.contact@gmail.com",          
        to: email,          
        subject: "New Meeting Request from Teacher",          
        html: `        
        <div style="background-color: #f9f7cf; padding: 20px;         font-family: Arial, sans-serif; color: #333;&quot;">          
          <div style="text-align: center; margin-bottom: 20px;&quot;">            
            <img src="https://your-logo-url.com/logo.png" alt="ConnectEd Logo" style="width: 100px; height: auto;" />          
          </div>          
          <div style="background-color: #fff;           padding: 20px; border-radius: 8px;           box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);">            
            <h2 style="color: #2c3e50;            ">New Meeting Request from ${data.senderName}</h2>            
            <p style="font-size: 16px; line-height: 1.6;">              
              <strong>Note from teacher:</strong> ${data.note}            
            </p>            
            <p style="font-size: 16px; line-height: 1.6;">              
              Please click the link below to view the request:            
            </p>            
            <div style="text-align: center; margin: 20px 0;">              
              <a href="yourapp://app/openRequest?requestId=${context.params.docId}" style="background-color: #f39c12; color: #fff; padding: 10px 20px; text-decoration: none; border-radius: 5px; font-size: 16px;">View Request</a>            
            </div>          
          </div>          
          <div style="text-align: center;           margin-top: 20px; font-size: 12px; color: #999;">            
            <p>© 2024 ConnectEd. All rights reserved.</p>            
            <p>If you did not request this email, please ignore it.</p>          
          </div>        
        </div>        
        `,        
      };

      console.log("Sending email to:", email);

      await transporter.sendMail(mailOptions);        
      console.log("Email sent successfully to:", email);      
    } catch (error) {        
      console.error("Error sending email notification:", error);      
    }    
  });

但运行时邮件未发送,且日志中出现以下错误:

2024-09-07T14:37:34.038106Z ? sendMeetingNotification: Error sending email notification: Error: 7 PERMISSION_DENIED: Missing or insufficient permissions.
2024-09-07T14:37:34.038127Z ? sendMeetingNotification:     at callErrorFromStatus (/workspace/node_modules/@grpc/grpc-js/build/src/call.js:31:19)

2024-09-07T14:37:34.038251Z ? sendMeetingNotification:   code: 7,
2024-09-07T14:37:34.038256Z ? sendMeetingNotification:   details: 'Missing or insufficient permissions.',
s.',
2024-09-07T14:37:34.038261Z ? sendMeetingNotification:   metadata: Metadata {
2024-09-07T14:37:34.038266Z ? sendMeetingNotification:     internalRepr: Map(1) { 'x-debug-tracking-id' => [Array] },
d' => [Array] },
2024-09-07T14:37:34.038270Z ? sendMeetingNotification:     options: {}
2024-09-07T14:37:34.038274Z ? sendMeetingNotification:   }
2024-09-07T14:37:34.038274Z ? sendMeetingNotification:   }
2024-09-07T14:37:34.038279Z ? sendMeetingNotification: }
2024-09-07T14:37:34.039269068Z D sendMeetingNotification: Function execution took 54 ms, finished with status: 'ok'

昨天还能正常运行,今天突然失效,尝试调整Firebase权限、添加Gmail账号到云权限均无效,需要协助解决。


解决方案
  • 检查Cloud Functions服务账号的Firestore权限
    Cloud Functions默认使用[你的项目ID]@appspot.gserviceaccount.com这个服务账号执行。登录Google Cloud控制台的IAM页面,找到该账号,确保它拥有Cloud Datastore User角色(或更细粒度的roles/datastore.reader角色),这样才能读取users集合中的文档。

  • 验证Gmail应用密码的有效性

    1. 确认你的Google账号已开启两步验证,应用密码仅对开启2FA的账号有效。
    2. 检查应用密码是否正确,若最近重置过账号密码或重新生成过应用密码,需更新代码中的pass字段。
    3. 登录Google账号的安全设置页面,确认没有禁用应用密码的相关权限。
  • 调整Firestore安全规则
    确保Firestore规则允许服务账号访问users集合,修改规则如下:

    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
        match /users/{userId} {
          // 允许认证用户或服务账号读取
          allow read: if request.auth != null || request.serviceAccount;
          // 其他写入规则根据你的需求设置
          allow write: if request.auth.uid == userId;
        }
        match /meeting_requests/{docId} {
          allow create: if request.auth != null;
        }
      }
    }
    
  • 修复代码中的HTML语法错误
    代码中的HTML模板存在多余的&quot;转义字符,会导致HTML渲染异常,同时可能影响邮件发送的完整性。修正后的HTML部分如下:

    const mailOptions = {          
      from: "\"ConnectEd\" connected.app.contact@gmail.com",          
      to: email,          
      subject: "New Meeting Request from Teacher",          
      html: `        
      <div style="background-color: #f9f7cf; padding: 20px; font-family: Arial, sans-serif; color: #333;">          
        <div style="text-align: center; margin-bottom: 20px;">            
          <img src="https://your-logo-url.com/logo.png" alt="ConnectEd Logo" style="width: 100px; height: auto;" />          
        </div>          
        <div style="background-color: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);">            
          <h2 style="color: #2c3e50;">New Meeting Request from ${data.senderName}</h2>            
          <p style="font-size: 16px; line-height: 1.6;">              
            <strong>Note from teacher:</strong> ${data.note}            
          </p>            
          <p style="font-size: 16px; line-height: 1.6;">              
            Please click the link below to view the request:            
          </p>            
          <div style="text-align: center; margin: 20px 0;">              
            <a href="yourapp://app/openRequest?requestId=${context.params.docId}" style="background-color: #f39c12; color: #fff; padding: 10px 20px; text-decoration: none; border-radius: 5px; font-size: 16px;">View Request</a>            
          </div>          
        </div>          
        <div style="text-align: center; margin-top: 20px; font-size: 12px; color: #999;">            
          <p>© 2024 ConnectEd. All rights reserved.</p>            
          <p>If you did not request this email, please ignore it.</p>          
        </div>        
      </div>        
      `,        
    };
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 15:09:52