Overview
In most apps, notifications and alerts are an essential requirement. When dealing with updates to account information, such as email and password, it’s essential to notify users through different mediums about this change. Therefore, they can take immediate action if they do not author it.
In this tutorial, you will learn how to integrate Twilio with Firebase using the Send Messages with Twilio extension and send SMS and WhatsApp alerts whenever the password is updated.
What is Twilio?
twilio.com
Twilio offers different programmable communication tools for making and receiving phone calls and sending and receiving text messages, along with allowing third-party communication channels such as WhatsApp through its web service APIs.
You will not learn how to set up a Twilio account in this blog, so if you’re new to Twilio, check out their starting guide.
Getting started
Pre-requisites
- A Twilio account (a trial account would work).
- A Firebase project with email and password authentication enabled.
What will you build?
The sample in this tutorial uses Flutter for the front-end part, but it’s not the primary topic here. You can make your UI or apply your newly acquired knowledge to any existing projects.
This is a demonstration of our sample:
Putting it in steps:
- A user would sign up with an email and password; if new, they are asked to provide a phone number.
- Once in, they can update the password.
- A message will be sent to the user through SMS and WhatsApp informing them that the password has changed with the exact time and date.
Installing the “Sending Messages with Twilio” extension
extensions.dev
The first step is to set up the Twilio extension. This extension can ask Twilio to send a new SMS or WhatsApp message simply by creating a Firestore document.
There are two ways to install any Firebase Extension:
- Through the console: click here to install it.
- Through the Firebase CLI: Create a new directory for your Firebase project or navigate to any existing one, then run:
firebase ext:install twilio/send-message --local --project=projectId_or_alias
The CLI will walk you through installation by asking you to provide a couple of configurations, some of mandatory, others optional, or default values.
Twilio extension configurations
You need to obtain two credentials from your Twilio account to configure the extension: Account Sid and Auth Token. Both can be found in your Twilio console.
Once you have them, you can configure the Firebase extension as follows:
💡 Note that the collection name here is important, if you changed the default
messages
name, make sure to remember it as you will need for the rest of the tutorial.
The configuration is the same if you choose to configure it through the CLI.
The frontend part
The primary focus is not frontend, but you can play around with the Flutter sample.
Basically, there are 2 important methods in this sample:
- The sign in/up method.
The following code tries to sign the user in, if they have no account yet it will ask for a Phone Number. The phone number is then saved to Firestore. The document has the
uid
as the document ID.
final auth = FirebaseAuth.instance;
try {
await auth.signInWithEmailAndPassword(email: email, password: password);
} on FirebaseAuthException catch (e) {
if (e.code == 'user-not-found') {
try {
if (phoneNumberController.text.isEmpty) {
setState(() {
askForPhoneNumber = true;
});
} else {
final credential = await auth.createUserWithEmailAndPassword(email: email, password: password);
if (credential.user != null) {
try {
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.set({'phoneNumber': phoneNumber});
} catch (e) {
// TODO catch error
}
}
}
} catch (e) {
// TODO catch error
}
} else {
// TODO catch error
}
} catch (e) {
// TODO catch error
}
- The password update method.
Updating the password should update or set the
lastPasswordUpdate
in theuser
collection.
try {
final user = FirebaseAuth.instance.currentUser!;
await user.reauthenticateWithCredential(
EmailAuthProvider.credential(
email: user.email!,
password: oldPasswordController.text,
),
);
await user.updatePassword(newPasswordController.text);
// This will trigger our backend function to send an SMS & WhatsApp message
// to inform the user of the password change.
await FirebaseFirestore.instance
.collection('users')
.doc(user.uid)
.set({'lastPasswordUpdate': DateTime.now()}, SetOptions(merge: true));
} on FirebaseAuthException catch (e) {
log('$e', name: 'Error', error: e);
}
So the user document would look like this:
The backend part
The requirement in this sample is to send an SMS and WhatsApp once the password is updated. Therefore, you need an event trigger that can initiate the process. For that, you will use Cloud Functions.
Set-up Cloud Functions
Create a Firebase project directory, cd
into it, then run:
firebase init
Follow the installation process, and pick “Functions” when it asks about the features.
Writing the Cloud Function trigger
Cloud functions offer a lot of triggers for different events happening in Firebase, one of which is Firestore. You can watch a collection or a sub-collection and run some code when a document is created, updated, or deleted.
For our use case, you need to set a onUpdate
trigger. The snippet here uses TypeScript.
This trigger is watching the collection users
, which has a document for each user in your app.
import * as functions from "firebase-functions";
export const sendPasswordChangedAlert = functions.firestore
.document("users/{userId}")
.onUpdate(async (snap) => {
const user = snapshot.after.data();
const lastPasswordUpdate: admin.firestore.Timestamp =
user?.lastPasswordUpdate;
});
Anything inside the body of this function will be triggered once a document is updated in the users
collection. The argument snapshot
is an object returned by the function and contains two snapshots of the updated document: before
and after
.
We are interested in the data inside snapshot.after
, particularly the lastPasswordUpdate
attribute. This means whenever the user updates the password; you need to update the lastPasswordUpdate
attribute in Firestore. This is what will trigger the function.
What would happen if any other attribute is updated in a document inside users
? This function will be triggered as well. Therefore, you need to secure your trigger not to send messages for irrelevant updates.
const lastPasswordUpdate: admin.firestore.Timestamp = user?.lastPasswordUpdate;
const oldLastPasswordUpdate: admin.firestore.Timestamp =
snap.before.data()?.lastPasswordUpdate;
if (!lastPasswordUpdate || lastPasswordUpdate.isEqual(oldLastPasswordUpdate)) {
return;
}
Basically, this code will not proceed further if there’s no lastPasswordUpdate
, or if its value is equal to the one before the update, so you don’t trigger any message in case the password has not changed.
Next, the actual part where you will construct the message and trigger the extension. First, prepare the body of the message, with two variations, one for SMS and the other for WhatsApp.
const body =
"Your password has been updated on " +
lastPasswordUpdate.toDate().toString() +
", if you did not do this, please contact support immediately.";
const smsBody = {
to: `${user.phoneNumber}`,
body: body,
};
const whatsappBody = {
from: `whatsapp:+14155238886`,
to: `whatsapp:${user.phoneNumber}`,
body: body,
};
Twilio requires a prefix for WhatsApp numbers both in the to
and from
fields.
Now you will use the Firebase Admin SDK to add a new document to messages
collection. If you didn’t install it already, you could do so by running the following:
cd functions && npm i firebase-admin
Then add a new document to Firestore:
const messagesCollection = admin.firestore().collection("messages");
await messagesCollection.add(smsBody);
await messagesCollection.add(whatsappBody);
Putting it all together, the final code for the Cloud Function trigger would look like this:
import * as admin from "firebase-admin";
import * as functions from "firebase-functions";
admin.initializeApp();
export const sendPasswordChangedAlert = functions.firestore
.document("users/{userId}")
.onUpdate(async (snap) => {
const user = snap.after.data();
const lastPasswordUpdate: admin.firestore.Timestamp =
user?.lastPasswordUpdate;
const oldLastPasswordUpdate: admin.firestore.Timestamp =
snap.before.data()?.lastPasswordUpdate;
if (
!lastPasswordUpdate ||
lastPasswordUpdate.isEqual(oldLastPasswordUpdate)
) {
return;
}
const body =
"Your password has been updated on " +
lastPasswordUpdate.toDate().toString() +
", if you did not do this, please contact support immediately.";
const smsBody = {
to: `${user.phoneNumber}`,
body: body,
};
const whatsappBody = {
from: `whatsapp:${process.env.TWILIO_FROM_NUMBER}`,
to: `whatsapp:${user.phoneNumber}`,
body: body,
};
if (user.phoneNumber) {
const messagesCollection = admin.firestore().collection("messages");
await messagesCollection.add(smsBody);
await messagesCollection.add(whatsappBody);
}
});
Finally, make sure to deploy the function:
firebase deploy --only functions:sendPasswordChangedAlert
What happens when a new message is triggered?
Twilio automatically updates the document with information about the delivery status. You don’t need to go to the Twilio console to check the delivery status.
Using this extension in a non-Firebase backend
What if you have a backend written in another language and already have your triggers? You can still use this extension! 🔥
All you need is the Firebase Admin SDK, which is already supported in NodeJS, Python, Java, Go, and C#.
✨ Bounce: trigger an email
You can add Email to the list of your app notifications. The Trigger Email extension is easy to set up and works similarly. Read our tutorial on how to configure and use it.
After the extension is configured, adding it to your Cloud Function is fairly easy:
import * as admin from "firebase-admin";
import * as functions from "firebase-functions";
admin.initializeApp();
export const sendPasswordChangedAlert = functions.firestore
.document("users/{userId}")
.onUpdate(async (snap) => {
const user = snap.after.data();
const lastPasswordUpdate: admin.firestore.Timestamp =
user?.lastPasswordUpdate;
const oldLastPasswordUpdate: admin.firestore.Timestamp =
snap.before.data()?.lastPasswordUpdate;
if (
!lastPasswordUpdate ||
lastPasswordUpdate.isEqual(oldLastPasswordUpdate)
) {
return;
}
const body =
"Your password has been updated on " +
lastPasswordUpdate.toDate().toString() +
", if you did not do this, please contact support immediately.";
const smsBody = {
to: `${user.phoneNumber}`,
body: body,
};
const whatsappBody = {
from: `whatsapp:${process.env.TWILIO_FROM_NUMBER}`,
to: `whatsapp:${user.phoneNumber}`,
body: body,
};
if (user.phoneNumber) {
const messagesCollection = admin.firestore().collection("messages");
await messagesCollection.add(smsBody);
await messagesCollection.add(whatsappBody);
}
if (user.email) {
const mailCollection = admin.firestore().collection("mail");
await mailCollection.add({
to: [user.email],
message: {
subject: "Yor password has been changed",
html: "Your fancy HTML body goes here",
},
});
}
});
Wrapping up
This tutorial taught you about Twilio communication services and how to integrate them easily with Firebase through extensions. Click here to find the sample Cloud Function code.
There are plenty of other extensions you can use to make your life easier! Check out the directory of the available extensions here.
Stay tuned for more updates and exciting news that we will share in the future. Follow us on Invertase Twitter, Linkedin, and Youtube, and subscribe to our monthly newsletter to stay up-to-date. You may also join our Discord to have an instant conversation with us.