Mailgun integration with Firebase email trigger extension times out in TypeScript React Native project

I’m working on a TypeScript React Native app using Expo and trying to build a user invitation system. The idea is simple - enter a username and email address, then send an invitation email to that person.

My setup uses Firebase Firestore to store invitation data and email queue documents. I’m using the “Trigger Email from Firestore” extension which should connect to Mailgun through SMTP to actually send the emails.

The problem is that while the invitation records and email documents are being created correctly in Firestore, the function keeps timing out and no emails show up in my Mailgun dashboard.

Here are the relevant logs from Google Cloud Console:

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] Function execution started
INFO 2024-04-23T18:51:39.645436Z [resource.labels.functionName: ext-firestore-send-email-processQueue] Started execution of extension with configuration
INFO 2024-04-23T18:51:43.879766Z [resource.labels.functionName: ext-firestore-send-email-processQueue] Completed execution of extension
DEBUG 2024-04-23T18:51:43.883830637Z [resource.labels.functionName: ext-firestore-send-email-processQueue] Function execution took 4481 ms, finished with status: 'ok'
DEBUG 2024-04-23T18:52:35.972814575Z [resource.labels.functionName: ext-firestore-send-email-processQueue] 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

Here’s my Cloud Function code:

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

admin.initializeApp();

// Configure Mailgun
const mailService = new NodeMailgun();
mailService.apiKey = "api";
mailService.domain = "sandboxcxxxxx.mailgun.org";
mailService.fromEmail = "[email protected]";
mailService.fromTitle = "MyApp";
mailService.init();

// Function to handle email sending
export const handleEmailSend = functions.runWith({
  timeoutSeconds: 120,
  memory: '256MB'
}).firestore.document("emails/{emailId}").onCreate(async (snapshot, context) => {
  const emailData = snapshot.data();

  if (!emailData.recipient || !emailData.title || !emailData.content) {
    console.error("Missing required email data", emailData);
    return null;
  }

  try {
    await mailService.send(emailData.recipient, emailData.title, emailData.content);
    console.log("Email delivered successfully");
    return null;
  } catch (error) {
    console.error("Email delivery failed:", error);
    return null;
  }
});
// DatabaseService.ts
import { db } from '../config/firebase';
import { collection, addDoc, serverTimestamp } from 'firebase/firestore';

export async function sendInvitation(username: string, userEmail: string, senderId: string): Promise<string> {
    const inviteData = {
        username: username,
        email: userEmail,
        sent: true,
        state: 'waiting',
        sender: senderId,
        timestamp: serverTimestamp()
    };

    try {
        const docRef = await addDoc(collection(db, "invites"), inviteData);
        console.log("Invite record created with ID: ", docRef.id);

        // Add email to queue
        const emailData = {
            recipient: userEmail,
            template: {
                title: "Join our app!",
                content: `<p>Hi ${username},</p><p>You've been invited by ${senderId} to join our app. Click here to get started.</p>`
            }
        };
        await addDoc(collection(db, "emails"), emailData);

        return docRef.id;
    } catch (error) {
        console.error("Error processing invitation:", error);
        throw error;
    }
}
// InviteScreen.tsx
import React, { useState } from 'react';
import { View, TextInput, Button, Text, Alert, StyleSheet } from 'react-native';
import { sendInvitation } from '../services/DatabaseService';

const InviteScreen: React.FC = () => {
    const [emailAddress, setEmailAddress] = useState<string>('');
    const [username, setUsername] = useState<string>('');
  
    const processInvite = async () => {
      const currentUser = "user456";
      try {
        const inviteId = await sendInvitation(username, emailAddress, currentUser);
        Alert.alert("Done", `Invitation processed! ID: ${inviteId}`);
      } catch (error) {
        console.error("Invitation failed:", error);
        Alert.alert("Failed", "Could not send invitation.");
      }
    };
  
    return (
      <View style={styles.wrapper}>
        <Text>Send Invitation</Text>
        <TextInput
          style={styles.textField}
          placeholder="Username"
          value={username}
          onChangeText={setUsername}
        />
        <TextInput
          style={styles.textField}
          placeholder="Email Address"
          value={emailAddress}
          onChangeText={setEmailAddress}
        />
        <Button title="Send Invite" onPress={processInvite} />
      </View>
    );
  };
  
  const styles = StyleSheet.create({
    wrapper: {
      flex: 1,
      justifyContent: 'center',
      padding: 20
    },
    textField: {
      height: 40,
      borderColor: 'gray',
      borderWidth: 1,
      marginBottom: 10,
      padding: 10
    }
  });
  
export default InviteScreen;

What could be causing these timeout issues with the Mailgun connection?

your mailgun API key config is wrong - you’ve got “api” hardcoded instead of the actual key. the extension probably needs different SMTP settings than what ts-mailgun uses. i’d ditch your custom function and just use the extension with proper mailgun SMTP settings in the firebase console.

Your issue is with the Mailgun SMTP settings in your Firebase extension config, not your code. For the Trigger Email extension with Mailgun, you need to set up SMTP through the Firebase console: use smtp.mailgun.org as host, port 587, and your actual Mailgun SMTP username/password - not the API key you’re using in your custom function. The extension wants SMTP credentials, not API keys. Also double-check that your Mailgun domain is verified and not sandboxed - sandbox domains have sending restrictions that cause silent failures. The timeout means the extension can’t authenticate with SMTP, so it keeps retrying until it gives up. You can remove your custom handleEmailSend function since the extension automatically handles sending when you add documents to the emails collection.

Based on your logs, I think your custom Cloud Function is conflicting with the Firebase extension. You’ve got your own handleEmailSend function running alongside the “Trigger Email from Firestore” extension - they’re both trying to process the same documents at once, which creates race conditions and timeouts.

The Firebase extension expects a specific document structure in your emails collection, but your DatabaseService.ts creates documents with a nested template object instead of the flat structure it needs. Restructure your email document creation like this:

const emailData = {
    to: userEmail,
    message: {
        subject: "Join our app!",
        html: `<p>Hi ${username},</p><p>You've been invited by ${senderId} to join our app.</p>`
    }
};

Ditch your custom handleEmailSend function completely - the extension already handles this. The timeout happens because both your function and the extension are fighting over the same email documents.