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.
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.
Below are some of the prerequisites for this hands-on tutorial:
node
command in the command lineNext, we’ll get started with setting up SendGrid to send emails. Let’s get cracking!
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.
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):
Now we can click Create Account. Subsequently, we will need to provide more details about ourselves to continue registering for SendGrid:
Once we have registered (and verified our email), we can Create a Single Sender by clicking the blue button:
In the sender creation form, fill up the details as follows (note that it’s better not to use a general email like Gmail):
After that, we can click the blue Start button under Integrate using our Web API or SMTP relay, as seen below:
Subsequently, we’ll have a choice between using Web API or SMTP Relay. Select the Web API option by clicking the corresponding Choose button:
Now select the Node.js option, as below:
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:
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.
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
:
After a few seconds, I saw the email in my 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:
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.
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:
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.
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:
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:
Then, we should be able to see that email in our inbox, too:
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.
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.
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.
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.
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. Start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowCompare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
2 Replies to "How to send emails with Node.js using SendGrid"
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.
Hey, great article! Just a small suggestion, when setting up the Express application and specifying the port, it might be beneficial to prioritize fetching the port value from the environment variable first, and then fallback to using port 3000 if it’s not available.
Thank you.