Nodemailer not working when used with Firebase Functions - javascript

I'm trying to send myself an email when a new user account is made in my web app. Here is the current code I'm deploying to Firebase Functions:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const nodemailer = require("nodemailer");
admin.initializeApp();
require("dotenv").config();
const {
SENDER_EMAIL,
SENDER_PASSWORD
} = process.env;
exports.sendEmailNotification = functions.firestore.document("users/{userId}").onCreate(async (snapshot, context) => {
const data = snapshot.data();
// create reusable transporter object using the default SMTP transport
let transporter = nodemailer.createTransport({
host: "smtp.gmail.com",
port: 465,
secure: true,
auth: {
user: SENDER_EMAIL,
pass: SENDER_PASSWORD,
},
});
// send mail with defined transport object
let info = await transporter.sendMail({
from: `"MY_APP_NAME" <${SENDER_EMAIL}>`,
to: "MY_PERSONAL_GMAIL_ACCOUNT",
subject: `A New User Has Joined MY_APP_NAME!`,
text: `A new user has joined MY_APP_NAME. Name: ${data.name}, email: ${data.email}`
});
})
Running a similar version on my machine using the node index.js command works no problem. The problem seems to be when it runs on Firebase Functions.

According to your current code, it seems to be working correctly, as you said, it only works fine in your machine using the node.js.
Probably the guide to follow to get the same result is the Nodemailer as a module for Node.js.
Now, if you would like to do it using Cloud Functions for Firebase, I can highly recommend you to follow the Send Email Using Firebase Functions & Nodemailer guide.
Additional guide for Send Email with Firebase functions and Nodemailer.

For me, it was failing because it wasn't importing nodemailer as I did not install it in the actual functions folder.
If you are installing nodemailer or any package please make sure that you are installing it in the actual functions folder.
You can confirm if it was installed if you check package.json and see that the dependencies have nodemailer in it.

Related

How to Using Nodemailer

I want to ask how to using nodemailer with dynamic email and pass from database
export const mailTransporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: email_user,
pass: email_pass,
},
});
email_user and email_pass from file env, I want to email_user and email_pass from the database, so I think to create a function for getting value email and pass from the database, then save into variable and use in mailTransport. Guys any suggestion or opinion for it?
Wrap your nodemailer.createTransport into a function before you export it, then from the function, you get the credential from DB before constructing the nodemailer.createTransport.
module.exports = createTransportWithCredential
function createTransportWithCredential(){
return new Promise((resolve,reject)=>{
//get credential from DB, you may need to use promise-then to handle async situation
//example:
getCredentialFromDB().then(credentials=>{
let transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: credential.user,
pass: credential.password,
},
});
resolve(transporter)
})
})
}
From the other js file you can do:
const mailer = require("./nodemailer")
mailer.createTransportWithCredential().then(transporter=>{
//use the transporter
})
Depends on what type of database you have. If you're using mysql you can use the mysql2 package to make queries. It looks like this.
I recommend creating a simple package outside of your main project but this is not completely necessary.
npm init
npm install dotenv mysql2
require('dotenv').config({ path: "/home/ubuntu/.env" });
const mysql = require("mysql2/promise");
const connection = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: "main",
flags: "-FOUND_ROWS",
charset: "utf8mb4_0900_ai_ci",
multipleStatements: true,
connectionLimit: 10,
queueLimit: 0
});
module.exports = connection;
Edit the options as you like. This one creates a pool of 10 connections and connects to a database named main. It also allows for multiple statements within a single query. That might not be desirable so turn that off if you'd like. Finally, I'm requiring an environment file, but I am specifying a specific location rather than getting one automatically from within the project folder (since this is its own package that will be imported into the main project).
Next we import the database package into our main project.
Just follow this page to install a local package Installing a local module using npm?
It should be something like this while inside your main project directory.
npm install ../database
if your database package is located next to your main project folder. Just replace ../database with whatever the path is to the separate database package.
Now inside our main project, it would look something like this. (I'm assuming you labelled your new package database but if not, just replace with whatever name you used.
require('dotenv').config({ path: "/home/ubuntu/.env" });
const connection = require("database");
const userID = "81cae194-52bd-42d3-9554-66385030c35b";
connection.query(`
SELECT
EmailUsername,
EmailPassword
FROM
UserEmail
WHERE
UserID = ?
`, [userID])
.then(([results, fields]) => {
let transporter = nodemailer.createTransport({
host: process.env.EMAIL_HOST,
port: process.env.EMAIL_PORT,
secure: true,
auth: {
user: results[0].EmailUsername,
pass: results[0].EmailPassword
},
});
// Put transporter.sendMail() here
})
.catch(error => console.log(error));
This is just a sample of what you could do and how you could do it. You need to use your own critical thinking to fit it into your own project and style.
There is no one way to do it and some of the choices I've made are personal decisions. You should be able to merge this with Jerry's answer. He goes more into how to create the nodemailer module. I am only showing you how to connect database data with nodemailer.
Please read up on https://www.npmjs.com/package/mysql2 especially the promise wrapper section. This solution also uses dotenv https://www.npmjs.com/package/dotenv and https://nodemailer.com/about/

Trying to send email with Nodemailer and Twilio Sendgrid with normal auth (not Oauth2). SendMessage() fails with Error: Missing credentials for PLAIN

I am attempting to send an email using Nodemailer and Twilio Sendgrid, following the tutorial here. As far as I can tell I am following the instructions in the tutorial, as well as theNodemailer and Sendgrid documentation. Every time this method is called, the code in the catch block executes, and I get the error Error: Missing credentials for "PLAIN".
My question was closed due to association with the question here, however my problem is different and none of the solutions on the thread apply. I am using my own domain to send, not gmail.com. I want to solve the problem without using Oauth2, which from what I understand I should not need, given that I am using an email domain I control. Also I am already using pass' rather than 'password for my authorization data (the top solution on the associated answer).
I've been stuck on this for a few days now , and I'd appreciate any insight anyone can offer!
Here is my code:
async function sendEmail(email, code) {
try{
const smtpEndpoint = "smtp.sendgrid.net";
const port = 465;
const senderAddress = 'Name "contact#mydomain.com"';
const toAddress = email;
const smtpUsername = "apikey";
const smtpPassword = process.env.SG_APIKEY;
const subject = "Verify your email";
var body_html = `<!DOCTYPE>
<html>
<body>
<p>Your authentication code is : </p> <b>${code}</b>
</body>
</html>`;
let transporter = nodemailer.createTransport({
host: smtpEndpoint,
port: port,
secure: true,
auth: {
user: smtpUsername,
pass: smtpPassword,
},
logger: true,
debug: true,
});
let mailOptions = {
from: senderAddress,
to: toAddress,
subject: subject,
html: body_html,
};
let info = await transporter.sendMail(mailOptions);
return { error: false };
} catch (error) {
console.error("send-email-error", error);
return {
error: true,
message: "Cannot send email",
};
}
}
And here is the log:
Thanks!
You have already identified the issue of API key not being passed into the Nodemailer transport. While hardcoding the key is a possible solution, it's not a good practice. Usually secrets and keys are managed via environment variables so they are, for example, not accidentally committed to a repository and can be configured externally without changing the code.
In the tutorial you linked, working with the environment variable is addressed, but I see there is a mistake with .env file. So let me try to recap how to properly get SG_APIKEY from environment variable and .env file.
In your project directory create the .env file with the following contents:
SG_APIKEY=<your_sendgrid_api_key>
(obviously replace <your_sendgrid_api_key> with your actual API key)
Make sure dotenv package is installed: npm i dotenv
At the beginning of the file where you use Nodemailer, add the following line:
require("dotenv").config();
This will ensure the SG_APIKEY is loaded from .env file.
You can check if the env variable is set correctly with console.log(process.env.SG_APIKEY)
A comment on the (closed) previous version of this thread solved the problem for me:

Passing Vue.js form data to Nodemailer script with Axios

I have some form data in one of my Vue components that I want to pass on to my Nodemailer script so that data can be sent as an email. I'm trying to use Axios to do this.
Nothing is happening though as I don't actually know what I'm doing!
The Nodemailer script I have set up works when I execute the file in the command line. What I need is for it to execute when the form in my Vue.js component is submitted.
Here is my Nodemailer script -
"use strict";
const nodemailer = require("nodemailer");
require('dotenv').config();
// async..await is not allowed in global scope, must use a wrapper
async function main() {
// create reusable transporter object using the default SMTP transport
let transporter = nodemailer.createTransport({
host: "smtp.gmail.com",
port: 465,
secure: true, // true for 465, false for other ports
auth: {
user: process.env.user, // generated ethereal user
pass: process.env.password, // generated ethereal password
},
});
// send mail with defined transport object
let info = await transporter.sendMail({
from: process.env.user, // sender address
to: process.env.email, // list of receivers
subject: 'Translation Suggestion', // Subject line
text: "Hello world?", // plain text body
html: "<p>Traditional: <br> Simplified: <br> Pinyin: <br> English: "
});
console.log("Message sent: %s", info.messageId);
// Message sent: <b658f8ca-6296-ccf4-8306-87d57a0b4321#example.com>
// Preview only available when sending through an Ethereal account
console.log("Preview URL: %s", nodemailer.getTestMessageUrl(info));
// Preview URL: https://ethereal.email/message/WaQKMgKddxQDoou...
}
main().catch(console.error);
And the function being called upon submit in my form component -
<button type="submit" #click="sendEmail" class="form__btn">Suggest</button>
sendEmail () {
axios.post("localhost:3000/send-translation-suggest-email", () => {
this.traditional,
this.simplified,
this.pinyin,
this.english
})
}
To create a REST-API with express.js, first initialize a node.js-project (npm init [-y]). Then you install express.js (npm install express).
When you have your setup, you can create an index.js-file, the server. In it, you will have to adapt the content of the express Hello World example, so that the route it accepts is not GET / but POST /send-translation-suggest-email. Then make sure your server listens to port 3000 (as you specified in the client-side code).
In the listener to POST /send-translation-suggest-email, you can call the main-method from your other file (make sure to import the file properly, with node.js's require-syntax).
Then, you can call the backend server from the frontend as you wished.

Nodemailer fails only in production

When I try to send an e-mail using nodemailer I got an connection timeout error with code 'ETIMEDOUT', but when I try on my notebook there's no error, both using the same e-mail account and password.
This is the file 'mail.js':
const nodemailer = require('nodemailer');
const user = process.env.EMAIL;
const pass = process.env.EMAIL_PASS;
const transporter = nodemailer.createTransport({
service: 'kinghost',
host: 'smtp.kinghost.net',
port: 587,
secure: false,
pool: true,
auth: { user, pass }
});
module.exports = {
sendMail(to, subject, html) {
const mailOptions = {
from: user,
to,
subject,
html
};
console.log(user)
transporter.sendMail(mailOptions, (err, info) => {
if (err) {
console.log(err)
};
console.log(`Mail to ${mail} has been sended`);
});
}
};
It might be because on a production website, you are on a server that has a different IP to your own, and perhaps your email service is running some filtering because it thinks it might be unintended behaviour.
You should maybe look into other secure ways of authenticating your email client, or perhaps see if you can register the IP of your production server (less effective).
Hey programmer friend.
I went through the same situation using kinghost. Make sure you check your email provider's dashboard for an option called "SMTP INTERNATIONAL".
Quick fix:
change your config
nodemailer.createTransport({
host: **"smtpi.kinghost.net"**, // smtp international
secure: true, // force port 465
port: 465, // default port
auth: {
user: process.env.EMAIL, //email
pass: process.env.EMAIL_PASS, //pass
}
});
Your ECS or EC2 container must run on a different source, for this reason, activate the international email in your provider and use the same to solve this problem.
Your ECS or EC2 container must run on a different source, for this reason, activate the international email in your provider and use the same to solve this problem.
For my application this worked.

Transnational email with SendGrid and Firebase No Errors but no emails (Used NodeMailer as answer)

ok so I set up an Ionic webapp with a contact form and I have the form interacting with firebase meaning all my form info is being stored on the real time database. Now I have setup SendGrid according to this tutorial:
Firestore - Database Triggered Events;
https://fireship.io/lessons/sendgrid-transactional-email-guide/
However the cloud functions are not being triggered when new data is being entered. I am not getting any errors on the console and from sendgrid dashboard there are no requests. My understanding is that when there is change in the database it will automatically trigger the function and then sendgrid will send emails with the relevant data.
Here is my code;
// Firebase Config
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
admin.initializeApp();
// Sendgrid Config
import * as sgMail from '#sendgrid/mail';
const API_KEY = functions.config().sendgrid.key;
const TEMPLATE_ID = functions.config().sendgrid.template;
sgMail.setApiKey(API_KEY);
// Emails the author when a new messages is added
export const newMessage = functions.firestore.document('messages/{messageId}').onCreate( async (change, context) => {
// Raw Data
// const post = postSnap.data();
const msgData = change.data();
// Email
const msg = {
to: msgData.email,
from: 'Your_email#gmail.com',
templateId: TEMPLATE_ID,
dynamic_template_data: {
subject: 'New Message',
name: msgData.name,
text: `Here is the message: ${msgData.message}`,
phone: msgData.phone
},
};
// Send it
return sgMail.send(msg);
});
Deployment of the functions was successful to firebase.
Please any help is appreciated.
edit //////////////////////////////////////////////// edit
Ended up using Nodemailer instead.
It's Probobly Free Firebase Spark Plan https://firebase.google.com/pricing. Cloud Functions: Outbound Networking = Google Services Only. If You change to Blaze Plan You still will not pay any thing if You no use much Outbound Networking. I have 2x Blaze Plans 3 months and pay nothing.
ok so this is what worked for me after searching and searching. Thanks to #Mises for giving me a direction to follow. For others that are trying to send transactional emails with firebase using nodemailer here is how I did it.
I followed the above link given to me by #Mises;
https://github.com/firebase/functions-samples/tree/Node-8/email-confirmation
I was able to upload the function to firebase, but I was still getting an error in firebase function logs;
-There was an error while sending the email: { Error: Missing
credentials for "PLAIN"
So then from there I followed this link;
Missing credentials for "PLAIN" nodemailer
unfortunately activating less secure apps on google did not work for me.
aslo offical docs from nodemailer here;
https://nodemailer.com/about/
Hope this helps someone else.

Categories

Resources