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 | SMTP service | Cloud service | |
---|---|---|---|
Config | medium, needs Firestore | very easy | very easy, needs workable cloud email service |
Flexibility | medium, relays on database | high | high |
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.

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.

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.

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.


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

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.