Sending email using nodemailer not working - node.js

I have this sendMail cloud function here through i am trying to send a simple email. I am not sure what is the mistake i am doing but i keep getting 400 Bad request error on postman whenever i hit this function.
P.S i am adding correct credentials of my gmail account too
Here is my cloud function
const functions = require('firebase-functions');
const cors = require('cors')({origin: true});
const admin = require("firebase-admin");
const bodyParser = require("body-parser");
const nodemailer = require("nodemailer");
var smtpTransport = require('nodemailer-smtp-transport');
let transporter = nodemailer.createTransport(smtpTransport({
service: 'Gmail',
auth: {
user: 'abc#gmail.com',
pass: '12345'
}
}));
//Send email
exports.sendMail = functions.https.onRequest((request, responde) => {
// cors(req, res, () => {
// getting dest email by query string
res.set('Access-Control-Allow-Origin', '*');
res.set('Access-Control-Allow-Methods', 'GET', 'POST');
res.set('Access-Control-Allow-Headers', 'Content-Type');
if(req.method === 'OPTIONS') {
res.end();
}
else
{
if(req.body.dest != null || req.body.dest != undefined) {
const dest = req.query.dest;
const mailOptions = {
from: 'Ehsan Nisar <ABC#gmail.com>',
to: dest,
subject: 'I\'M A PICKLE!!!', // email subject
html: `<p style="font-size: 16px;">Pickle Riiiiiiiiiiiiiiiick!!</p>
<br />
<img src="https://images.prod.meredith.com/product/fc8754735c8a9b4aebb786278e7265a5/1538025388228/l/rick-and-morty-pickle-rick-sticker" />
` // email content in HTML
};
// returning result
return transporter.sendMail(mailOptions, (erro, info) => {
if(erro){
return res.send(erro);
}
return res.send('Sended');
});
}
else {
res.send(400, {
"message": "All fields are required"
})
}
// });
}
});

Related

nodemailer not sent email in cloud function

I have this code in a cloud function where I want to use nodemailer to send some notification emails.
const transporter = nodemailer.createTransport({
host: 'smtp.gmail.com',
port: 465,
secure: true,
auth: {
user: 'mygmailaddress#gmail.com',
pass: 'apppassword'
}
})
/* Send email for custom programs to unsigned patients */
exports.sendEmailToNewPatient = functions.https.onRequest( (req, res) => {
cors( req, res, () => {
const mailOptions = {
to: req.body.userEmail,
from: 'mygmailaddress#gmail.com',
subject: `${req.body.doctorName} test!`,
text: 'Test message',
html: '<h1>Test message/h1>'
}
transporter.sendMail(mailOptions, (error, info) => {
if( error ) {
res.send(error.message)
}
const sendMailResponse = {
accepted: info.accepted,
rejected: info.rejected,
pending: info.pending,
envelope: info.envelope,
messageId: info.messageId,
response: info.response,
}
res.send(sendMailResponse)
})
})
})
I'm calling the function using a POST request made with axios, when the request is send I will get a status code of 200 and in the data object of axios response I will have this informations
data:
code: "EENVELOPE"
command: "API"
When I check my test email address to verify if the email is sent, I will not have any message as expected.
Any suggestion about?
I can see a small mistake in your code that the heading tag is not closed in the html message.
For more reference on how to send the mail using node mailer you can follow the below code.
As mentioned in the link:
const functions = require("firebase-functions");
const nodemailer = require('nodemailer');
const smtpTransport = require('nodemailer-smtp-transport');
const cors = require("cors")({
origin: true
});
exports.emailMessage = functions.https.onRequest((req, res) => {
const { name, email, phone, message } = req.body;
return cors(req, res, () => {
var text = `<div>
<h4>Information</h4>
<ul>
<li>
Name - ${name || ""}
</li>
<li>
Email - ${email || ""}
</li>
<li>
Phone - ${phone || ""}
</li>
</ul>
<h4>Message</h4>
<p>${message || ""}</p>
</div>`;
var sesAccessKey = 'YOURGMAIL#gmail.com';
var sesSecretKey = 'password';
var transporter = nodemailer.createTransport(smtpTransport({
service: 'gmail',
auth: {
user: sesAccessKey,
pass: sesSecretKey
}
}));
const mailOptions = {
to: "myemail#myemail.com",
from: "no-reply#myemail.com",
subject: `${name} sent you a new message`,
text: text,
html: text
};
transporter.sendMail(mailOptions, function(error, info){
if(error){
console.log(error.message);
}
res.status(200).send({
message: "success"
})
});
}).catch(() => {
res.status(500).send("error");
});
})
;
For more information you can check the blog , thread and documentation where brief explanations including code is provided.
If all above has been followed well then you can check this thread for further details:
First of all, you have to enable the settings to allow less secure apps for the gmail account that you are using. Here is the link.
Secondly, Allow access for "Display Unlock captcha option" (Allow access to your Google account). Here is the link.
As mentioned by Frank van Puffelen ,here you can process POST data in Node.js using console.log(req.body.userEmail) for reference you can check link.

Firebase email function not finding user

I have a contact form on my website that upon sending clicking to submit, runs a firebase function to execute the request. The issue i'm having all of a sudden is the following error (taken from the firebase logs)
Error: There is no user record corresponding to the provided identifier.
at FirebaseAuthError.FirebaseError [as constructor] (/workspace/node_modules/firebase-admin/lib/utils/error.js:42:28)
at FirebaseAuthError.PrefixedFirebaseError [as constructor] (/workspace/node_modules/firebase-admin/lib/utils/error.js:88:28)
at new FirebaseAuthError (/workspace/node_modules/firebase-admin/lib/utils/error.js:147:16)
at /workspace/node_modules/firebase-admin/lib/auth/auth-api-request.js:513:15
at /workspace/node_modules/firebase-admin/lib/auth/auth-api-request.js:1347:13
at process._tickCallback (internal/process/next_tick.js:68:7)
The function in charge of the sending is the following:
sendRequest() {
this.loading = true
return new Promise(resolve => {
const newUrl = `https://us-central1-easymediakit.cloudfunctions.net/contactForm?name=${
this.form.name
}&email=${this.form.email}&message=${encodeURI(
this.form.message
)}&uid=${this.uid}`
fetch(`${newUrl}`, {
withCredentials: true,
headers: {
'Content-Type': 'application/json'
}
})
.then(() => {})
.then(async response => {
this.loading = false
this.success = 'Your email has been sent.'
resolve(response)
})
}).catch(error => {
this.loading = false
console.log('Send error', error)
})
}
It was working and I haven't made any changes to the function that handles the email sending so I'm not sure where the issue is coming from, any guidance is greatly appreciated.
Update:
in index.js
const contactFormModule = require('./email/email-contact-form')
exports.contactForm = functions.https.onRequest((req, res) => {
return contactFormModule.handler(req, res)
the contact form:
const cors = require('cors')({ origin: true })
const domain = 'mg.epkbuilder.com'
const apiKey = 'f27b2755b05f3a366542ea538a00c6a2-d32d817f-dec22e2e'
const mailgun = require('mailgun-js')({ apiKey, domain })
const admin = require('firebase-admin')
const MailComposer = require('nodemailer/lib/mail-composer')
exports.handler = (req, res) => {
cors(req, res, async () => {
const uid = req.query.uid
const name = req.query.name
const email = req.query.email
const message = req.query.message
console.log('uid', uid)
console.log('message', `${message}`)
const user = await admin.auth().getUser(uid)
console.log('user', user)
const mailOptions = {
from: `${name} noreply#easymediakit.io`,
replyTo: `${name} ${email}`,
to: user.email,
subject: `Direct message from ${name}`,
text: `${message}`
}
let mail = new MailComposer(mailOptions).compile()
console.log('mail', mail)
return mail.build((error, message) => {
if (error) {
console.log('Email unsuccessful', error)
res.status(400).send(error)
}
const dataToSend = {
to: user.email,
message: message.toString('ascii')
}
return mailgun.messages().sendMime(dataToSend, sendError => {
if (sendError) {
console.log('Email unsuccessful', error)
res.status(400).send(error)
}
return res.send('Email successfully sent!')
})
})
})
}
The error seems to come from this line:
const user = await admin.auth().getUser(uid)
The error indicates that no user exists in Firebase Authentication for the uid value that you specified. You might want to double check the this.uid value in )}&uid=${this.uid} in your client-side code.

CORS error using angular httpClient with firebase functions

I have this ngrx effect in my angular app that send an http request using httpClient
#Injectable()
export class MyEffects {
constructor(private actions$: Actions, private httpClient: HttpClient) {
}
sendEmail$: Observable<Action> = createEffect(() => this.actions$.pipe(
ofType(sendEmail),
concatMap(action =>
this.httpClient.post('https://us-central1-<PROJECT_ID>.cloudfunctions.net/myApi', action.data).pipe(
map(() => actionFinished({status: HttpActionsStatus.SUCCESS})),
catchError(() => of(actionFinished({status: HttpActionsStatus.ERROR})))
)
)
));
}
When I run this with firebase emulator (http://localhost:5001/<PROJECT_ID>/us-central1/myApi it works just fine, but when I deploy the app and trigger the request I get this error
Access to XMLHttpRequest at 'https://us-central1-<PROJECT_ID>.cloudfunctions.net/myApi' from origin 'https://<PROJECT_ID>.web.app' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
This is my functions file
const fs = require('fs');
const {promisify} = require('util');
const readFile = promisify(fs.readFile);
const admin = require('firebase-admin');
const handlebars = require('handlebars');
const nodemailer = require('nodemailer');
const functions = require('firebase-functions');
const email = decodeURIComponent(functions.config().gmail.email);
const password = encodeURIComponent(functions.config().gmail.password);
const cors = require('cors')({origin: true, optionsSuccessStatus: 200});
admin.initializeApp();
const transporter = nodemailer.createTransport({
service: 'gmail',
secure: false,
port: 25,
auth: {
user: email,
pass: password
},
tls: {
rejectUnauthorized: false
}
});
exports.myApi = functions.https.onRequest((request: any, response: any) =>
cors(request, response, () =>
readHTMLFile('../src/assets/html/new_message.html', (err: any, html: string) => {
const template = handlebars.compile(html);
const htmlToSend = template(request.body);
const mailOptions = {
to: email,
from: request.body.senderEmail,
subject: request.body.subject,
html: htmlToSend
};
return transporter.sendMail(mailOptions, async (error: any, {}) => {
if (!error) {
const responseEmailOptions = {
to: request.body.senderEmail,
from: email,
subject: request.body.subject,
html: await readFile('../src/assets/html/auto_reply.html'),
};
transporter.sendMail(responseEmailOptions);
return response.status(200).send(request.body);
}
throw error;
});
})
)
);
const readHTMLFile = (path: string, callback: any) => {
fs.readFile(path, {encoding: 'utf-8'}, (err: any, html: string) => {
if (!err) {
return callback(null, html);
}
throw err;
});
};
Basically what it does it to send an email and a verification email back to the sender.
I'll Appreciate your help!

Ask for credentials before showing my swagger

I'm trying to add security to my API swagger endpont. I have created my API using node.js and express and swagger-ui-express module. The problem is that anyone is able to access to my swagger endpoint. So, to solve this, I thought about adding a basic auth before showing swagger content.
Example of implementing basic auth on endpoint:
app.get('/users', (req, res) => {
let user = auth(req)
if (user === undefined || user['name'] !== 'admin' || user['pass'] !== 'adminpass') {
res.statusCode = 401
res.setHeader('WWW-Authenticate', 'Basic realm="Node"')
res.end('Unauthorized')
} else {
res.status(200).send('Return all users');
}
});
That same example I want to add in swagger's endpoint:
const swaggerUi = require('swagger-ui-express');
const YAML = require('yamljs');
const swaggerDocument = YAML.load('./swagger.yaml');
const swaggerOptions = {
swaggerDefinition: {
info: {
version: "1.0.0",
title: "Customer API",
description: "Customer API Information",
contact: {
name: "Amazing Developer"
},
servers: ["http://localhost:3000"]
}
},
// ['.routes/*.js']
apis: ["index.js"]
};
const swaggerDocs = swaggerJsDoc(swaggerOptions);
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));
Can anyone help me? I tried to do it but it doesn't work. I even don't know if it is possible.
SOLUTION:
app.use('/api-docs', function(req, res, next){
let user = auth(req)
if (user === undefined || user['name'] !== 'admin' || user['pass'] !== 'adminpass') {
res.statusCode = 401
res.setHeader('WWW-Authenticate', 'Basic realm="Node"')
res.end('Unauthorized')
} else {
next();
}
}, swaggerUi.serve, swaggerUi.setup(swaggerDocument));
Edit: for those asking, auth is a function that takes the base64 encoded credentaials from the request header, decodes them and returns an object. Like follows:
const auth = (req) => {
const authorizationHeader = req.headers.authorization;
const base64 = authorizationHeader.substr(6);
const credentials = Buffer.from(base64, 'base64').toString();
const [name, pass] = credentials.split(':');
return { name, pass };
}
app.use('/api-docs', function(req, res, next){
let user = auth(req)
if (user === undefined || user['name'] !== 'admin' || user['pass'] !== 'adminpass') {
res.statusCode = 401
res.setHeader('WWW-Authenticate', 'Basic realm="Node"')
res.end('Unauthorized')
} else {
next();
}
}, swaggerUi.serve, swaggerUi.setup(swaggerDocument));

How to send email in Firebase for free?

I'm aware that Firebase doesn't allow you to send emails using 3rd party email services. So the only way is to send through Gmail.
So I searched the internet for ways, so here's a snippet that works and allows me to send email without cost.
export const shareSpeechWithEmail = functions.firestore
.document("/sharedSpeeches/{userId}")
.onCreate(async (snapshot, context) => {
// const userId = context.params.userId;
// const data = snapshot.data();
const mailTransport = nodemailer.createTransport(
`smtps://${process.env.USER_EMAIL}:${process.env.USER_PASSWORD}#smtp.gmail.com`
);
const mailOptions = {
to: "test#gmail.com",
subject: `Message test`,
html: `<p><b>test</b></p>`
};
try {
return mailTransport.sendMail(mailOptions);
} catch (err) {
console.log(err);
return Promise.reject(err);
}
});
I want to create a template, so I used this package called email-templates for nodemailer.
But the function doesn't get executed in Firebase Console and it doesn't show an error and shows a warning related to "billing".
export const shareSpeechWithEmail = functions.firestore
.document("/sharedSpeeches/{userId}")
.onCreate(async (snapshot, context) => {
const email = new Email({
send: true,
preview: false,
views: {
root: path.resolve(__dirname, "../../src/emails")
// root: path.resolve(__dirname, "emails")
},
message: {
// from: "<noreply#domain.com>"
from: process.env.USER_EMAIL
},
transport: {
secure: false,
host: "smtp.gmail.com",
port: 465,
auth: {
user: process.env.USER_EMAIL,
pass: process.env.USER_PASSWORD
}
}
});
try {
return email.send({
template: "sharedSpeech",
message: {
to: "test#gmail.com",
subject: "message test"
},
locals: {
toUser: "testuser1",
fromUser: "testuser2",
title: "Speech 1",
body: "<p>test using email <b>templates</b></p>"
}
});
} catch (err) {
console.log(err);
return Promise.reject(err);
}
});
You can definitely send emails using third party services and Cloud Functions, as long as your project is on the Blaze plan. The official provided samples even suggest that "if switching to Sendgrid, Mailjet or Mailgun make sure you enable billing on your Firebase project as this is required to send requests to non-Google services."
https://github.com/firebase/functions-samples/tree/master/quickstarts/email-users
The key here, no matter which email system you're using, is that you really need to upgrade to the Blaze plan in order to make outgoing connections.
you can send emails by using nodemailer:
npm install nodemailer cors
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const nodemailer = require('nodemailer');
const cors = require('cors')({origin: true});
admin.initializeApp();
/**
* Here we're using Gmail to send
*/
let transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: 'yourgmailaccount#gmail.com',
pass: 'yourgmailaccpassword'
}
});
exports.sendMail = functions.https.onRequest((req, res) => {
cors(req, res, () => {
// getting dest email by query string
const dest = req.query.dest;
const mailOptions = {
from: 'Your Account Name <yourgmailaccount#gmail.com>', // Something like: Jane Doe <janedoe#gmail.com>
to: dest,
subject: 'test', // email subject
html: `<p style="font-size: 16px;">test it!!</p>
<br />
` // email content in HTML
};
// returning result
return transporter.sendMail(mailOptions, (erro, info) => {
if(erro){
return res.send(erro.toString());
}
return res.send('Sended');
});
});
});
See also here
Set Security-Level to avoid error-messages:
Go to : https://www.google.com/settings/security/lesssecureapps
set the Access for less secure apps setting to Enable
Refer to
Call a sendMail() cloud function directly via functions.https.onCall(..) :
As #Micha mentions don't forget to enable Less Secure Apps for the outgoing email: https://www.google.com/settings/security/lesssecureapps
const functions = require('firebase-functions');
const nodemailer = require('nodemailer');
let mailTransport = nodemailer.createTransport({
service: 'gmail',
auth: {
user: 'supportabc#gmail.com',
pass: '11112222'
}
});
exports.sendMail = functions.https.onCall((data, context) => {
console.log('enter exports.sendMail, data: ' + JSON.stringify(data));
const recipientEmail = data['recipientEmail'];
console.log('recipientEmail: ' + recipientEmail);
const mailOptions = {
from: 'Abc Support <Abc_Support#gmail.com>',
to: recipientEmail,
html:
`<p style="font-size: 16px;">Thanks for signing up</p>
<p style="font-size: 12px;">Stay tuned for more updates soon</p>
<p style="font-size: 12px;">Best Regards,</p>
<p style="font-size: 12px;">-Support Team</p>
` // email content in HTML
};
mailOptions.subject = 'Welcome to Abc';
return mailTransport.sendMail(mailOptions).then(() => {
console.log('email sent to:', recipientEmail);
return new Promise(((resolve, reject) => {
return resolve({
result: 'email sent to: ' + recipientEmail
});
}));
});
});
Thanks also to: Micha's post
You can send for free with Firebase extensions and Sendgrid:
https://medium.com/firebase-developers/firebase-extension-trigger-email-5802800bb9ea
DEPRECATED: https://www.google.com/settings/security/lesssecureapps
Use "Sign in with App Passwords": https://support.google.com/accounts/answer/185833?hl=en to create an app password.
follow the steps in the link then use the given password with your user in mailTransport:
const mailTransport = nodemailer.createTransport({
service: 'gmail',
auth: {
user: 'user#gmail.com',
pass: 'bunchofletterspassword'
}
});

Resources