← Back to blog

Send account updates to Firebase users using Twilio

Mais Alheraki

Open Source Engineer

12th October, 2022

Need a custom tool? Invertase can help

Tired of investing in off-the-shelf software that doesn't quite fit your business? Invertase can build solutions tailored to your exact needs. Tell us what you're looking for here and we'll be in touch.

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

  1. A Twilio account (a trial account would work).
  2. 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:

  1. A user would sign up with an email and password; if new, they are asked to provide a phone number.
  2. Once in, they can update the password.
  3. 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:

  1. Through the console: click here to install it.
  2. 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.

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.

Configuration in console

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:

  1. 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
}
  1. The password update method. Updating the password should update or set the lastPasswordUpdate in the user 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:

User document

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.

WhatsApp document in Firestore

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 TwitterLinkedin, 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.

Mais Alheraki

Open Source Engineer

Categories

Tags