DEV Community

Cover image for Setting up an auto-email micro function for Firebase RTDB
Nathaniel Arfin
Nathaniel Arfin

Posted on

Setting up an auto-email micro function for Firebase RTDB

Introduction

I really like Firebase.

It helps me get projects launched quickly and easily with awesome App tooling, handing things like auth, database, and even storage.

Recently, I launched my personal website on my preferred FERN stack. I built out my form to submit messages to the /messages path of my database. This is great, it gives me a clean and organized way to store my messages, but the Firebase Console is pretty well unusable on mobile, and I want to make sure I’m getting back to requests quickly.

We’re going to take advantage of Firebase Cloud Functions to automatically email ourselves messages that get sent to the database. First, let’s figure out what this is:

Cloud Functions for Firebase is a serverless framework that lets you automatically run backend code in response to events triggered by Firebase features and HTTPS requests.

With Firebase, you can trigger functions from Alerts, Blocking Auth events, Auth events, Analytics, Firestore, Remote Config, Storage, Pub/Sub, Labs, and Realtime Database events!

Now, I seriously recommend reading those docs, because it really walks you through the process. If, instead, you want to follow along here, we can get started by setting up our environment and logging into our gcloud account.

This walkthrough assumes you have a firebase project with RTDB set up.

Getting Started

Hop into your favourite IDE, start a new project, and throw in an npm install -g firebase-tools firebase-admin. Now that we have the dependancies loaded, you can log in to your application. Hit it with firebase login and firebase init functions.

Your file structure should now look like:

myproject
|
+- firebase.json  # Describes properties for your project
|
+- functions/     # Directory containing all your functions code
      |
      +- .eslintrc.json  # Optional file containing rules for JavaScript linting.
      |
      +- package.json  # npm package file describing your Cloud Functions code
      |
      +- index.js      # Main source file for your Cloud Functions code</strong>
      |
      +- node_modules/ # Directory where your dependencies (declared in
                        # package.json) are installed
Enter fullscreen mode Exit fullscreen mode

If you pop in to index.js, you’ll see something that looks like this:

const functions = require("firebase-functions");

// // Create and deploy your first functions
// // https://firebase.google.com/docs/functions/get-started
//
// exports.helloWorld = functions.https.onRequest((request, response) => {
//   functions.logger.info("Hello logs!", {structuredData: true});
//   response.send("Hello from Firebase!");
// });
Enter fullscreen mode Exit fullscreen mode

So here we can see the basics of how Cloud Functions works. It accepts a request object and response function, and responds to the caller with the contents of the object passed into the response function.

Building our listener

Now, we aren’t using HTTP triggers, we’re using our RTDB. First, remove the commented section from index.js, and we’ll add the imports we’ll need, and set up the admin:

const admin = require ('firebase-admin');
const {onValueCreated} = require("firebase-functions/v2/database");

admin.initializeApp();
Enter fullscreen mode Exit fullscreen mode

Next, we specify the path we want to listen to:

const admin = require ('firebase-admin');
const {onValueCreated} = require("firebase-functions/v2/database");

admin.initializeApp();

const catchSubmission = onValueCreated("/messages/{uid}", (event) => {

});
Enter fullscreen mode Exit fullscreen mode

So here, we have specified that when a new value is created at messages/{uid}, it is passed into our function as the “event”.

Now, depending on how you shape your form submission, you’re going to handle this differently. I keep mine simple, with from , message, and replyto fields. We can grab these values by destructuring like this:

const admin = require ('firebase-admin');
const {onValueCreated} = require("firebase-functions/v2/database");

admin.initializeApp();

const catchSubmission = onValueCreated("/messages/{uid}", (event) => {
    const {from, message, replyTo} = event.data.val();
});
Enter fullscreen mode Exit fullscreen mode

Awesome! We’re now caught our message! Now we just have to shape it, and send it off.

Sending our email

For email, we’re going to use the very popular nodemailer package. Throw an npm install nodemailer and import it at the top of our function, and we’ll create our transporter instance:

const admin = require ('firebase-admin');
const {onValueCreated} = require("firebase-functions/v2/database");
const nodemailer = require('nodemailer');

admin.initializeApp();

const user = process.env.EMAIL;
const pass = process.env.PASSWORD

const transporter = nodemailer.createTransport({
    host: 'smtp.gmail.com',
    port: 465,
    secure: true,
    auth: {user,pass},
});

const catchSubmission = onValueCreated("/messages/{uid}", (event) => {
    const {from, message, replyTo} = event.data.val();
});
Enter fullscreen mode Exit fullscreen mode

There, we have specified our SMTP transporter. I am using Gmail, so I will have to use an App-specific password. I’m going to pass in the email address and password from the environment for flexibility.

Now, nodemailer is very versatile, but I really only need something simple. I’m going to define a simple email and send it off using the transporter’s sendMail!

Note: Gmail does not accept aliases in the “From” field.

const admin = require ('firebase-admin');
const {onValueCreated} = require("firebase-functions/v2/database");
const nodemailer = require('nodemailer');

admin.initializeApp();

const user = process.env.EMAIL;
const pass = process.env.PASSWORD

const transporter = nodemailer.createTransport({
    host: 'smtp.gmail.com',
    port: 465,
    secure: true,
    auth: {user,pass},
});

const catchSubmission = onValueCreated("/messages/{uid}", async (event) => {
    const {from, message, replyTo} = event.data.val();
    const email = transporter.sendMail({
        from,
        subject: \`A new message from ${from} through your website!\`,
        replyTo: \`${replyTo}\`,
        to: "your@email.com",
        text: message,
    });
});

Enter fullscreen mode Exit fullscreen mode

Great! now we’ve set up a function that listens to our messages route. We can wrap it up by responding back to the event with any changes we would like to make. In this case, let’s send the Message ID:

const admin = require ('firebase-admin');
const {onValueCreated} = require("firebase-functions/v2/database");
const nodemailer = require('nodemailer');

admin.initializeApp();

const user = process.env.EMAIL;
const pass = process.env.PASSWORD

const transporter = nodemailer.createTransport({
    host: 'smtp.gmail.com',
    port: 465,
    secure: true,
    auth: {user,pass},
});

const catchSubmission = onValueCreated("/messages/{uid}", async (event) => {
    const {from, message, replyTo} = event.data.val();
    const email = transporter.sendMail({
        from: user,
        subject: \`A new message from ${from} through your website!\`,
        replyTo: \`${replyTo}\`,
        to: "your@email.com",
        text: message,
    });
    return event.data.ref.update({emailId: email.messageId})
});
Enter fullscreen mode Exit fullscreen mode

With that set up, we can export this as a module, and get ready to deploy!

const admin = require ('firebase-admin');
const {onValueCreated} = require("firebase-functions/v2/database");
const nodemailer = require('nodemailer');

admin.initializeApp();

const user = process.env.EMAIL;
const pass = process.env.PASSWORD

const transporter = nodemailer.createTransport({
    host: 'smtp.gmail.com',
    port: 465,
    secure: true,
    auth: {user,pass},
});

const catchSubmission = onValueCreated("/messages/{uid}", async (event) => {
    const {from, message, replyTo} = event.data.val();
    const email = transporter.sendMail({
        from: user,
        subject: \`A new message from ${from} through your website!\`,
        replyTo: \`${replyTo}\`,
        to: "your@email.com",
        text: message,
    });
    return event.data.ref.update({emailId: email.messageId})
});

exports.emailer = catchSubmission;
Enter fullscreen mode Exit fullscreen mode

Deployment

Deploying a Firebase function is incredibly easy. In our case, because we used the process.env methodology, we can define our variables in .env. I don’t recommend storing passwords in .env, make use of a secrets manager.

Once you’re ready, throw firebase deploy --only functions into your console, and you should be all set!

Wrapping Up

In this quick article, we went over how to set up a cloud function that listens for a write at a specific location, we created an SMTP transporter for sending emails, and we deployed our micro-function!

Now, we’re able to programmatically email ourselves form submissions to our webforms using Firestore Realtime Database and nodemailer.

Top comments (0)