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

TypeScript React Native集成Mailgun与Firestore触发邮件超时问题排查

Expo TypeScript React Native 邀请功能邮件发送超时排查

我正在开发一款基于Expo的TypeScript React Native应用,搭建「邀请用户」功能:输入昵称和邮箱后,受邀用户会收到加入应用的邮件。实现逻辑为创建Firestore的invitations和mail集合,将数据推送到「Trigger Email from Firestore」扩展,再通过SMTP转发至Mailgun。目前Firestore中已成功生成邀请记录和邮件文档,但Google控制台出现超时错误,且Mailgun无任何相关记录。

控制台日志

INFO 2024-04-23T18:51:39.332357Z [resource.labels.functionName: ext-firestore-send-email-processQueue] Initializing extension with configuration
DEBUG 2024-04-23T18:51:39.402786533Z [resource.labels.functionName: ext-firestore-send-email-processQueue] [labels.executionId: y8j24zp92do1] Function execution started
INFO 2024-04-23T18:51:39.645436Z [resource.labels.functionName: ext-firestore-send-email-processQueue] [labels.executionId: y8j24zp92do1] Started execution of extension with configuration
INFO 2024-04-23T18:51:43.879766Z [resource.labels.functionName: ext-firestore-send-email-processQueue] [labels.executionId: y8j24zp92do1] Completed execution of extension
DEBUG 2024-04-23T18:51:43.883830637Z [resource.labels.functionName: ext-firestore-send-email-processQueue] [labels.executionId: y8j24zp92do1] Function execution took 4481 ms, finished with status: 'ok'
DEBUG 2024-04-23T18:52:35.972814575Z [resource.labels.functionName: ext-firestore-send-email-processQueue] [labels.executionId: yfsqgcwx6njq] Function execution took 59999 ms, finished with status: 'timeout'
INFO 2024-04-23T18:52:46.694433Z [resource.labels.functionName: ext-firestore-send-email-processQueue] Initializing extension with configuration

超时错误详情

{
  insertId: "15ci506ffa9z6x",
  labels: {2},
  logName: "projects/icecreamsync/logs/cloudfunctions.googleapis.com%2Fcloud-functions",
  receiveTimestamp: "2024-04-23T18:52:35.983830631Z",
  resource: {2},
  severity: "DEBUG",
  textPayload: "Function execution took 59999 ms, finished with status: 'timeout'",
  timestamp: "2024-04-23T18:52:35.972814575Z",
  trace: "projects/icecreamsync/traces/651c7307ad3a2528ac08440baec5accb"
}

相关代码

index.ts(云函数)

import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
import { NodeMailgun } from "ts-mailgun";

admin.initializeApp();  

// Mailgun instance
const mailer = new NodeMailgun();
mailer.apiKey = "api";
mailer.domain = "sandboxcxxxxx.mailgun.org";
mailer.fromEmail = "noreply@xxxxx.mailgun.org";
mailer.fromTitle = "App Name";
mailer.init();

// Cloud Function to send an email 
export const sendEmailOnCreate = functions.runWith({
  timeoutSeconds: 120,  // Set higher timeout
  memory: '256MB'       // Set adequate memory
}).firestore.document("mail/{mailId}").onCreate(async (snap, context) => {
  const mailData = snap.data();

  if (!mailData.to || !mailData.subject || !mailData.html) {
    console.error("Required email fields are missing", mailData);
    return null;  // terminate function if essential fields are missing
  }

  try {
    await mailer.send(mailData.to, mailData.subject, mailData.html);
    console.log("Mail sent successfully");
    return null;
  } catch (error) {
    console.error("Failed to send email:", error);
    return null;  // consider logging this error to a persistent store
  }
});

FirebaseService.ts(Firestore操作)

import { db } from '../firebaseConfig';
import { collection, addDoc, serverTimestamp } from 'firebase/firestore';

export async function inviteFriend(friendNickname: string, friendEmail: string, userId: string): Promise<string> {
    const invitation = {
        nickname: friendNickname,
        email: friendEmail,
        invitationSent: true, // Set to true assuming email will be sent
        status: 'pending',
        invitedBy: userId,
        createdAt: serverTimestamp()
    };

    try {
        const docRef = await addDoc(collection(db, "invitations"), invitation);
        console.log("Invitation created with ID: ", docRef.id);

        // Create a document in the mail collection expected by the Trigger Email extension
        const emailContent = {
            to: friendEmail,
            message: {
                subject: "You've been invited!",
                html: `<p>Hello ${friendNickname},</p><p>You have been invited by ${userId}. Click here to accept the invitation.</p>`
            }
        };
        await addDoc(collection(db, "mail"), emailContent);

        return docRef.id;
    } catch (error) {
        console.error("Error creating invitation and sending email:", error);
        throw error;
    }
}

SettingsScreen.tsx(前端界面)

import React, { useState } from 'react';
import { View, TextInput, Button, Text, Alert, StyleSheet } from 'react-native';
import { inviteFriend } from '../services/firebaseService';

const SettingsScreen: React.FC = () => {
    const [email, setEmail] = useState<string>('');
    const [nickname, setNickname] = useState<string>('');
  
    const handleInvite = async () => {
      const userId = "user123";  // This should ideally be fetched from your auth state
      try {
        // Pass UserID here 
        const invitationId = await inviteFriend(nickname, email, userId);
        Alert.alert("Success", `Invitation sent successfully! ID: ${invitationId}`);
      } catch (error) {
        console.error("Failed to send invitation:", error);
        Alert.alert("Error", "Failed to send invitation.");
      }
    };
  
    return (
      <View style={styles.container}>
        <Text>Invite a Friend</Text>
        <TextInput
          style={styles.input}
          placeholder="Nickname"
          value={nickname}
          onChangeText={setNickname}
        />
        <TextInput
          style={styles.input}
          placeholder="Email"
          value={email}
          onChangeText={setEmail}
        />
        <Button title="Invite" onPress={handleInvite} />
      </View>
    );
  };
  
  const styles = StyleSheet.create({
    container: {
      flex: 1,
      justifyContent: 'center',
      padding: 20
    },
    input: {
      height: 40,
      borderColor: 'gray',
      borderWidth: 1,
      marginBottom: 10,
      padding: 10
    }
  });
  
export default SettingsScreen;

排查与修复方案

1. 移除自定义云函数与官方扩展的冲突

你同时启用了「Trigger Email from Firestore」官方扩展,又自定义了sendEmailOnCreate云函数监听mail集合创建事件,两者会同时触发导致资源竞争,直接引发扩展函数超时。

修复:删除自定义的sendEmailOnCreate云函数,完全依赖官方扩展处理邮件发送逻辑。

2. 修正Mailgun配置问题

  • 自定义云函数中mailer.apiKey填写的是无效值"api",如果继续使用自定义函数,需替换为真实的Mailgun API密钥;若使用官方扩展,需在扩展配置页面正确填写Mailgun的SMTP或API认证信息。
  • 确认Mailgun域名、发件邮箱已在Mailgun后台完成验证,避免邮件被拦截或无法发送。

3. 验证邮件文档结构

官方「Trigger Email from Firestore」扩展要求的mail文档结构是正确的(你当前的emailContent结构符合要求),但需确保没有其他字段干扰扩展解析,比如不要额外添加扩展不识别的字段。

4. 扩展日志排查

在Firebase控制台的「扩展」页面,查看「Trigger Email from Firestore」的运行日志,确认超时具体原因是SMTP连接失败、API调用错误还是权限问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 03:19:50