Nodemailer error while using gmail in firebase functions - node.js

I am using firebase cloud functions. I have the following setup configured. While that is working completely fine on my local machine, It's giving me an issue when run on the servers. I have tried gazillion work arounds on the internet but no luck. What is wrong with this?
'use strict'
const functions = require('firebase-functions');
var admin = require('firebase-admin');
const express = require('express');
const nodemailer = require('nodemailer');
const app = express()
var emailRecepient;
var userName;
const smtpTransport = nodemailer.createTransport({
service: "gmail",
host: 'smtp.gmail.com',
port: 587, // tried enabling and disabling these, but no luck
secure: false, // similar as above
auth: {
user: '<emailid>',
pass: '<password>'
},
tls: {
rejectUnauthorized: false
}
});
var mailOptions = {
from: 'test <hello#example.com>',
to: emailRecepient,
subject: 'Welcome to test',
text: 'welcome ' + userName + ". did you see the new things?"
};
function sendmail() {
smtpTransport.sendMail(mailOptions, function (error, info) {
if (error) {
console.log(error);
}
else {
console.log('Email sent: ' + info.response);
}
});
};
exports.sendEmails = functions.database.ref('/users/{userID}/credentials').onCreate((snap, context) => {
const userID = context.params.userID;
const vals = snap.val()
userName = vals.name;
emailRecepient = vals.email;
smtpTransport.sendMail(mailOptions, function (error, info) {
if (error) {
console.log("Error sending email ---- ",error);
}
else {
console.log('Email sent: ' + info.response);
}
});
return true;
});
The error I got on all cases is :
Error sending email 2 ---- { Error: Invalid login: 534-5.7.14 <https://accounts.google.com/signin/continue?sarp=1&scc=1&plt=AKgnsbsi
534-5.7.14 qRQLfD9YlFZDsPj7b8QQACro9c41PjhSVo0NZ4i5ZHNlyycFi_FyRp8VdZ_dH5ffWWAABQ
534-5.7.14 8rH2VcXkyZBFu00-YHJUQNOqL-IqxEsZqbFCwCgk4-bo1ZeDaKTdkEPhwMeIM2geChH8av
534-5.7.14 0suN293poXFBAk3TzqKMMI34zCvrZlDio-E6JVmTrxyQ-Vn9Ji26LaojCvdm9Bq_4anc4U
534-5.7.14 SpQrTnR57GNvB0vRX1BihDqKuKiXBJ5bfozV1D1euQq18PZK2m> Please log in via
534-5.7.14 your web browser and then try again.
534-5.7.14 Learn more at
534 5.7.14 https://support.google.com/mail/answer/78754 t2sm3669477iob.7 - gsmtp
at SMTPConnection._formatError (/user_code/node_modules/nodemailer-smtp-transport/node_modules/smtp-connection/lib/smtp-connection.js:528:15)
at SMTPConnection._actionAUTHComplete (/user_code/node_modules/nodemailer-smtp-transport/node_modules/smtp-connection/lib/smtp-connection.js:1231:30)
at SMTPConnection.<anonymous> (/user_code/node_modules/nodemailer-smtp-transport/node_modules/smtp-connection/lib/smtp-connection.js:319:22)
at SMTPConnection._processResponse (/user_code/node_modules/nodemailer-smtp-transport/node_modules/smtp-connection/lib/smtp-connection.js:669:16)
at SMTPConnection._onData (/user_code/node_modules/nodemailer-smtp-transport/node_modules/smtp-connection/lib/smtp-connection.js:493:10)
at emitOne (events.js:96:13)
at TLSSocket.emit (events.js:188:7)
at readableAddChunk (_stream_readable.js:176:18)
at TLSSocket.Readable.push (_stream_readable.js:134:10)
at TLSWrap.onread (net.js:559:20)
code: 'EAUTH',
response: '534-5.7.14 <https://accounts.google.com/signin/continue?sarp=1&scc=1&plt=AKgnsbsi\n534-5.7.14 qRQLfD9YlFZDsPj7b8QQACro9c41PjhSVo0NZ4i5ZHNlyycFi_FyRp8VdZ_dH5ffWWAABQ\n534-5.7.14 8rH2VcXkyZBFu00-YHJUQNOqL-IqxEsZqbFCwCgk4-bo1ZeDaKTdkEPhwMeIM2geChH8av\n534-5.7.14 0suN293poXFBAk3TzqKMMI34zCvrZlDio-E6JVmTrxyQ-Vn9Ji26LaojCvdm9Bq_4anc4U\n534-5.7.14 SpQrTnR57GNvB0vRX1BihDqKuKiXBJ5bfozV1D1euQq18PZK2m> Please log in via\n534-5.7.14 your web browser and then try again.\n534-5.7.14 Learn more at\n534 5.7.14 https://support.google.com/mail/answer/78754 t2sm3669477iob.7 - gsmtp',
responseCode: 534,
command: 'AUTH PLAIN' }
I have even turned of the allow secure apps in the google settings. But for some reason this doesn't seem to work. Any help is extremely appreciated.
As advised by Renaud, I tried firebase-samples/email-confirmation and I am having following error :
TypeError: snapshot.changed is not a function
at exports.sendEmailConfirmation.functions.database.ref.onWrite (/user_code/index.js:38:17)
at Object.<anonymous> (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:112:27)
at next (native)
at /user_code/node_modules/firebase-functions/lib/cloud-functions.js:28:71
at __awaiter (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:24:12)
at cloudFunction (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:82:36)
at /var/tmp/worker/worker.js:758:24
at process._tickDomainCallback (internal/process/next_tick.js:135:7)
Cheers

When you execute an asynchronous operation in a background triggered Cloud Function, you must return a promise, in such a way the Cloud Function waits that this promise resolves in order to terminate.
This is very well explained in the official Firebase video series here: https://firebase.google.com/docs/functions/video-series/. In particular watch the three videos titled "Learn JavaScript Promises" (Parts 2 & 3 especially focus on background triggered Cloud Functions, but it really worth watching Part 1 before).
So you should modify your code as follows:
exports.sendEmails = functions.database.ref('/users/{userID}/credentials').onCreate((snap, context) => {
const userID = context.params.userID;
const vals = snap.val()
userName = vals.name;
emailRecepient = vals.email;
return smtpTransport.sendMail(mailOptions);
});
If you want to print to the console the result of the email sending, you can do as follows:
return smtpTransport.sendMail(mailOptions)
.then((info) => console.log('Email sent: ' + info.response))
.catch((error) => console.log("Error sending email ---- ", error));
});
Actually there is an official Cloud Functions sample that does exactly that, see https://github.com/firebase/functions-samples/blob/master/email-confirmation/functions/index.js

I see it was a long discussion, let me share the code snippets which worked out for me for others with similar issues so it will be easier to figure out.
1) function.ts (it's written in TypeScript)
import * as admin from 'firebase-admin';
import * as functions from 'firebase-functions';
import * as nodemailer from 'nodemailer';
admin.initializeApp();
// I'm taking all these constants as secrets injected dynamically (important when you make `git push`), but you can simply make it as a plaintext.
declare const MAIL_ACCOUNT: string; // Declare mail account secret.
declare const MAIL_HOST: string; // Declare mail account secret.
declare const MAIL_PASSWORD: string; // Declare mail password secret.
declare const MAIL_PORT: number; // Declare mail password secret.
const mailTransport = nodemailer.createTransport({
host: MAIL_HOST,
port: MAIL_PORT, // This must be a number, important! Don't make this as a string!!
auth: {
user: MAIL_ACCOUNT,
pass: MAIL_PASSWORD
}
});
exports.sendMail = functions.https.onRequest(() => {
const mailOptions = {
from: 'ME <SENDER#gmail.com>',
to: 'RECEIVER#gmail.com',
subject: `Information Request from ME`,
html: '<h1>Test</h1>'
};
mailTransport
.sendMail(mailOptions)
.then(() => {
return console.log('Mail sent'); // This log will be shown in Firebase Firebase Cloud Functions logs.
})
.catch(error => {
return console.log('Error: ', error); // This error will be shown in Firebase Cloud Functions logs.
});
});
This said, you should receive an e-mail from SENDER#gmail.com to RECEIVER#gmail.com, of course modify it for your own needs.
Note: I got the same issue with sending mails correctly on localhost, but on deployment it did not. Looks like the problem in my case was that I did not use port and host in createTransport, instead I had:
const mailTransport = nodemailer.createTransport({
service: 'gmail',
auth: {
user: MAIL_ACCOUNT,
pass: MAIL_PASSWORD
}
});
On top of this do not forget about enabling Less secure app access to ON. Also https://accounts.google.com/DisplayUnlockCaptcha might be helpful.

After checking that all the things listed above were done within my code, I solved the problem login in Google´s account (with the account I'm using with my proyect). I had to recognize the activity form another origin (google detected Firebase was trying to access to that account), and that was it.
After that I tryed to send another email from firebase cloud and it was working fine

Related

Emails are not sent out after deploying to Lamba function

I'm using Nodemailer and SES to send out mails from my application, it works perfectly locally but doesn't work once I deploy to Lambda function, it returns with a "Endpoint request timed out" error message after it exceeds the 30 seconds API gateway time limit.
Note: The Lambda function has all the permissions required to send out SES mails.
I thought SES was the problem and I decided to use gmail instead, I got the same result. Checking my cloudwatch logs I find this error:
2022-11-16T06:54:23.547Z 6d967bf2-0837-4bd8-ae5f-011842656d15 INFO Error: Connection timeout
at SMTPConnection._formatError (/var/task/node_modules/nodemailer/lib/smtp-connection/index.js:790:19)
at SMTPConnection._onError (/var/task/node_modules/nodemailer/lib/smtp-connection/index.js:776:20)
at Timeout.<anonymous> (/var/task/node_modules/nodemailer/lib/smtp-connection/index.js:235:22)
at listOnTimeout (node:internal/timers:559:17)
at processTimers (node:internal/timers:502:7) {
code: 'ETIMEDOUT',
command: 'CONN'
}
Here's my function for sending out the mails, I have modified it so many times, I thought I was the one not structuring it properly.
const transporter = nodemailer.createTransport ({
service: 'gmail',
host: 'smtp.gmail.com',
port: 587,
secure: true,
auth: {
user: config.REACT_APP_EMAIL,
pass: config.REACT_APP_EMAIL_PASS,
},
});
const sendOutMail = async (receiver, emailSubject, body) => {
return transporter.sendMail({
from: config.REACT_APP_EMAIL,
to: receiver,
subject: emailSubject,
text: body,
html: '<div> ' + emailSubject + '</div>',
})
.then( results => {
console.log("Success:", results);
return true
})
.catch ( error => {
console.log("Error:", error);
return false
})
}
All modifications of my function works locally, but none works after I deploy
Since your lambda function runs inside a VPC, make sure you have internet access on this VPC. Otherwise you need to create a VPC Endpoint for SES so that your lambda can make that API call.
Here is the documentation for reference:
https://docs.aws.amazon.com/ses/latest/dg/send-email-set-up-vpc-endpoints.html

Node mailer error : Username and Password cannot be accepted

I am using Mailtrap.io as smpt server for sending mail using node mailer. I have two files email.js for email setup and authController containing forgotPassword as handler function.
Whenever I hit
http://localhost:8000/api/v1/users/forgotPassword
I get this error as response .
Error: Invalid login: 535-5.7.8 Username and Password not accepted. Learn more at
535 5.7.8 https://support.google.com/mail/?p=BadCredentials g16-20020a05600c4ed000b003974860e15esm21842702wmq.40 - gsmtp
at SMTPConnection._formatError (E:\starter\node_modules\nodemailer\lib\smtp-connection\index.js:784:19)
at SMTPConnection._actionAUTHComplete (E:\starter\node_modules\nodemailer\lib\smtp-connection\index.js:1536:34)
at SMTPConnection.<anonymous> (E:\starter\node_modules\nodemailer\lib\smtp-connection\index.js:540:26)
at SMTPConnection._processResponse (E:\starter\node_modules\nodemailer\lib\smtp-connection\index.js:947:20)
at SMTPConnection._onData (E:\starter\node_modules\nodemailer\lib\smtp-connection\index.js:749:14)
at TLSSocket.SMTPConnection._onSocketData (E:\starter\node_modules\nodemailer\lib\smtp-connection\index.js:189:44)
at TLSSocket.emit (node:events:527:28)
at addChunk (node:internal/streams/readable:315:12)
at readableAddChunk (node:internal/streams/readable:289:9)
at TLSSocket.Readable.push (node:internal/streams/readable:228:10)
at TLSWrap.onStreamRead (node:internal/stream_base_commons:190:23) {
code: 'EAUTH',
response: '535-5.7.8 Username and Password not accepted. Learn more at\n' +
'535 5.7.8 https://support.google.com/mail/?p=BadCredentials g16-20020a05600c4ed000b003974860e15esm21842702wmq.40 - gsmtp',
responseCode: 535,
command: 'AUTH PLAIN'
}
Here is my code.
email.js
const nodemailer = require('nodemailer');
const sendEmail = async (options) => {
//1 Create a transporter
const transporter = nodemailer.createTransport({
service: 'Gmail',
host: process.env.EMAIL_HOST,
port: process.env.EMAIL_PORT,
auth: {
user: process.env.EMAIL_USERNAME,
pass: process.env.EMAIL_PASSWORD,
},
});
//2 Define Email Options
const mailOptions = {
from: 'Farhan Ajmal <18251598-146#uog.edu.pk>',
to: options.email,
subject: options.subject,
text: options.message,
};
//3 Send email
await transporter.sendMail(mailOptions);
};
module.exports = sendEmail;
authController.js
const jwt = require('jsonwebtoken');
const { promisify } = require('util');
const User = require('../models/userModel');
const catchAsync = require('../utils/catchAsync');
const AppError = require('../utils/appError');
const sendEmail = require('../utils/email');
exports.forgotPassword = catchAsync(async (req, res, next) => {
//Get user based on posted email
const user = await User.findOne({ email: req.body.email });
if (!user) {
next(new AppError('There is no user with that email address ', 404));
}
//Send email
try {
await sendEmail({
email: user.email,
subject: 'Your password is only valid for 10 min',
message,
});
res.sendStatus(200);
} catch (err) {
console.log(err);
return next(
new AppError('There was an error sending the email. Try again later'),
500
);
}
});
Gmail/Google has recently removed Less Secure app access. This is what they says:
To help keep your account secure, from May 30, 2022, ​​Google no longer supports the use of third-party apps or devices which ask you to sign in to your Google Account using only your username and password.
So now any emailer having direct SMTP email sending will throw error "535-5.7.8 Username and Password not accepted". This is because now Google is asking for app specific password and the email account password isn't app password so they show username/password not accepted.
You need to create and use App specific password. An app password works like an alternate password for your account. It can only be used by the applications you share it with, so it’s more secure than sharing your primary password.
To create app specific password:
Enable 2 Factor Authentication.
Go to Account settings of Gmail and select App Passwords.
Name it whatever you want and create the password.
Use this new 16 digit password along with gmail email for SMTP (At the place of password use this 16 digit password)
Enjoy sending emails..!!

Error: connect ECONNREFUSED Nodemailer Ethereal

I have an error when trying to use the example from nodemailer.
I modified it a little bit but it has all of the same core. When I try to run it I get this error:
Error: connect ECONNREFUSED 13.49.22.0:587
at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1161:16) {
errno: -111,
code: 'ESOCKET',
syscall: 'connect',
address: '13.49.22.0',
port: 587,
command: 'CONN'
}
I have searched and I can't find any answers about this. I have heard that downgrading nodemailer's version to 4.7.0 fixes it but I have not tested this yet.
I also tested this connection to google, but it didn't work.
The function that is actually run is this:
async function nodeMailerMain() {
// Generate test SMTP service account from ethereal.email
// Only needed if you don't have a real mail account for testing
let testAccount = await nodemailer.createTestAccount().catch((err) => console.log(err))
// create reusable transporter object using the default SMTP transport
let transporter = await nodemailer.createTransport({
host: testAccount.smtp.host,
port: testAccount.smtp.port,
secure: testAccount.smtp.secure, // true for 465, false for other ports
auth: {
user: testAccount.user, // generated ethereal user
pass: testAccount.pass, // generated ethereal password
},
})//.catch((err) => console.log(err))
console.log(testAccount.smtp.host);
console.log("successfully created transporter");
let message = {
from: 'Sender Name <sender#example.com>',
to: 'Recipient <recipient#example.com>',
subject: 'Nodemailer is unicode friendly ✔',
text: 'Hello to myself!',
html: '<p><b>Hello</b> to myself!</p>'
};
// send mail with defined transport object
transporter.sendMail(message, (error, info) => {
if (error) {
return console.log(error);
}
console.log('Email sent: ' + info.response);
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...
});
}
It turns out that, since I was using a Gitpod workspace, the email functionality had been blocked to avoid spam-bots. See this issue on Github for more information: https://github.com/gitpod-io/gitpod/issues/8976

Unable to connect to Realm Object Server using NodeJs

I've installed Realm Object Server using the docker container method on a VM on the google cloud platform. The container is running and I am able to connect in a browser and see the ROS page. I am able to connect to it using Realm Studio and add a user.
I have a nodeJS app running locally on a Mac and I'm trying to use that to sign in and write to realm on the server. When I run the app I get an error and the user returned is an empty object. Not sure what I'm doing wrong.
I'm new to NodeJS.
Code:
var theRealm;
const serverUrl = "http://xx.xx.xx.xx:9080";
const username = "xxxx";
const password = "xxxx";
var token = "long-token-for-enterprise-trial";
Realm.Sync.setFeatureToken(token);
console.log("Will log in user");
Realm.Sync.User.login(serverUrl, username, password)
.then(user => {
``
// user is logged in
console.log("user is logged in " + util.inspect(user));
// do stuff ...
console.log("Will create config");
const config = {
schema:[
schema.interventionSchema,
schema.reportSchema
],
sync: {
user: user,
url: serverUrl
}
};
console.log("Will open realm with config: " + config);
const realm = Realm.open(config)
.then(realm => {
// use the realm instance here
console.log("Realm is active " + realm);
console.log("Will create Realm");
theRealm = new Realm({
path:'model/realm_db/theRealm.realm',
schema:[
schema.interventionSchema,
schema.reportSchema
]
});
console.log("Did create Realm: " + theRealm);
})
.catch(error => {
// Handle the error here if something went wrong
console.log("Error when opening Realm: " + error);
});
})
.catch(error => {
// an auth error has occurred
console.log("Error when logging in user: " + error);
});
Output:
Will log in user
Server is running...
user is logged in {}
Will create config
Will open realm with config: [object Object]
TypeError: Cannot read property 'token_data' of undefined
at performFetch.then.then (/pathToProject/node_modules/realm/lib/user-methods.js:203:49)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
TypeError: Cannot read property 'token_data' of undefined
at performFetch.then.then (/pathToProject/node_modules/realm/lib/user-methods.js:203:49)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
Error # user-methods.js:203:49
const tokenData = json.access_token.token_data;
json is:
{ user_token:
{ token: 'xxxxxxxx',
token_data:
{ app_id: 'io.realm.Auth',
identity: 'xxxxxxx',
salt: 'xxxxxxxx',
expires: 1522930743,
is_admin: false } } };
So json.access_token.token_data is undefined but json. user_token.token_data would not be.
I would suggest you to try the ROS connection with realm studio in that u can check logs as well which will help you to fix the error. If your still not able to fix then you can contact Realm support team even they helped me to fix the issue of ROS connection in Xamarin Forms using Docker.

Getting "bad username or password" error while attempting to connect to broker

I have followed the tutorial presented here, but I couldn't connect a client to the server. I always get the following error message (full stacktrace):
Error: Connection refused: Bad username or password
at MqttClient._handleConnack (${project_dir}/node_modules/mqtt/lib/client.js:760:24)
at MqttClient._handlePacket (${project_dir}/node_modules/mqtt/lib/client.js:300:12)
at process (${project_dir}/node_modules/mqtt/lib/client.js:242:12)
at Writable.writable._write (${project_dir}/node_modules/mqtt/lib/client.js:252:5)
at doWrite (${project_dir}/node_modules/mqtt/node_modules/readable-stream/lib/_stream_writable.js:345:64)
at writeOrBuffer (${project_dir}/node_modules/mqtt/node_modules/readable-stream/lib/_stream_writable.js:334:5)
at Writable.write (${project_dir}/node_modules/mqtt/node_modules/readable-stream/lib/_stream_writable.js:271:11)
at Socket.ondata (_stream_readable.js:528:20)
at emitOne (events.js:77:13)
at Socket.emit (events.js:169:7)
I have double-checked my environment variables, whose values came from my Auth0 account, (particularly CLIENT_ID, DOMAIN, CLIENT_SECRET and CONNECTION) but they seem fine.
I've changed the client's code a bit to match the current version of MQTT.js. Here is the code:
const mqtt = require('mqtt');
const settings = {
port: 1883,
keepalive: 1000,
protocolId: 'MQIsdp', // already tried 'MQTT' with protocol version 4
protocolVersion: 3,
clientId: 'a random id',
username: 'an account at Auth0',
password: 'the password of the account'
}
var client = mqtt.connect('mqtt://localhost', settings);
client.on("connect", function () {
client.subscribe("topic");
console.log("connected");
});
client.on("message", function (topic, message) {
// message is Buffer
console.log(message.toString());
client.end();
});
The broker code is very similar to the presented in the tutorial. I want to solve this error before changing it.
const mosca = require('mosca')
const Auth0Mosca = require('auth0mosca');
require('dotenv').config();
const settings = {
port: 1883
};
if (!process.env.AUTH0_DOMAIN || !process.env.AUTH0_CLIENT_ID ||
!process.env.AUTH0_CLIENT_SECRET || !process.env.AUTH0_CONNECTION) {
throw 'Make sure you have AUTH0_DOMAIN, AUTH0_CLIENT_ID, AUTH0_CLIENT_SECRET and AUTH0_CONNECTION in your .env file';
}
const auth0 = new Auth0Mosca('https://' + process.env.AUTH0_DOMAIN, process.env.AUTH0_CLIENT_ID, process.env.AUTH0_CLIENT_SECRET, process.env.AUTH0_CONNECTION);
// mosca server
const server = new mosca.Server(settings);
server.authenticate = auth0.authenticateWithCredentials();
server.authorizePublish = auth0.authorizePublish();
server.authorizeSubscribe = auth0.authorizeSubscribe();
server.on('ready', setup);
// MQTT server is ready
function setup() {
console.log('Mosca server is up and running');
}
server.on('clientConnected', function(client) {
console.log('New connection: ', client.id);
});
I know that it is probably a stupid mistake or a library update that is causing this. For the latter case, here are the versions:
"auth0mosca": "^0.1.0",
"mosca": "^2.3.0",
"mqtt": "^2.5.0"
Finally, I have checked that the request reaches the broker.
I have forked the repository with the code of the tutorial and updated its dependencies to the latest versions. Also, added the possibility to verify JWTs signed with, for example, RS256 algorithm.
This solved my problem and I hope it helps everyone facing the same issue.

Resources