Setting up an auto-email micro function for Firebase RTDB
Tags: javascript, webdev, tutorial, node
Published: 2023-05-25 20:39:27

## Introduction

[I really like Firebase](https://dev.to/wra-sol/series/22717). 

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](https://nathanielarfin.com) 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](https://firebase.google.com/docs/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](https://firebase.google.com/docs/functions/database-events?gen=2nd)**!

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:

```jsx
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
```

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

```jsx
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!");
// });
```

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:

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

admin.initializeApp();
```

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

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

admin.initializeApp();

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

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:

```jsx
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();
});
```

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](https://www.npmjs.com/package/nodemailer) package. Throw an `npm install nodemailer` and import it at the top of our function, and we’ll create our transporter instance:

```jsx
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();
});
```

There, we have specified our SMTP transporter. I am using Gmail, so I will have to use an [App-specific password](https://support.google.com/accounts/answer/185833?hl=en). I’m going to pass in the email address and password from the environment for flexibility.

Now, nodemailer is [very versatile](https://nodemailer.com/about/), 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.*

```jsx
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,
    });
});

```

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:

```jsx
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})
});
```

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

```jsx
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;
```

## 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.