spirosgyros.net

Setting Up Email in Node.js: A Comprehensive Guide

Written on

Chapter 1: The Importance of Email

Email plays a crucial role in online communication. Its ability to transmit messages electronically dates back before the advent of the internet, making it one of the most vital and universally accepted methods of digital communication. If you're developing online software, integrating email functionality is indispensable.

However, the initial setup can often prove to be more complicated than expected. This article will guide you through the process of incorporating this essential feature into your applications using a reliable tech stack tailored for Node.js. This setup is versatile, suitable for both simple projects and large-scale applications; at my company, we leverage a similar architecture to dispatch system notifications to hundreds of thousands of users daily. For a practical demonstration, refer to this repository.

The Challenges of Sending Emails with Node.js

Working with email in Node.js can introduce several challenges. The Simple Mail Transfer Protocol (SMTP) has limitations compared to HTTP, which is designed for web pages. When it comes to email design, you're restricted to a limited set of HTML and CSS, primarily due to security concerns and the inherent nature of older technology. For instance, if you're trying to create a button, you can't use a standard HTML <button> element; instead, you'll need to resort to other HTML structures.

Compounding these issues are the quirks of legacy email clients, such as Microsoft Outlook. Given its vast user base, Outlook's specific requirements add complexity and reduce code clarity.

Moreover, transitioning from languages like PHP can leave you puzzled, especially since many PHP web servers come with built-in sendmail capabilities, whereas Node.js does not provide this out of the box.

Don't fret! There are various tools available to navigate these challenges. This article will present a straightforward blueprint to seamlessly integrate email functionality into your Node.js applications.

Section 1.1: The Solution

With the appropriate tools, we can tackle the aforementioned challenges effectively. After a bit of initial setup, the rest of the process is relatively smooth.

The primary tools we will utilize include:

  • Nodemailer: The leading Node.js library for sending emails.
  • MJML: A framework for creating responsive and compatible email designs, along with Handlebars for dynamic content support in MJML templates.
  • Amazon SES: A cloud-based SMTP server and interface.

Make sure you have Node.js installed to follow along with this guide. If you prefer video tutorials, check out my video version of this guide below.

Alternative Options

While the tools I've selected are popular and well-established, there are other alternatives that may better suit your specific needs. If Nodemailer doesn't meet your requirements, consider EmailJS. For responsive designs, Foundation for Emails is a good choice. You can explore additional templating systems in my article on HTML templating options. If you're looking for cloud-based SMTP services, consider options like Mailgun and Sendgrid.

Section 1.2: Designing Our Email Service

When creating a new library or service, it's beneficial to envision how the code will be utilized. I want the email-sending process to be simple and intuitive. For example, consider this straightforward implementation:

await email.onboarding.send("[email protected]", variables, options);

In this case, information such as the sender, subject, cc, bcc, attachments, and HTML content can be defined for each template. If there’s a need to override any of these settings, we can do so through the options parameter.

Next, let's craft a basic email service that minimizes the effort required each time a new email template is introduced.

Subsection 1.2.1: Project Setup

We’ll begin by creating a new directory for our application.

mkdir nodejs-nodemailer-tutorial

cd nodejs-nodemailer-tutorial

Once inside, we can initialize a new Node.js project.

npm init -y

Then, we can install the required dependencies.

npm i @aws-sdk/client-ses handlebars mjml nodemailer

I'm opting for TypeScript, which is increasingly becoming the standard for JavaScript server projects. Let's add a basic tsconfig.json file:

{

"compilerOptions": {

"target": "es2016",

"module": "commonjs",

"esModuleInterop": true,

"forceConsistentCasingInFileNames": true,

"strict": true,

"skipLibCheck": true

}

}

To run TypeScript and to include type definitions for some libraries, we’ll add additional development dependencies.

npm i -D @types/mjml @types/nodemailer nodemon ts-node typescript

Finally, modify the package.json to set the main entry point and define a script to run the application.

{

"main": "src/index.ts",

"scripts": {

"start": "ts-node src/index.ts"

}

}

The Transporter

First, we need to create a transporter—essentially a client for sending our emails. For production, we’ll utilize Amazon SES, while during development, we can use a mail catcher like Ethereal to preview emails without actually sending them. We’ll switch to the SES version when the NODE_ENV is set to "production".

// src/services/email/transporter.ts

import * as nodemailer from "nodemailer";

import * as aws from "@aws-sdk/client-ses";

function getTransporter() {

if (process.env.NODE_ENV === "production") {

const ses = new aws.SES({

apiVersion: "2010-12-01",

region: "eu-west-2",

credentials: {

accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? "",

secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? "",

},

});

return nodemailer.createTransport({

SES: { ses, aws },

});

} else {

return nodemailer.createTransport({

host: "smtp.ethereal.email",

port: 587,

secure: false,

auth: {

user: "[email protected]",

pass: "jn7jnAPss4f63QBp6D",

},

});

}

}

export const transporter = getTransporter();

The Base Email

To share functionality across different email types, we can create a base class that individual email classes can extend. Here's the basic structure.

// src/services/email/base.ts

export abstract class BaseEmail {

protected from: string;

protected subject: string | ((variables: T) => string);

protected template: string;

constructor({

from,

subject,

template,

}: {

from: string;

subject: string | ((variables: T) => string);

template: string;

}) {

this.from = from;

this.subject = subject;

this.template = template;

}

}

We support both static strings and dynamic callbacks for the subject. Now, let's implement a generic send function to manage the email-sending process.

// src/services/email/base.ts

import fs from "fs/promises";

import path from "path";

import mjml2html from "mjml";

import * as nodemailer from "nodemailer";

import * as handlebars from "handlebars";

import { transporter } from "./transporter";

export abstract class BaseEmail {

// existing code...

public async send(

to: string,

variables: T,

options?: nodemailer.SendMailOptions

) {

const mjml = await fs.readFile(this.template, "utf-8");

const html = mjml2html(handlebars.compile(mjml)(variables)).html;

await transporter.sendMail({

to,

from: this.from,

subject: typeof this.subject === "string" ? this.subject : this.subject(variables),

html,

...options,

}).then((info) => {

console.info("Message sent: ", info.messageId);

const previewUrl = nodemailer.getTestMessageUrl(info);

if (previewUrl) {

console.info("Preview URL: ", previewUrl);

}

});

}

}

Templates

Now that we have our email structure, it's time to create the templates. Inside the src/email directory, we’ll create a templates folder, along with subfolders for onboarding and referral email templates.

/email/

└── /templates/

├── /onboarding/

│ ├── template.mjml

│ └── index.ts

└── /referral/

├── template.mjml

└── index.ts

Here’s a simple onboarding template:

Hi {{firstName}},

You're invited!

To claim your account and login, click the button below:

Login

In index.ts, we extend the BaseEmail class:

// src/services/email/templates/onboarding/index.ts

import path from "path";

import { BaseEmail } from "../../base";

interface OnboardingEmailVariables {

firstName: string;

loginUrl: string;

}

export class OnboardingEmail extends BaseEmail {

constructor() {

super({

from: "[email protected]",

subject: "Welcome!",

template: path.join(__dirname, "template.mjml"),

});

}

}

For the referral email, we can create a similar structure:

Hello {{firstName}}

You've been referred by {{referrer.firstName}} {{referrer.lastName}}!

Click the button below to claim your account:

Claim Account

And the corresponding index.ts file:

// src/services/email/templates/referral/index.ts

import path from "path";

import { BaseEmail } from "../../base";

interface ReferralEmailVariables {

firstName: string;

referrer: {

firstName: string;

lastName: string;

};

loginUrl: string;

}

export class ReferralEmail extends BaseEmail {

constructor() {

super({

from: "[email protected]",

subject: ({ referrer }: ReferralEmailVariables) => You've been referred by ${referrer.firstName}!,

template: path.join(__dirname, "template.mjml"),

});

}

}

The Email Service

Next, we’ll group our email templates in an EmailService class.

// src/services/email/index.ts

import { OnboardingEmail } from "./templates/onboarding";

import { ReferralEmail } from "./templates/referral";

class EmailService {

onboarding = new OnboardingEmail();

referral = new ReferralEmail();

}

export const email = new EmailService();

Finally, we can test our setup in the root index.ts file:

// src/index.ts

import { email } from "./services/email";

(async () => {

await email.onboarding.send("[email protected]", {

firstName: "John",

});

await email.referral.send("[email protected]", {

firstName: "John",

referrer: {

firstName: "Jane",

lastName: "Doe",

},

});

})();

Run the application with:

npm run start

You should see messages like:

Message sent: <[email protected]>

To enhance your emails, you can refer to the first video linked above for a visual guide.

By following this guide, you can set up a robust email-sending system in your Node.js applications. Happy coding!

Share the page:

Twitter Facebook Reddit LinkIn

-----------------------

Recent Post:

# Boost Your Productivity: Simple Strategies for Success

Discover actionable tips to enhance your productivity and manage your time effectively.

# Exploring the Dangers and Opportunities of AI Technology

Examining Jacob Ward's insights on AI's impact on human choices and behavior, emphasizing the need for ethical oversight.

How Melting Glaciers Might Aid in Combating Climate Change

Explore how glacial rock flour could serve as a powerful tool for carbon capture and improve agricultural practices.