Sending emails with Firebase Cloud Functions

September 2, 2019

Firebase functions are a lot of fun. They allow you to uncouple business logic from your app.

Instead of running computationally expensive tasks on your server you can leverage firebase functions.

Here's a quick and dirty guide to using Firebase functions to send welcome emails.

var functions = require('firebase-functions')
var mailgun = require('mailgun-js')({apiKey, domain})
exports.sendWelcomeEmail = functions.database.ref('users/{uid}').onWrite(event => {
  // only trigger for new users [event.data.previous.exists()]
  // do not trigger on delete [!event.data.exists()]
  if (!event.data.exists() || event.data.previous.exists()) {
    return
  }
  var user = event.data.val()
  var {email} = user
  var data = {
    from: 'app@app.com',
    subject: 'Welcome!',
    html: `<p>Welcome! ${user.name}</p>`,
    'h:Reply-To': 'app@app.com',
    to: email
  }
  mailgun.messages().send(data, function (error, body) {
    console.log(body)
  })
})

In the above code snippet, we use the transactional service Mailgun to send a welcome email. We're also using the node package mailgun-js, a wrapper around Mailgun's API.

Things to note:

  • This function will only execute once per user - which we can change if needed.
  • We might want to add a callback to our firebase server if the email is sent successfully.
  • apiKey - this is your Mailgun API key.
  • domain - this is your domain name registered with Mailgun.

Subscribing new users to receive emails

Let's say that instead of sending one welcome email, we want to initiate a drip campaign.

In that case, we could write to our firebase database and update a field (like { subscribed: true }).

One thing to be mindful of is cloud functions that write to the same database path that triggers them. If you're not careful you might induce an infinite loop.

var functions = require('firebase-functions');
var admin = require('firebase-admin')
admin.initializeApp(functions.config().firebase);
var mailgun = require('mailgun-js')({apiKey, domain})
exports.subscribeUser = functions.database.ref('users/{uid}').onWrite(event => {
  var user = event.data.val()
  var {uid} = event.params
  // Exit if the user was deleted or the user is currently subscribed
  if (!event.data.exists() || user.subscribed ) {
    return
  }
  let members = {
    address: user.email
  }
  var listId = 'your mailgun list id'
  // Add the user to our mailgun list
  return mailgun.lists(listId).members().add({members, subscribed: true}, function (err, body) {
    if(!err) {
      return admin.database().ref('users/' + uid + '/subscribed').set(true)
    }
  })  
})

Here's what we did in the snippet above:

  • Make sure that the functions doesn't trigger on delete (!event.data.exists())
  • Make sure that the function doesn't trigger if the user is already subscribed
  • Subscribe the user to a list using the mailgun API
  • use firebase admin to update the value at users/{uid}/subscribed to true.

Sending Emails With Mailchimp

Instead of using a transactional email service like Mailgun or Sendgrid, you could also use Mailchimp (which is just a user-friendly wrapper around Mailgun).

Here's how you'd use Mailchimp's API.

var fetch = require('isomorphic-fetch')
var btoa = require('btoa');
// POST /lists/{list_id}/members
// Add a new list member
function createFetch () {  
  var MAILCHIMP_API_KEY = ''
  // NOTE: mailchimp's API uri differs depending on your location. us6 is the east coast. 
  var url = 'https://us6.api.mailchimp.com/3.0/lists/' + listId + '/members'
  var method = 'POST'
  var headers = {
    'authorization': "Basic " + btoa('randomstring:' + MAILCHIMP_API_KEY),
    'Accept': 'application/json',
    'Content-Type': 'application/json'
  }
  var body = JSON.stringify({email_address: user.email, status: 'subscribed'})
  return fetch(url, {
    method,
    headers,
    body
  }).then(resp => resp.json())
  .then(resp => {
    console.log(resp)
  })
}

Notes

  • You'll need to npm i btoa --save for this to work. btoa binary encodes data to base 64-encoded ascii.
  • Mailchimp's API is really well documented.

Deploying Firebase Functions

npm i firebase-tools -g # install firebase cli
cd my-project 
firebase login
firebase init functions

Now add your code to ./functions/index.js:

exports.sendEmail = ...

And npm install any required packages. (firebase-functions and firebase-admin should be preinstalled after firebase init functions).

Finally deploy with:

firebase deploy --only functions

Gotchas

  • You'll need to enable billing at https://google.firebase.com for your project to make external API requests. If billing is not enabled you'll get an opaque error.
  • Remember that if you're using ES6 syntax you'll need to transpile it with babel.

Resources