I'm running a node server and I'm sending emails with SendGrid. I need to separate my email HTMLs from my js files so I can modify them from a single base. What I have now is this:
const express = require('express')
const config = require('config')
const sgMail = require('#sendgrid/mail')
const sendKey = config.get('SENDGRID_API_KEY')
sgMail.setApiKey(sendKey)
const msg = {
to: "test#test.com",
from: "test#test.com",
subject: 'Welcome To The App',
text: 'Text is here',
html: <strong>HTML HERE</strong>
}
sgMail.send(msg)
I want to call my HTML property outside of my current js file instead of writing HTML inside my msg object.
How can I have a separate welcomeEmail.html file and add it to my msg object in my js file?
I've tried fs module but all I have is
Error: ENOENT: no such file or directory, open './welcomeEmail.html'
I couldn't be able to read my HTML file anyway.
Any idea of what I'm missing?
You can use fs, you probably have read from wrong path.
Use this:
fs.readFile('./welcomeEmail.html', 'utf8', (err, content)=>{//do Something});
Make sure welcomeEmail.html is in the right place in your project.
Please remember readFile is async so you should do the rest of your code in the callback, so your code should be something like this (depends on what is the use case):
const express = require('express')
const config = require('config')
const sgMail = require('#sendgrid/mail')
const sendKey = config.get('SENDGRID_API_KEY')
const fs = require('fs')
sgMail.setApiKey(sendKey)
fs.readFile('./welcomeEmail.html', 'utf8', (err, content)=>{
if(err){
console.log(err);
}
else{
let msg = {
to: "test#test.com",
from: "test#test.com",
subject: 'Welcome To The App',
text: 'Text is here',
html: content
}
sgMail.send(msg)
}
});
I'm building my first node/express app and am following this tut.
I am at a point where I am trying to get all JSON data and put it in an array to be sent to the template and rendered. When I try to run the app via CLI, I get the following error:
Directory Structure
The data output at the var blogsurlall location
hellotest.js
var routes = require('./routes/index');
var express = require('express');
var app = express();
var request = require("request");
var blogsurlall = "https://[JSON export URL location configured in a Drupal 8 view]";
app.set('view engine','ejs');
var server = app.listen (2000, function(){ console.log('Waiting for you on port 2000'); });
/* Get all global blogs data */
request({
url: blogsurlall,
json: true
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
blogsdata_all = body;
}
// Create blogs array for footer.
var blogs = [];
// Fill up the array with blogs.
blogsdata_all.blogs.forEach(function(item){
blogs = blogs.concat(item);
});
app.locals.blogsdata = blogs;
});
app.use('/', routes);
index.js
var express = require('express');
var routes = express.Router();
routes.get('/', function(req, res){ res.render('default',{title: 'Home', body: 'blogsdata'}); });
routes.get('/about-us', function(req, res){ res.send('<h1>Lucius Websystems</h1>Amsterdam, The Netherlands'); });
routes.get('/about/:name?', function(req, res){ var name = req.params.name; res.send('<h1>' +name +'</h1>About text'); });
/* GET Blog detail page. */
routes.get('/blog/:blogid', function(req, res, next) {
// Place json data in a var.
var blogsdata = req.app.locals.blogsdata;
// Create array.
var blogItem = [];
// Check and build current URL
var currentURL = '/blog/' + req.params.blogid;
// Lop through json data and pick correct blog-item based on current URL.
blogsdata.forEach(function (item) {
if (item.title == currentURL) {
blogItem = item;
}
});
if (blogItem.length == 0) {
// Render the 404 page.
res.render('404', {
title: '404',
body: '404'
});
} else {
// Render the blog page.
res.render('blog-detail', {
blog: blogItem
});
}
});
module.exports = routes;
From the CLI error, it appears no blog data is even returned to be read into the array.
I have carefully gone through the tutorial several times and I think there are steps that may be implied that I am missing.
Can someone please help me understand how to get the blog data so that it can be read into the array and output to my template?
Also open to troubleshooting suggestions in comments.
Thanks for reading!
The error is raising in this line:
blogsdata_all.blogs.forEach(function(item){
As the error says, blogs is undefined.
If there is an error in the request or status code isn't 200, the body is not assigned to the variable, but you are not finishing the execution, so the variable in that case would be undefined.
Other possible problem is the json received doesn't have blogs as key of the body.
Check this both things and let us know if you found the problem
Created a basic express.js application and added a model (using thinky and rethinkdb) trying to pass the changesfeed to the jade file and unable to figure how to pass the results of the feed. My understanding is that changes() returns infinite cursor. So it is always waiting for new data. How to handle that in express res. Any idea what am I missing here?
var express = require('express');
var router = express.Router();
var thinky = require('thinky')();
var type = thinky.type;
var r = thinky.r;
var User = thinky.createModel('User', {
name: type.string()
});
//end of thinky code to create the model
// GET home page.
router.get('/', function (req, res) {
var user = new User({name: req.query.author});
user.save().then(function(result) {
console.log(result);
});
//User.run().then(function (result) {
//res.render('index', { title: 'Express', result: result });
//});
User.changes().then(function (feed) {
feed.each(function (err, doc) { console.log(doc);}); //pass doc to the res
res.render('index', { title: 'Express', doc: doc}) //doc is undefined when I run the application. Why?
});
});
module.exports = router;
The problem that I believe you are facing is that feed.eachis a loop that is calling the contained function for each item contained in the feed. So to access the doc contained in console.log(doc) you are going to need to either place your code in the function in which doc exists(is in the scope of the variable doc), or you are going to need to make a global variable to store doc value(s).
So for example, assuming doc is a string and that you wish to place all doc's in an array. You would need to start off by creating a variable which has a scope that res.render is in, which for this example will be MYDOCS. Then you would need to append each doc to it, and after that you would simply use MYDOC anytime you are attempting to access a doc outside of the feed.each function.
var MYDOCS=[];
User.changes().then(function (feed){
feed.each(function (err, doc) { MYDOCS.push(doc)});
});
router.get('/', function (req, res) {
var user = new User({name: req.query.author});
user.save().then(function(result) {
console.log(result);
});
//User.run().then(function (result) {
//res.render('index', { title: 'Express', result: result });
//});
res.render('index', { title: 'Express', doc: MYDOCS[0]}) //doc is undefined when I run the application. Why?
});
module.exports = router;
I want to send email with nodemailer using html template. In that template I need to inject some dynamically some variables and I really can't do that. My code:
var nodemailer = require('nodemailer');
var smtpTransport = require('nodemailer-smtp-transport');
smtpTransport = nodemailer.createTransport(smtpTransport({
host: mailConfig.host,
secure: mailConfig.secure,
port: mailConfig.port,
auth: {
user: mailConfig.auth.user,
pass: mailConfig.auth.pass
}
}));
var mailOptions = {
from: 'my#email.com',
to : 'some#email.com',
subject : 'test subject',
html : { path: 'app/public/pages/emailWithPDF.html' }
};
smtpTransport.sendMail(mailOptions, function (error, response) {
if (error) {
console.log(error);
callback(error);
}
});
Let's say I want in emailWithPDF.html something like this:
Hello {{username}}!
I've found some examples, where was smth like this:
...
html: '<p>Hello {{username}}</p>'
...
but I want it in separate html file. Is it possible?
What you can do is read the HTML file using fs module in node and then replace the elements that you want changed in the html string using handlebars
var nodemailer = require('nodemailer');
var smtpTransport = require('nodemailer-smtp-transport');
var handlebars = require('handlebars');
var fs = require('fs');
var readHTMLFile = function(path, callback) {
fs.readFile(path, {encoding: 'utf-8'}, function (err, html) {
if (err) {
callback(err);
}
else {
callback(null, html);
}
});
};
smtpTransport = nodemailer.createTransport(smtpTransport({
host: mailConfig.host,
secure: mailConfig.secure,
port: mailConfig.port,
auth: {
user: mailConfig.auth.user,
pass: mailConfig.auth.pass
}
}));
readHTMLFile(__dirname + 'app/public/pages/emailWithPDF.html', function(err, html) {
if (err) {
console.log('error reading file', err);
return;
}
var template = handlebars.compile(html);
var replacements = {
username: "John Doe"
};
var htmlToSend = template(replacements);
var mailOptions = {
from: 'my#email.com',
to : 'some#email.com',
subject : 'test subject',
html : htmlToSend
};
smtpTransport.sendMail(mailOptions, function (error, response) {
if (error) {
console.log(error);
}
});
});
I use it in all my projects. more clean and up to date and understandable. callback hell doesn't exist.
sendMail.ts The html file reads with handlebar, puts the relevant variables into the contents, and sends.
import * as nodemailer from 'nodemailer';
import * as handlebars from 'handlebars';
import * as fs from 'fs';
import * as path from 'path';
export async function sendEmail(email: string, subject: string, url: string) {
const __dirname = path.resolve();
const filePath = path.join(__dirname, '../emails/password-reset.html');
const source = fs.readFileSync(filePath, 'utf-8').toString();
const template = handlebars.compile(source);
const replacements = {
username: "Umut YEREBAKMAZ"
};
const htmlToSend = template(replacements);
const transporter = nodemailer.createTransport({
host: "smtp.mailtrap.io",
port: 2525, // 587
secure: false,
auth: {
user: "fg7f6g7g67",
pass: "asds7ds7d6"
}
});
const mailOptions = {
from: '"noreply#yourdomain.com" <noreply#yourdomain.com>',
to: email,
subject: subject,
text: url,
html: htmlToSend
};
const info = await transporter.sendMail(mailOptions);
console.log("Message sent: %s", info.messageId);
console.log("Preview URL: %s", "https://mailtrap.io/inboxes/test/messages/");
}
String replace isn't a good idea because you'll have to restore old strings or create a backup file to be able to change them another time, also it won't be asynchrone and it will cause a problem in every way!
you can do it much easier and more cleaner:
just go to your mail options and add context with your variables:
var mailOptions = {
from: 'nginx-iwnl#gmail.com',
to: 'username#gmail.com',
subject: 'Sending email',
template: 'yourTemplate',
context: { // <=
username: username,
whatever: variable
}
};
next thing to do is openning your html file and call your variables like:
{{username}}
If you're using Nodemailer 2.0.0 or higher, check this documentation:
https://community.nodemailer.com/2-0-0-beta/templating/ There they explain how to make use of external rendering with templates like that:
// external renderer
var EmailTemplate = require('email-templates').EmailTemplate;
var send = transporter.templateSender(new EmailTemplate('template/directory'));
They also give this example:
// create template based sender function
// assumes text.{ext} and html.{ext} in template/directory
var sendPwdReminder = transporter.templateSender(new EmailTemplate('template/directory'), {
from: 'sender#example.com',
});
where you see how to pass variables.
You will need the email-templates module: https://github.com/crocodilejs/node-email-templates and a template engine of your choice.
Also in the documentation of email-templates you'll find how to make your file structure in order that your templates can be found:
html.{{ext}} (required) - for html format of email
text.{{ext}} (optional) - for text format of email style.
{{ext}}(optional) - styles for html format subject.
{{ext}}(optional) - for subject of email
See supported template engines for possible template engine extensions (e.g. .ejs, .jade, .nunjucks) to use for the value of {{ext}} above.
You may prefix any file name with anything you like to help you identify the files more easily in your IDE. The only requirement is that the filename contains html., text., style., and subject. respectively.
Create one file emailTemplates.js there yo can store each template as a function
emailTemplates.js
const newsLetterEmail = (clientName) => `<p>Hi ${clientName}, here you have today news.</p>`
const welcomeEmail = (clientName, username) => `<p>Welcome ${clientName}, your username is ${username}.</p>`
export {newsLetterEmail, welcomeEmail}
Then in the controllers call any templateFunction and store in output varaible
controller.js
import {welcomeEmail} from './emailTeamplates.js'
const registerUser = async(req, res) => {
const {name, usename, email} = req.body
// User register code....
const output = welcomeEmail(name, username)
let mailOptions = {
from: '"Welcome" <welcome#welcome.com>',
to: 'client#gmail.com',
subject: 'Welcome email!',
text: 'Hello World',
html: output,
}
For those using pug as templating engine
Just a quick way to render a template in a separate file using pug's render function:
// function to send an e-mail. Assumes you've got nodemailer and pug templating engine installed.
// transporter object relates to nodemailer, see nodemailer docs for details
const nodemailer = require('nodemailer');
const pug = require('pug');
function send_some_mail(iterable){
var message = {
from: 'from#example.com',
to: 'to#example.com',
subject: 'Message title',
html: pug.renderFile(__dirname + 'path_to_template.pug', {iterable: iterable})
};
transporter.sendMail(message, function(err, info){...})
}
// template.pug
each item in iterable
li
p #{item.name}
See https://pugjs.org/api/getting-started.html for further details. Note that this will cause template re-compilation every time a message is sent. That is fine for occasional e-mail deliveries. If you send tons of e-mails, you can cache the compiled template to work around that. Check out pug docs for that set up if you need it.
You can use a Web Request to build an html template using handlebars or any other engine.
Create a template
First you must create an html template for the email body. In this example I used a handlebars hbs file.
Do your design stuff with html and add the variables that you will need in the message:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Welcome Email Template</title>
</head>
<body>
<p style="font-size: 14px; font-weight: normal;">Hi {{data.name}}</p>
</body>
</html>
Create a template request
You must create the access to this view. Then a request is created where we can send the template name as an url parameter to make the request parameterizable for other templates.
const web = express.Router()
web.post('/template/email/:template', function(req, res) {
res.render(`templates/email/${req.params.template}`, {
data: req.body
})
})
Mail function
Finally you can send the email after making the request to the template. You can use a function like the following:
const nodemailer = require('nodemailer')
const request = require("request")
function sendEmail(toEmail, subject, templateFile) {
var options = {
uri: `http://localhost:3000/template/email/${templateFile}`,
method: 'POST',
json: { name: "Jon Snow" } // All the information that needs to be sent
};
request(options, function (error, response, body) {
if (error) console.log(error)
var transporter = nodemailer.createTransport({
host: mailConfig.host,
port: mailConfig.port,
secure: true,
auth: {
user: mailConfig.account,
pass: mailConfig.password
}
})
var mailOptions = {
from: mailConfig.account,
to: toEmail,
subject: subject,
html: body
}
transporter.sendMail(mailOptions, function(error, info) {
if (error) console.log(error)
})
})
}
This can be done without templates.
Try changing it to this:
`Hello ${username}!`
Make sure that these are not inverted commas but back ticks.
There is one easy way to insert variable inside html in nodemailer.
html:"<p>Your message "+variable1+".Message continueous "+variable 2+"</p>"
You can also use async/await syntax or promises and avoid using callbacks, like I did here, using async/await:
const fs = require("fs").promises;
const path = require("path");
const handlebars = require("handlebars");
const relativeTemplatePath = "../../html/reset-pw-email-template.html";
function sendEmail(){
const templatePath = path.join(__dirname, relativeTemplatePath);
const templateFile = await fs.readFile(templatePath, 'utf-8');
const template = handlebars.compile(templateFile);
const replacements = {
username:""
};
const finalHtml = template(replacements);
const mailOptions = {
from: "",
to: "",
subject: "",
html: finalHtml,
};
}
I'm trying to set up an express service for a program that I'm writing that contacts an external API and then returns the results so it can be stored in a Mongo I have set up.
This seems like it should be fairly straightforward, but I'm new to Node.js/Express and I'm getting a "Can't set headers after they are sent" error.
I'm getting the data that I want from the external API, but how to send that data properly back to my Angular app.js so it can update in my table?
"addSelected()" is the function I'm calling in my app.js to kick off the process. The "data" prints part of the way through the full response but then cuts off and gives me the "Can't set Headers after they are sent" error. From what I understand this is from sending the response and then trying to modify the response header after the fact.. but I'm unsure of a workaround or if I'm just formatting everything wrong as this is my first swing at MEAN stack in general.
I know the problem is on the line "res.send(data)" in server.js but I don't know how to correctly format the response.
My code:
server.js
//server.js
//setup ==============================
var express = require ('express');
var request = require('request');
var app = express();
var mongoose = require('mongoose');
var https = require('https');
//config ============================
app.use(express.static(__dirname + '/public/'));
console.log("running PipeHelper");
mongoose.connect('mongoedit');
var Schema = mongoose.Schema;
var opSchema = new Schema({
title: String,
description: String,
company: String,
post_date: String,
close_date: String,
contact: String,
location: String,
url: String,
notice_type: String
});
var item = mongoose.model('item', opSchema);
//routes===========================
//returns full database
app.get('/api/db', function(req, res){
item.find({},function(err, items){
if (err) res.send(err);
res.json(items);
});
});
//searches FBO for opportunities to add to database
app.get('/api/search:FBO_key', function(req, res){
var data;
console.log("2");
var baseURL = "api.data.gov"
var params = "/gsa/fbopen/v0/opps?q=" + req.params.FBO_key;
params += "&api_key="+"keyyyy";
params += "&all";
params += "&start=0";
params += "&p=1";
params += "&limit=10";
url = baseURL+params;
var options = {
port: 443,
host: 'api.data.gov',
path: params,
method: 'GET'
};
//get FBO data
var request = https.request(options, function(response){
console.log("4");
response.on('data', function (chunk){
//response data to send back to app.js
data += chunk.toString();
res.send(data);
});
});
console.log("3");
request.end();
request.on('error', function(e){
console.error(e);
});
});
app.get('/', function(req,res){
res.sendfile('./public/index.html');
});
app.listen(8000);
app.js
var app = angular.module("pipeHelper", ['smart-table']);
app.controller('mainCtrl', [
'$scope', '$http', function($scope, $http){
$scope.selected = [];
$scope.displayData= [];
$scope.items=[];
$scope.FBOsearch;
//populates table on startup with whole DB
$http.get('./api/db')
.success(function(data){
$scope.items=data;
$scope.displayData = [].concat($scope.items);
})
.error(function(data){
console.log('Error: '+data);
});
$scope.addSelected = function(){
//search FBO, add opportunities, update table
console.log("1");
$http.get('./api/search'+'NGA')
.success(function(data){
console.log("5");
console.log(data);
$scope.items=data;
$scope.displayData= [].concat($scope.items);
})
.error(function(data){
console.log('Error: ' +data);
});
};
$scope.isSelected = function(item){
//if its selected, remove it
// if its unselected, add it
if ($scope.selected.indexOf(item)==-1){
$scope.selected.push(item);
}
else{
$scope.selected.splice($scope.selected.indexOf(item), 1);
}
console.log($scope.selected);
//temp placeholder function. Eventually add to array of selected objects for placement in Pipeliner/deletion
};
}]);
solved the issue. I was unaware that response.on('data') gets called multiple times, thus calling my res.send(data) multiple times and incompletely causing it to crash with the error. I added the following to the request function:
response.on('end'function(){
res.send(data);
};
basically when the external API data is finished coming in, send it with express. Learn by doing I suppose. Hope this helps someone eventually.
I can't leave a comment, so I will just make it an answer.
I would recommend installing node-inspector, npm install -g node-debug. Then run your app with node-debug server.js. This will spawn a new instance of Firefox or Chrome dev tools and allows you to debug your nodeJS code. Very useful.
The error you are seeing is most likely related to request.end(), if I were to guess. After .end() is called, you can no longer modify the header content. I doubt it would make a difference, but try putting the request.end() after you have the request.on('error') call.
EDIT: 10/15/15
I would highly recommend installing VS Code. It has a built-in debugger for node apps.