Dennish
Dennish

Reputation: 116

How to use a hbs template on Firebase Storage with nodemailer-express-handlebars in a database onCreate trigger function?

I am trying to use a handlebars template file uploaded to my firebase project storage (appId.appspot.com/templates/testTemplate.hbs) with nodemailer to send an email when an onCreate function is triggered on a realtime database node.

I can send emails successfully using html formatted string but really need to use a template to add dynamic data into the email.

Here my function:

import * as functions from "firebase-functions";
const admin = require("firebase-admin");
const hbs = require("nodemailer-express-handlebars");
const nodemailer = require("nodemailer");

const smtpConfig = {
  host: "mailHost",
  port: 465,
  secure: true,
  auth: {
    user: "xxxxxxxx",
    pass: "xxxxxxxx"
  }
};

const transporter = nodemailer.createTransport(smtpConfig);

exports.sendEmail = functions.database
  .ref("/databasePath/{pushId}")
  .onCreate(async (snapshot, context) => {
    const userData = snapshot.val();
    admin.initializeApp({
      storageBucket: "appId.appspot.com"
    });
    const bucket = admin.storage().bucket();
    const templatesFolder = bucket.name + "/templates/"; // path to storage folder with templates

    transporter.use(
      "compile",
      hbs({
        viewPath: templatesFolder,
        extName: ".hbs"
      })
    );
    const uniqueCode = "generated by a function";
    const uniqueLink = "https://appId.firebaseapp.com/?id=" + uniqueCode;
    const message = {
      from: "fromEmail",
      to: "toEmail",
      subject: "Subject",
      template: "testTemplate", // name of the template file
      context: {
        user: "User name",
        link: uniqueLink
      }
    };

    try {
      await transporter.sendMail(message);
      console.log("Email sent to:", "toEmail");
    } catch (error) {
      console.error("Error sending email:", error);
    }
    return null;
  });

When the function is triggered I get the following error in the logs:

There was an error while sending the email: { Error: ENOENT: no such file or directory, open '/user_code/appId.appspot.com/templates/testTemplate.hbs' at Error (native) errno: -2, code: 'ENOENT', syscall: 'open', path: '/user_code/appId.appspot.com/templates/testTemplate.hbs' }

The bucket.name has '/user_code' at the start so hbs can't find the template. How can I get the right path to the templates folder?

Upvotes: 0

Views: 1187

Answers (2)

Dennish
Dennish

Reputation: 116

Here's the updated function:

import * as functions from "firebase-functions";
const admin = require("firebase-admin");
const hbs = require("nodemailer-express-handlebars");
const nodemailer = require("nodemailer");

const smtpConfig = {
  host: "mailHost",
  port: 465,
  secure: true,
  auth: {
    user: "xxxxxxxx",
    pass: "xxxxxxxx"
  }
};

const transporter = nodemailer.createTransport(smtpConfig);

exports.sendEmail = functions.database
  .ref("/databasePath/{pushId}")
  .onCreate(async (snapshot, context) => {
    const userData = snapshot.val();
    const templatesFolder = __dirname + "/templates"; // <--

    transporter.use(
      "compile",
      hbs({
        viewPath: templatesFolder,
        extName: ".handlebars"
      })
    );
    const uniqueCode = "generated by a function";
    const uniqueLink = "https://appId.firebaseapp.com/?id=" + uniqueCode;
    const message = {
      from: "fromEmail",
      to: userData.email, // from the snapshot
      subject: "Subject",
      template: "testTemplate", // name of the template file
      context: {
        user: userDate.name, // from the snapshot
        link: uniqueLink
      }
    };

    try {
      await transporter.sendMail(message);
      console.log("Email sent to:", userData.email);
    } catch (error) {
      console.error("Error sending email:", error);
    }
    return null;
  });

Add the template files to "functions/lib/templates/testTemplate.handlebars"

Upvotes: 0

Doug Stevenson
Doug Stevenson

Reputation: 317760

It doesn't look like you haven't actually written any code that downloads a file from Cloud Storage. You can't just build a path to a file in Cloud Storage, pass it off to some other component, and hope it just knows what to do with the path. All you've done is pass it the name of a local file that doesn't exist. You're going to have to actually download the file to a temp folder in order to make use of it locally.

Or better yet, just skip Cloud Storage and deploy the template along with your functions. You can just read the file directly off disk at no additional cost. (Each Cloud Storage download costs money.)

Upvotes: 0

Related Questions