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?