I’m creating a Typescript React Native app (with Expo) and am trying to scaffold an “invite a user feature” where you input a nickname and email, then that user will receive an email inviting them to join the app.
It works by creating a Firestore collection for invitations and mails
enter image description here
enter image description here
And then pushes them to Trigger Email from Firestore, which then pushes to Mailgun via SMTP.
The invitation and mail are being successfully created in Firestore, but I’m getting a timeout error on Google console and nothing is appearing in Mailgun.
Here are my console logs:
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
To expand on the timeout error:
{
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"
}
Here is the responsible code:
// index.ts
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
import {NodeMailgun} from "ts-mailgun";
admin.initializeApp();
export const myFunction = functions.runWith({
timeoutSeconds: 120,
memory: "256MB",
}).firestore.document("mail/{mailId}").onCreate((snap, context) => {
// function code here
});
const mailer = new NodeMailgun();
mailer.apiKey = "key";
mailer.domain = "domain.mailgun.org";
mailer.fromEmail =
"[email protected]";
mailer.fromTitle = "Name";
mailer.init();
export const sendInvitationEmail = functions.firestore
.document("mail/{mailId}")
.onCreate(async (snap, context) => {
const data = snap.data();
if (!data.to || !data.subject || !data.html) {
console.error("Required email fields are missing", data);
return;
}
try {
await mailer.send(data.to, data.subject, data.html);
console.log("Mail sent successfully");
// Optionally, update the 'invitationSent'
} catch (error) {
console.error("Error sending mail:", error);
// Optionally, handle failed email attempts
}
});
//FirebaseService.ts
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;