Geshan Manandhar Geshan is a seasoned software engineer with more than a decade of software engineering experience. He has a keen interest in REST architecture, microservices, and cloud computing. He also blogs at geshan.com.np.

How to send emails with Node.js using SendGrid

16 min read 4515

Send Emails Nodejs Sendgrid

Email remains one of the most widely used methods of written communication, especially for businesses. Whether to share marketing info, transactional records, or anything else, there are multiple ways to send emails. In this post, we’ll see how we can use Node.js and SendGrid to send an order confirmation (transactional) email to our customers when they place an order.

Why use SendGrid to send emails with Node.js?

We can send emails in a variety of ways from any programming language, Node.js included. We could use a package like Nodemailer, for example, which can use multiple transports like SMTP, Sendmail, or even Amazon SES.

The problem with a random SMTP or Sendmail, however, is that the emails will most likely be classified as spam and land in our customers’ junk folders. And we definitely don’t want that to happen.

To tackle this issue, it is best to use a trusted third-party email SaaS. SendGrid is arguably the industry leader in the transactional email space. Their pricing starts at $14.95 for 50K emails per month.

For our purposes, though, they have a free plan that still lets us send up to 100 emails a day. In addition to better delivery, they provide essential features like analytics and a template editor.

Prerequisites

Below are some of the prerequisites for this hands-on tutorial:

  1. You are generally aware of how JavaScript and Node.js work
  2. You know about the async/await syntax in JavaScript
  3. You know how to use the node command in the command line
  4. You are aware of the Express.js function and how middlewares work in Express.js

Next, we’ll get started with setting up SendGrid to send emails. Let’s get cracking!

How to send emails with Node.js and SendGrid

To send emails using SendGrid from Node.js, we will first need to register for a free SendGrid account. Then we can follow their step-by-step email API integration guide as follows.

Register and generate an API key on SendGrid

To register for a free account, you will need a valid email address and a password of at least 16 characters (pro tip: you can use Password Generator for that):

Sendgrid API Account Generate Homepage Display

Now we can click Create Account. Subsequently, we will need to provide more details about ourselves to continue registering for SendGrid:

SendGrid Create Account User Information Section

Once we have registered (and verified our email), we can Create a Single Sender by clicking the blue button:

SendGrid API Account Generate Homepage Display

In the sender creation form, fill up the details as follows (note that it’s better not to use a general email like Gmail):

Sender Creation Form User Details

After that, we can click the blue Start button under Integrate using our Web API or SMTP relay, as seen below:

 Integrate Web API Button SMTP Relay Choice

Subsequently, we’ll have a choice between using Web API or SMTP Relay. Select the Web API option by clicking the corresponding Choose button:

Web API SMTP Relay Setup Method

Now select the Node.js option, as below:

SendGrid Setup Guide Integrate

Then we can generate the API key we will use in our code. I’ve named it first-key, but you can name it whatever you wish. Copy the API key and keep it safe — we will use it in the next step:

Generate API Key Code

We can go to the end of the above form and check the box I’ve integrated the code above, then click the Next: Verify Integration button.

If you log out and log back in to SendGrid, you will be asked to enable two-factor authentication. You can set it up with your mobile number and SMS.

With this done, we will see how to quickly test sending emails with Node.js using the SendGrid API key.

Set up a simple Node.js script to send email

To set up a new Node.js application with npm, we can run the following:

npm init -y

The -y flag will set all the default values for us. Next, we will set the API key we have made in the sengrid.env file and add it to gitignore as follows:

echo "export SENDGRID_API_KEY='YOUR_API_KEY'" > sendgrid.env && echo "sendgrid.env" >> .gitignore && source ./sendgrid.env

Make sure you replace YOUR_API_KEY in the block above with the API key we generated in the previous step.

After this, we install the @sendgrid/mail npm package since we are only going to send an email, per the scope of this tutorial:

npm i --save @sendgrid/mail

The above command will add the latest version of the @sendgrid/mail npm package to our package.json and install it locally in our node_modules folder.

Next, we will write the following code to test whether we are able to send emails with SendGrid:

const sendGridMail = require('@sendgrid/mail');
sendGridMail.setApiKey(process.env.SENDGRID_API_KEY);

function getMessage() {
  const body = 'This is a test email using SendGrid from Node.js';
  return {
    to: '[email protected]',
    from: '[email protected]',
    subject: 'Test email with Node.js and SendGrid',
    text: body,
    html: `<strong>${body}</strong>`,
  };
}

async function sendEmail() {
  try {
    await sendGridMail.send(getMessage());
    console.log('Test email sent successfully');
  } catch (error) {
    console.error('Error sending test email');
    console.error(error);
    if (error.response) {
      console.error(error.response.body)
    }
  }
}

(async () => {
  console.log('Sending test email');
  await sendEmail();
})();

Let’s quickly review what the above code is doing.

First of all, we are including the @sendgrid/mail package, then we set the API key from the environment variable.



After that, we have a getMessage function that builds the email message containing to, from, subject, and body in text and HTML formats.

Next, we add the sendEmail function, which gets the message and sends the email using the send method in the SendGrid mail package. It is wrapped in a try...catch so that we log any potential errors. If it succeeds, we print a log saying Test email sent successfully.

It is all glued together with an ES8 async IIFE (immediately invoked function expression) to make it all work. I have saved it as send-test-email.js at the root of the project.

Below is a screenshot of how we can run it with node send-test-email.js:

eSendGrid Sendmail Package Node Run Code

After a few seconds, I saw the email in my inbox:

Nodejs SendGrid Test Email Inbox

At this juncture, we can go to the Email Activity page on SendGrid and search by the To email address. We will see the email was sent and opened, as seen below:

Sendgrid Email Activity Page

Hurray! Our proof of concept is working. You can view all the code changes done up to this point in the pull request. Now, we will transform it to be an API that we can call for any transaction email. By way of example, we’ll send an order confirmation email.

Create the send order confirmation email API

For a more practical real-life example, we will now create a send order confirmation email API. This API will be called whenever a user successfully places an order. Let’s say we had an order save method that did four things:

Order Save Method Four Tasks Diagram

Our send order confirmation API call would be called after the customer’s order has been successfully saved in the datastore.

To transform our test script into an API, we will need a web framework; for this, we will use Express.js. To install Express.js, run the following on the project root:

npm i --save express

Then we will add two files: index.js, the web server; and email.js, the service to send the email. The index.js file is below:

const express = require('express');
const email = require('./email');

const app = express();
const port = 3000 || process.env.PORT;

app.use(express.json());
app.use(express.urlencoded({ extended: false }));

app.get('/', (req, res) => {
  res.json({message: 'alive'});
})

app.post('/api/email/order-confirmation', async (req, res, next) => {
  try {
    res.json(await email.sendOrderConfirmation(req.body));
  } catch (err) {
    next(err);
  }
});

app.use((err, req, res, next) => {
  const statusCode = err.statusCode || 500;
  console.error(err.message, err.stack);
  res.status(statusCode).json({'message': err.message});


  return;
});

app.listen(port, () => {
  console.log(`Example API listening at http://localhost:${port}`)
});

Let’s scrutinize some of the main parts of the above Express web server.

First we require Express and initialize it. Then we use JSON- and URL-encoded middlewares to enable us to accept data for the POST endpoint for the order confirmation email.

After that, we create a / route that will show a message like alive. Next, we have the crux of the matter, where we define a POST API at /api/email/order-confirmation. This calls the sendOrderConfirmation method in the email module we’ll define later.

Subsequently, we see a barebones error handler middleware that can handle any errors thrown in the above routes. Finally, we start the server with app.listen and log that the server has started.


More great articles from LogRocket:


Next, we’ll take a look at how our email.js file is written. I have used the Stripo.email free plan to generate the mock order confirmation email; it is part of the template they provided. Below is our email.js file, which compiles the email and sends it using SendGrid:

const sendGridMail = require('@sendgrid/mail');
sendGridMail.setApiKey(process.env.SENDGRID_API_KEY);

function getOrderConfirmationEmailHtml(customerName, orderNr) {
  return `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office" style="width:100%;font-family:Arial, sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;padding:0;Margin:0"><head><meta charset="UTF-8"><meta content="width=device-width, initial-scale=1" name="viewport"><meta name="x-apple-disable-message-reformatting"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta content="telephone=no" name="format-detection"><title>New email template 2021-02-21</title> <!--[if (mso 16)]><style type="text/css">     a {text-decoration: none;}     </style><![endif]--> <!--[if gte mso 9]><style>sup { font-size: 100% !important; }</style><![endif]--> <!--[if gte mso 9]><xml> <o:OfficeDocumentSettings> <o:AllowPNG></o:AllowPNG> <o:PixelsPerInch>96</o:PixelsPerInch> </o:OfficeDocumentSettings> </xml><![endif]--><style type="text/css">#outlook a {    padding:0;}.ExternalClass { width:100%;}.ExternalClass,.ExternalClass p,.ExternalClass span,.ExternalClass font,.ExternalClass td,.ExternalClass div {  line-height:100%;}.es-button {  mso-style-priority:100!important;   text-decoration:none!important;}a[x-apple-data-detectors] { color:inherit!important;    text-decoration:none!important; font-size:inherit!important;    font-family:inherit!important;  font-weight:inherit!important;  line-height:inherit!important;}.es-desk-hidden {    display:none;   float:left; overflow:hidden;    width:0;    max-height:0;   line-height:0;  mso-hide:all;}@media only screen and (max-width:600px) {p, ul li, ol li, a { font-size:16px!important; line-height:150%!important } h1 { font-size:30px!important; text-align:center; line-height:120%!important } h2 { font-size:26px!important; text-align:center; line-height:120%!important } h3 { font-size:20px!important; text-align:center; line-height:120%!important } h1 a { font-size:30px!important } h2 a { font-size:26px!important } h3 a { font-size:20px!important } .es-header-body p, .es-header-body ul li, .es-header-body ol li, .es-header-body a { font-size:16px!important } .es-footer-body p, .es-footer-body ul li, .es-footer-body ol li, .es-footer-body a { font-size:16px!important } .es-infoblock p, .es-infoblock ul li, .es-infoblock ol li, .es-infoblock a { font-size:12px!important } *[class="gmail-fix"] { display:none!important } .es-m-txt-c, .es-m-txt-c h1, .es-m-txt-c h2, .es-m-txt-c h3 { text-align:center!important } .es-m-txt-r, .es-m-txt-r h1, .es-m-txt-r h2, .es-m-txt-r h3 { text-align:right!important } .es-m-txt-l, .es-m-txt-l h1, .es-m-txt-l h2, .es-m-txt-l h3 { text-align:left!important } .es-m-txt-r img, .es-m-txt-c img, .es-m-txt-l img { display:inline!important } .es-button-border { display:block!important } .es-btn-fw { border-width:10px 0px!important; text-align:center!important } .es-adaptive table, .es-btn-fw, .es-btn-fw-brdr, .es-left, .es-right { width:100%!important } .es-content table, .es-header table, .es-footer table, .es-content, .es-footer, .es-header { width:100%!important; max-width:600px!important } .es-adapt-td { display:block!important; width:100%!important } .adapt-img { width:100%!important; height:auto!important } .es-m-p0 { padding:0px!important } .es-m-p0r { padding-right:0px!important } .es-m-p0l { padding-left:0px!important } .es-m-p0t { padding-top:0px!important } .es-m-p0b { padding-bottom:0!important } .es-m-p20b { padding-bottom:20px!important } .es-mobile-hidden, .es-hidden { display:none!important } tr.es-desk-hidden, td.es-desk-hidden, table.es-desk-hidden { width:auto!important; overflow:visible!important; float:none!important; max-height:inherit!important; line-height:inherit!important } tr.es-desk-hidden { display:table-row!important } table.es-desk-hidden { display:table!important } td.es-desk-menu-hidden { display:table-cell!important } .es-menu td { width:1%!important } table.es-table-not-adapt, .esd-block-html table { width:auto!important } table.es-social { display:inline-block!important } table.es-social td { display:inline-block!important } a.es-button, button.es-button { font-size:20px!important; display:block!important; border-width:10px 20px 10px 20px!important } }</style></head>
  <body style="width:100%;font-family:Arial, sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;padding:0;Margin:0"><div class="es-wrapper-color" style="background-color:#555555"> <!--[if gte mso 9]><v:background xmlns:v="urn:schemas-microsoft-com:vml" fill="t"> <v:fill type="tile" color="#555555"></v:fill> </v:background><![endif]--><table class="es-wrapper" width="100%" cellspacing="0" cellpadding="0" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;padding:0;Margin:0;width:100%;height:100%;background-repeat:repeat;background-position:center top"><tr style="border-collapse:collapse"><td valign="top" style="padding:0;Margin:0"><table cellpadding="0" cellspacing="0" class="es-content" align="center" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;table-layout:fixed !important;width:100%"><tr style="border-collapse:collapse"><td align="center" style="padding:0;Margin:0"><table class="es-content-body" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;background-color:transparent;width:600px" cellspacing="0" cellpadding="0" align="center"><tr style="border-collapse:collapse"><td align="left" style="padding:0;Margin:0;padding-bottom:5px;padding-left:10px;padding-right:10px"> <!--[if mso]><table style="width:580px" cellpadding="0" cellspacing="0"><tr><td style="width:280px" valign="top"><![endif]--><table class="es-left" cellspacing="0" cellpadding="0" align="left" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;float:left"><tr style="border-collapse:collapse"><td class="es-m-p0r es-m-p20b" valign="top" align="center" style="padding:0;Margin:0;width:280px"><table width="100%" cellspacing="0" cellpadding="0" role="presentation" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px"><tr style="border-collapse:collapse"><td class="es-infoblock" align="left" style="padding:0;Margin:0;line-height:14px;font-size:12px;color:#A0A7AC"><p style="Margin:0;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;font-size:12px;font-family:Arial, sans-serif;line-height:14px;color:#A0A7AC">Put your preheader text here</p>
  </td></tr></table></td></tr></table> <!--[if mso]></td><td style="width:20px"></td>
  <td style="width:280px" valign="top"><![endif]--><table cellspacing="0" cellpadding="0" align="right" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px"><tr style="border-collapse:collapse"><td align="left" style="padding:0;Margin:0;width:280px"><table width="100%" cellspacing="0" cellpadding="0" role="presentation" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px"><tr style="border-collapse:collapse"><td align="right" class="es-infoblock" style="padding:0;Margin:0;line-height:14px;font-size:12px;color:#A0A7AC"><p style="Margin:0;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;font-size:12px;font-family:Arial, sans-serif;line-height:14px;color:#A0A7AC"><a href="https://viewstripo.email" target="_blank" class="view" style="-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;font-family:Arial, sans-serif;font-size:12px;text-decoration:none;color:#A0A7AC;line-height:18px">SEE THIS EMAIL ONLINE</a></p>
  </td></tr></table></td></tr></table> <!--[if mso]></td></tr></table><![endif]--></td></tr></table></td>
  </tr></table><table class="es-content" cellspacing="0" cellpadding="0" align="center" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;table-layout:fixed !important;width:100%"><tr style="border-collapse:collapse"><td align="center" style="padding:0;Margin:0"><table class="es-content-body" cellspacing="0" cellpadding="0" align="center" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;background-color:#F8F8F8;width:600px"><tr style="border-collapse:collapse"><td style="Margin:0;padding-left:10px;padding-right:10px;padding-top:20px;padding-bottom:20px;background-color:#191919" bgcolor="#191919" align="left"><table width="100%" cellspacing="0" cellpadding="0" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px"><tr style="border-collapse:collapse"><td valign="top" align="center" style="padding:0;Margin:0;width:580px"><table width="100%" cellspacing="0" cellpadding="0" role="presentation" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px"><tr style="border-collapse:collapse"><td align="center" style="padding:0;Margin:0;font-size:0"><a target="_blank" href="https://viewstripo.email/" style="-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;font-family:Arial, sans-serif;font-size:14px;text-decoration:none;color:#3CA7F1"><img class="adapt-img" src="https://ognkca.stripocdn.email/content/guids/CABINET_fb4f0a16f1a866906d2478dd087a5ccb/images/69401502088531077.png" alt width="105" height="101" style="display:block;border:0;outline:none;text-decoration:none;-ms-interpolation-mode:bicubic"></a></td>
  </tr></table></td></tr></table></td>
  </tr><tr style="border-collapse:collapse"><td style="Margin:0;padding-top:20px;padding-bottom:20px;padding-left:20px;padding-right:20px;background-color:#FFCC99" bgcolor="#ffcc99" align="left"><table width="100%" cellspacing="0" cellpadding="0" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px"><tr style="border-collapse:collapse"><td valign="top" align="center" style="padding:0;Margin:0;width:560px"><table width="100%" cellspacing="0" cellpadding="0" role="presentation" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px"><tr style="border-collapse:collapse"><td align="center" style="padding:0;Margin:0;padding-top:15px;padding-bottom:15px"><div><h2 style="Margin:0;line-height:29px;mso-line-height-rule:exactly;font-family:Arial, sans-serif;font-size:24px;font-style:normal;font-weight:normal;color:#242424"><span style="font-size:30px"><strong>Your order is confirmed. </strong></span><br></h2>
  </div></td></tr><tr style="border-collapse:collapse"><td align="center" style="padding:0;Margin:0;padding-left:10px"><p style="Margin:0;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;font-size:14px;font-family:Arial, sans-serif;line-height:21px;color:#242424">Hi ${customerName}, we've received order № ${orderNr} and are working on it now.<br></p><p style="Margin:0;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;font-size:14px;font-family:Arial, sans-serif;line-height:21px;color:#242424">We'll email you an update when we've shipped it.<br></p></td>
  </tr><tr style="border-collapse:collapse"><td align="center" style="Margin:0;padding-left:10px;padding-right:10px;padding-top:15px;padding-bottom:15px"><span class="es-button-border" style="border-style:solid;border-color:#2CB543;background:#191919 none repeat scroll 0% 0%;border-width:0px;display:inline-block;border-radius:20px;width:auto"><a href="https://viewstripo.email/" class="es-button" target="_blank" style="mso-style-priority:100 !important;text-decoration:none;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;font-family:'lucida sans unicode', 'lucida grande', sans-serif;font-size:18px;color:#FFFFFF;border-style:solid;border-color:#191919;border-width:10px 35px;display:inline-block;background:#191919 none repeat scroll 0% 0%;border-radius:20px;font-weight:normal;font-style:normal;line-height:22px;width:auto;text-align:center">View your order details</a></span></td></tr></table></td>
  </tr></table></td>
  </tr><tr style="border-collapse:collapse"><td style="Margin:0;padding-top:10px;padding-bottom:10px;padding-left:10px;padding-right:10px;background-color:#F8F8F8" bgcolor="#f8f8f8" align="left"><table width="100%" cellspacing="0" cellpadding="0" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px"><tr style="border-collapse:collapse"><td valign="top" align="center" style="padding:0;Margin:0;width:580px"><table width="100%" cellspacing="0" cellpadding="0" role="presentation" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px"><tr style="border-collapse:collapse"><td bgcolor="#f8f8f8" align="center" style="Margin:0;padding-left:10px;padding-right:10px;padding-top:20px;padding-bottom:20px;font-size:0"><table width="100%" height="100%" cellspacing="0" cellpadding="0" border="0" role="presentation" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px"><tr style="border-collapse:collapse"><td style="padding:0;Margin:0;border-bottom:1px solid #191919;background:#FFFFFF none repeat scroll 0% 0%;height:1px;width:100%;margin:0px"></td>
  </tr></table></td></tr></table></td></tr></table></td></tr></table></td>
  </tr></table><table cellpadding="0" cellspacing="0" class="es-footer" align="center" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;table-layout:fixed !important;width:100%;background-color:transparent;background-repeat:repeat;background-position:center top"><tr style="border-collapse:collapse"><td align="center" style="padding:0;Margin:0"><table class="es-footer-body" cellspacing="0" cellpadding="0" align="center" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;background-color:#242424;width:600px"><tr style="border-collapse:collapse"><td align="left" style="padding:20px;Margin:0"><table width="100%" cellspacing="0" cellpadding="0" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px"><tr style="border-collapse:collapse"><td valign="top" align="center" style="padding:0;Margin:0;width:560px"><table width="100%" cellspacing="0" cellpadding="0" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px"><tr style="border-collapse:collapse"><td align="center" style="padding:0;Margin:0;display:none"></td>
  </tr></table></td></tr></table></td></tr></table></td>
  </tr></table><table class="es-content" cellspacing="0" cellpadding="0" align="center" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;table-layout:fixed !important;width:100%"><tr style="border-collapse:collapse"><td align="center" style="padding:0;Margin:0"><table class="es-content-body" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;background-color:transparent;width:600px" cellspacing="0" cellpadding="0" align="center"><tr style="border-collapse:collapse"><td align="left" style="Margin:0;padding-left:20px;padding-right:20px;padding-top:30px;padding-bottom:30px"><table width="100%" cellspacing="0" cellpadding="0" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px"><tr style="border-collapse:collapse"><td valign="top" align="center" style="padding:0;Margin:0;width:560px"><table width="100%" cellspacing="0" cellpadding="0" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px"><tr style="border-collapse:collapse"><td align="center" style="padding:0;Margin:0;display:none"></td>
</tr></table></td></tr></table></td></tr></table></td></tr></table></td></tr></table></div></body></html>`;
}

function getMessage(emailParams) {
  return {
    to: emailParams.toEmail,
    from: '[email protected]',
    subject: 'We have got your order, you will receive it soon',
    text: `Hey ${emailParams.name}, we have received your order ${emailParams.orderNr}. We will ship it soon`,
    html: getOrderConfirmationEmailHtml(emailParams.name, emailParams.orderNr),
  };
}

async function sendOrderConfirmation(emailParams) {
  try {
    await sendGridMail.send(getMessage(emailParams));
    return  { message: `Order confirmation email sent successfully for orderNr: ${emailParams.orderNr}`};
  } catch (error) {
    const message = `Error sending order confirmation email or orderNr: ${emailParams.orderNr}`;
    console.error(message);
    console.error(error);
    if (error.response) {
      console.error(error.response.body)
    }
    return {message};
  }
}

module.exports = {
  sendOrderConfirmation
}

Let’s take a deeper look at what email.js is doing.

First we include the sendGrid mail module and add the API key to it. After that, we see a long function getOrderConfirmationEmailHtml, which has the HTML template of our order confirmation email. It takes in two variables, customerName and orderNr, that are filled in dynamically.

Next, we have a getMessage method that puts together the email message with to, from, subject, text, and HTML body. The HTML body is pulled in from the above getOrderConfirmationEmailHtml method.

Consequently, we have the async sendOrderConfirmation function that glues everything together with error handling. Here, we call the send method on the sendGridMail module passing the correct parameters we get from the getMessage function.

If all goes well, we respond back with a positive message inside an object. If there are any errors, we log them and respond back with an error message to the caller, which in this case is the route.

Only the sendOrderConfirmation is exposed from this module at the end of the email.js file.

After we have set up both files correctly, we can start the server with node index.js, which will show us something like the below:

Node Indexjs Server Start Display

Now, we can test our order confirmation email API with the following cURL command:

curl -i -X POST -H 'Accept: application/json' \
    -H 'Content-type: application/json' http://localhost:3000/api/email/order-confirmation \
    --data '{"name":"YourName", "orderNr": "12344", "toEmail": "[email protected]"}'

If everything goes well with the cURL command, we will see a 200 response like below:

Curl Command Success 200 Response

Then, we should be able to see that email in our inbox, too:

Order Confirmation Email Inbox

Congratulations! Our basic order confirmation email is working. As seen above, the two parameters — the customer’s name and order number — are dynamically placed in the email. All the code in this step with the Express.js web server is available in this pull request.

I know the HTML template for the email is a bit too long; we can replace it by using SendGrid’s Dynamic templates. This guide shows how to use it.

Scalability considerations

Depending on how many orders you get in a day, you might want to scale your email sending task. To do this, we mainly have two options.

The first is to use a queue system like RabbitMQ. Whenever a user successfully places an order, a producer will send a message to an exchange, which will be placed in one or more queues depending on the configuration.

Consumers would constantly listen to the queue and process the messages as they land in the queue. We can scale this approach by increasing the number of consumers as the number of messages grows.

Another low-maintenance way to scale the email sending task would be to use a serverless FaaS (function-as-a-service) — something like AWS Lambda or Vercel — to deploy our mini email sending service. The order placement logic can call this API in an async way so that even if the call fails, it is still OK.

The scalability here is handled by AWS, and as the resources are allocated per request, we don’t need to worry about resources, either. The cost factor might come into play if the scale is really big. I will leave further exploration on this topic to you. You may even find free Node.js hosting options that meet your needs.

Conclusion

We have seen how to send emails with Node.js using SendGrid in this step-by-step guide. The order confirmation is just one of many transactional emails we can send. If the email system is set up correctly and templates are dynamic, sending emails becomes a breeze with Node.js and SendGrid.

200’s only Monitor failed and slow network requests in production

Deploying a Node-based web app or website is the easy part. Making sure your Node instance continues to serve resources to your app is where things get tougher. If you’re interested in ensuring requests to the backend or third party services are successful, try LogRocket. https://logrocket.com/signup/

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens while a user interacts with your app. Instead of guessing why problems happen, you can aggregate and report on problematic network requests to quickly understand the root cause.

LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. .
Geshan Manandhar Geshan is a seasoned software engineer with more than a decade of software engineering experience. He has a keen interest in REST architecture, microservices, and cloud computing. He also blogs at geshan.com.np.

One Reply to “How to send emails with Node.js using SendGrid”

  1. Hi there! Really good artcle! Just one minor thing mate, when you are creating the Express application and defining the port, I think you should be first grabbing what it comes from the environment variable and then user port 3000 as fallback, so something like: const port = process.env.PORT || 3000; as you did for example with the statusCode.

Leave a Reply