How to send email using firebase functions

Three different serverless email sending solutions that will work out in serveless, hosting and VPS deployed apps.

Choose your implementation type

 

When it comes to sending serverless email with firebase you have basically three options regardless how your app is deployed (or will be). Delegating email sending to firebase is a good choice in terms of security, message queuing and convenience. First consider which of the following solutions is the most suited for your app. I will explain each of them in more detail later in this article for the Node.js environment. 

 

Trigger email

 

Although it's a native extension in Google serverless architecture it's also the most demanding in implementation and least flexible. You will have to use a firestore database to send your emails and be at least on the blaze plan which is payable.

 

SMTP email service

 

This is a more flexible solution. It doesn't need a database to operate and you can also stay with a basic and totally free firebase plan. A big con of such implementation is security because of the necessity to pass raw credentials of your email to the firebase function and this can be potentially insecure. 

 

Cloud email service

 

It has the same flexibility as the previous type of implementation but it's more secure. The biggest con is an additional requirement to have a workable external cloud email service. On the other hand SendGrid service enables you to send up to 100 emails per day for free and it doesn't need a credit card.

 

 

Trigger

email

SMTP

 email

 service

Cloud

email

service

Config

medium,

needs Firestore

very easy

very easy,

needs workable

cloud email service

 Flexibility

medium,

relays on database

highhigh

Costs

depends on quotas

of firebase,

needs blaze plan upfront

depends on quotas

of firebase,

depends on quotas

of email cloud service provider

and firebase

Security

high,

may require raw

email credentials

medium,

external packages dependency,

requires raw email credentials

high,

external packages dependency,

requires only API key

Addons

only if used with

an email cloud provider

no

email analytics,

spam filters

email delivery tracking

ip blocks

 

Still don't know what to use?

 

I find that in most cases a cloud email service is the best match because of strong security (you don't pass the credentials of your email address) and extensive additional functionality like email delivery tracking or spam filters. That's why I cover it as the first option.

 

 

Option 1 - Cloud email service

 

REQUIREMENTS

  • a Firebase functions project
  • cloud email account like SendGrid or MailGun

 

Register and configure Sendgrid

 

Start with setting up a SendGrid account and after that register a single sender address from which you want to send your emails.

 

SendGrid single sender

 

 

When done go to the Email API tab and follow the integration guide for Web API. At the and you should get your unique API key. Don't accept verification yet. First make your implementation and deployment to the firebase then start the verification process, call the deployed function and accept the verification.

 

 

SendGrid Node.js installation

 

Install dependencies in the cloud

 

In your firebase project change directory to functions and install the necessary packages.

npm install --save @sendgrid/mail cors express

 

Add API key to Firebase config

firebase functions:config:set sendgrid.key="YourAPIKey"

 

Check if the key has been properly registered.

firebase functions:config:get

 

CREATE AN EMAIL SENDING FUNCTION

In the index.js create a sending function and handle CORS properly. In the code below you have a workable function with enabled CORS so it will block any requests beyond your app domains. But for the testing purposes it will be convenient to disable CORS (actually allowing all origins) using this snippet instead:

// cors options, only for the testing purposes
const corsOptions = {origin: true}

 

// functions/index.js

const functions = require('firebase-functions');
const express = require('express');
const sgMail = require('@sendgrid/mail')
const app = express();
const cors = require('cors');

// change to your domain/s
const whitelist = ['https://yourDomain.com', 'yourDomain.com']

const corsOptions = {
    origin: function (origin, callback) {
        if (whitelist.indexOf(origin) !== -1) {
            // eslint-disable-next-line callback-return
            callback(null, true)
        } else {
            // eslint-disable-next-line callback-return
            callback(new Error('Not allowed by CORS'))
        }
    }
}

app.get('/', cors(corsOptions), function (request, response, next) {

    sgMail.setApiKey(functions.config().sendgrid.key)
    
    const from = 'yourSingleSenderEmailAddrss' // Change to your verified sender
    
    const {to, subject, text} = request.query
    
    const msg = {
        to,
        from,
        subject,
        text,
        html: `<strong>${text}</strong>`,
    }
    
    sgMail
        .send(msg)
        // eslint-disable-next-line promise/always-return
        .then(() => {
            response.status(200).send('Email sent');
        })
        .catch((error) => {
            response.status(500).send(error);
        })
})
exports.email = functions.https.onRequest(app);

 

Deploy your code into the cloud.

firebase deploy

 

After deployment copy your function url from the console which triggers email sending. The url should end with function name, in this case it will be just ‘email’.

 

ACCEPT SENDGRID API IMPLEMENTATION

Get back to the SendGrid website with API guide and verify your implementation. I recommend using Postman. Just pass the function url (get method) and fill the query tab pass with subject, to and text properties. Start the verification process then call your email function and accept verification in the SendGrid panel.

 

Postman serverless email sending

 

The last thing is to change your CORS settings to more restrictive, just refer to the code above. From now one your won't be able to use Postman (so the others reaching your function endpoint).

 

 

Option 2 - SMTP email service

 

This is the easiest way of implementing serverless email sending but it can cause some trouble when using your email provider services through SMTP. Gmail for example is quite restrictive so you will first have to allow such delegation. 

 

REQUIREMENTS

  • a Firebase functions project
  • credentials to any email (i.e. Gmail)

 

LIFTING GMAIL RESTRICTIONS

Go to your gmail settings and find the following options. You have to enable access through less secure apps to make email sending possible.

After you deploy your code below and send a first message Google probably will send back you a warning that your app is accessing their gmail service. You will have to allow access to this connection, just follow their instructions.

 

Gmail external apps access restriction

 

Gmail lifting less secure app access restrictions

 

This is not the end yet. You will have to disable Captcha as well.

 

Gmail disable Captcha

 

INSTALL NODE MAILER AND OTHER DEPENDENCIES

In your firebase project change directory to functions and install the necessary packages.

npm install --save nodemailer cors express

 

ADD EMAIL CREDENTIALS TO THE FIREBASE CONFIG

firebase functions:config:set nodemailer.email="yourEmailAddress@gmail.com"

firebase functions:config:set nodemailer.password="yourEmailPassword"

 

Check if the credentials have been properly registered.

firebase functions:config:get

 

CREATE AN EMAIL SENDING FUNCTION

In the index.js create a sending function and handle CORS properly. In the code below you have a workable function with enabled CORS so it will block any requests beyond your app domains. But for the testing purposes it will be convenient to disable CORS (actually allowing all origins) using this snippet instead:

// cors options, only for the testing purposes
const corsOptions = {origin: true}

 

In the code below I used  Gmail, in case of a different email provider just change the service name in the transporter constant.

// functions/index.js

const functions = require('firebase-functions');
const express = require('express');

const nodemailer = require('nodemailer');
const app = express();
const cors = require('cors');

// change to your domain/s
const whitelist = ['https://yourDomain.com', 'yourDomain.com'] 
const corsOptions = {
    origin: function (origin, callback) {
        if (whitelist.indexOf(origin) !== -1) {
            // eslint-disable-next-line callback-return
            callback(null, true)
        } else {
            // eslint-disable-next-line callback-return
            callback(new Error('Not allowed by CORS'))
        }
    }
}

const transporter = nodemailer.createTransport({
    service: 'gmail',
    auth: {
        user: functions.config().nodemailer.email,
        pass: functions.config().nodemailer.password
    }
});
app.get('/', cors(corsOptions), function (request, response, next) {
    const from = 'yourSingleSenderEmailAddrss' // Change to your verified sender
    const {to, subject, text} = request.query
    const msg = {
        to,
        from,
        subject,
        text,
        html: `<strong>${text}</strong>`,
    }
    transporter
        .sendMail(msg)
        // eslint-disable-next-line promise/always-return
        .then(() => {
            response.status(200).send('Email sent');
        })
        .catch((error) => {
            response.status(200).send(error);
        })
})
exports.email = functions.https.onRequest(app);

 

Deploy your code into the cloud.

firebase deploy

 

After deployment read from the console the function url which you can further use in your app.

 

 

Option 3 -Trigger email

 

It's the least flexible solution of all presented because emails can be only sent when a data is inserted to one of your collections. 

This solution can be used with SMTP or cloud email providers like SendGrid. I recommend the latter for more security and additional functionality. Refer to the previous sections of the article that describe ways how to deal with potential Gmail SMTP restrictions if you need to. When use SendGrid you will have to use the SMTP Relay option instead of WEB API in the Email API tab to get your SMTP credentials.

 

REQUIREMENTS

  • a Firebase functions project
  • credentials to any email or email service provider
  • configured firestore
  • a firestore collection for emails (it will be created automatically)
  • the payable Blaze plan
  • Trigger Email extension
  • firebase admin package
  • optionally user and templates collections (they will be created automatically if needed)

 

 

INSTALL TRIGGER EMAIL EXTENSION

Go to your firebase project and change directory to the functions folder.

In order to install the extension through the console you need to know your project ID. The code below will get you a list of the projects and their corresponding ids.

firebase projects:list

 

Then install the extension and Grant Email Trigger access to the project when asked.

firebase ext:install firestore-send-email --project=yourProjectId

 

 

CONFIGURE THE EXTENSION

 

a) Function location

You will be asked for the location of your function, accept the default as it should be the nearest location to your firestore database.

 

b)  SMTP connection URI

It has the following placeholder.

smtps://username:password@smtp.hostname.com:port.

 

For Gmail check the official Gmail SMTP information (look at the outgoing mail server). 

// example uri

smtps://blowstack:password1234@smtp.gmail.com

 

For SendGrid check the SendGrid SMTP setup guide.

// example uri

smtps://apikey:SG.gs4mYklHTq4nSasadads2adfad@smtp.sendgrid.net

 

 

c) Email documents collection

Choose the name for your email collection.

 

d) Default email sender

Use email an address related to your data provided in SMTP credentials.

 

e) Default REPLY-TO address

Can be any address you want.

 

f) Users collection (Optional)

If you plan to utilize the user collection, then provide it's name here.

 

g) Templates collection (Optional)

If you plan to utilize the templates collection, then provide it's name here.

 

INSTALL REQUIRED ADMIN PACKAGE, EXPRESS AND CORS

 npm install --save firebase-admin cors express

 

 

CREATE A BASIC EMAIL SENDING FUNCTION

// functions/index.js

const functions = require('firebase-functions');
const express = require('express');
const admin = require('firebase-admin');
admin.initializeApp()
const app = express();
const cors = require('cors');
const corsOptions = {origin: true}

// Firebase Email Trigger
app.get('/', cors(corsOptions), function (request, response, next) {
    const {to, subject, text} = request.query
    try {
    admin.firestore().collection('mail').add({
        to,
        message: {
            subject,
            html: `<p>${text}</p>`,
        },
    })
        response.status(200).send('Email sent')
    }catch (err) {
        response.status(500).send(err)
    }

})
exports.email = functions.https.onRequest(app);

 

Deploy your code and test the function (url  will be printed in the console). Remember that when using SendGrid you will have to start verifying your API first then call the function and finally accept the verification process.

 

firebase deploy

 

If everything works, change CORS to more restrictive. I mentioned how this can be done in the previous implementation types and you can use the same code here.

 

BlowStack 2023