0

I would like to simplify sending html-messages with Nodemailer by using messages stored as html-files instead of "hard-coded" message strings. However, for some reason Nodemailer does not work as I would expect it to do.

The following code works perfectly fine (version with "hard-coded" message string):

const nodemailer = require('nodemailer');
const fs = require('fs');

let htmlMessage = "";

// Retrieve message from file
fs.readFile("./Message.html", 'utf8', (err, data) => {
  if (err) throw err;
  console.log(data)
  htmlMessage = data;
});

console.log(htmlMessage);

// 1.) Define "transporter"
const transporter = nodemailer.createTransport({
  service: ...,
  auth: {
    user: ...,
    pass: ...
  }
})

// 2.) Configure email
const email = {
  from: ...,
  text: 'This is a test! (Plain Text)',
  
  // html: htmlMessage

  html: '<div style="margin: 1em; padding: 0.5em; background-color: rgb(90, 168, 90); font-size: 1.5em; '
  + 'border-radius: 0.5em; font-family: Arial, Helvetica, sans-serif;"> '
  + 'This is a test!'
  + '</div>'

};

// 3.) Send email
transporter.sendMail(email, (error, info) => { if (error) {
  console.error(error); } else {
  console.log('Message sent: %s', info.messageId); }
});

However, if I change the message like this ...

// 2.) Configure email
const email = {
  from: ...,
  text: 'This is a test! (Plain Text)',
  html: htmlMessage

  /*
  html: '<div style="margin: 1em; padding: 0.5em; background-color: rgb(90, 168, 90); font-size: 1.5em; '
  + 'border-radius: 0.5em; font-family: Arial, Helvetica, sans-serif;"> '
  + 'This is a test!'
  + '</div>'
  */
};

... and replace the "hard-coded" string with this file ...

Message.html

<div style="margin: 1em;
  padding: 0.5em;
  background-color: rgb(90, 168, 90);
  font-size: 1.5em;
  border-radius: 0.5em;
  font-family: Arial, Helvetica, sans-serif;">
  This is a test!
</div>

... sending the html content does not work any more. I am only receiving the "plain text version". For some reason Nodemailer fails. What am I doing wrong?

bassman21
  • 320
  • 2
  • 11
  • make sure htmlMessage is not empty at the time of sending – Shafqat Jamil Khan Aug 10 '21 at 11:27
  • htmlMessage is not empty. I console-log it after copying the string retrieved from the html-file to it to make sure that it has the desired content and I am absolutely sure that it is not empty – bassman21 Aug 10 '21 at 11:43
  • Can you share the result of that log? – Shafqat Jamil Khan Aug 10 '21 at 11:52
  • how are you reading the HTML file? if you are using the fs module it has a readFile function where you can specify the encoding. Make sure it is utf8. Example - `fs.readFile( '/message.html', 'utf8', () => {})` – Sibi Kandathil Aug 10 '21 at 12:03
  • I do use the fs module with "utf8"-encoding (as you can see in the first code block of my original post) – bassman21 Aug 10 '21 at 12:24
  • I have been sending not only HTML from a file but HTML templates processed with Handlebars to Nodemailer. So yes, I can confirm that it definitely works when you send it from a file. – slebetman Aug 10 '21 at 12:48

1 Answers1

0

Your problem is one of a common beginner misunderstanding of asynchronous code.

In your code you did this:

let htmlMessage = "";

// 1.) Retrieve message from file
fs.readFile("./Message.html", 'utf8', (err, data) => {

  // 3.) Callback completes
  if (err) throw err;
  console.log(data)
  htmlMessage = data;
});

// ....

console.log(htmlMessage); // SHOULD show empty string

// 2.) Configure email
const email = {
  from: ...,
  text: 'This is a test! (Plain Text)',
  html: htmlMessage
};

As you can see, the code executes 1.) then 2.) then some milliseconds after you have sent your email 3.). You basically have sent your email before reading your HTML file.

What you need to do is:

let htmlMessage = "";

// 1.) Retrieve message from file
fs.readFile("./Message.html", 'utf8', (err, data) => {

  // 2.) Callback completes
  if (err) throw err;
  console.log(data)
  htmlMessage = data;

  // ...

  // 3.) Configure email
  const email = {
    from: ...,
    text: 'This is a test! (Plain Text)',
    html: htmlMessage
  };
});

Alternatively, if you prefer to read the flow of your original code you can promisify fs.readFile() and use async/await:

const util = require('util');

const readFile = util.promisify(fs.readFile);

async function foo () {

  // 1.) Retrieve message from file
  let htmlMessage = await readFile("./Message.html", 'utf8');

  // ....

  // 2.) Configure email
  const email = {
    from: ...,
    text: 'This is a test! (Plain Text)',
    html: htmlMessage
  };
}

foo();
slebetman
  • 109,858
  • 19
  • 140
  • 171