Most web applications need to send email. It may be for registration, password resets, status reports, though to full marketing campaigns such as newsletters and promotions. This tutorial explains how to send email in Node.js, but the concepts and challenges apply to whatever systems you’re using.
You’ll find plenty of email-related modules on npm. The most popular is NodeMailer, which receives more than three million downloads every week.
To use it, you’ll require an SMTP server which can send email. You may be able to use your own email provider but, for the purposes of this demonstration, I’m using the free WPOven Test SMTP Server.
Create a new project folder:
mkdir emailtest
cd emailtest
Then create a new package.json
file with the following JSON content:
{
"name": "emailtest",
"type": "module",
"main": "index.js",
"dependencies": {
"nodemailer": "^6.0.0"
}
}
Install the modules (NodeMailer):
npm install
and create the following index.js
code:
import nodemailer from 'nodemailer';
const transporter = nodemailer.createTransport({
host: 'smtp.freesmtpservers.com',
port: 25
});
try {
const send = await transporter.sendMail({
from: '"Test Email" <test@email.com>', // sender address
to: 'someone@example.com', // list of receivers
subject: 'Hello!', // subject line
text: 'Hello world!', // plain text body
html: '<p>Hello world!</p>', // HTML body
});
console.dir(send, { depth: null, color: true });
}
catch(e) {
console.dir(e, { depth: null, color: true });
}
(Consider changing the to:
address to something unique so you can examine your own test emails!)
Run the code. You should see a result with a 250 OK
response
and a messageId
:
$ node index.js
{
accepted: [ 'someone@example.com' ],
rejected: [],
ehlo: [ 'SIZE 33554432', '8BITMIME', 'SMTPUTF8', 'HELP' ],
envelopeTime: 486,
messageTime: 289,
messageSize: 595,
response: '250 OK',
envelope: {
from: 'test@email.com',
to: [ 'someone@example.com' ]
},
messageId: '<4673597e-a9e4-e422-85f7-4422edf31774@email.com>'
}
Check the inbox of the to:
address you used by entering it at the WPOven Test SMTP Server page and clicking Access Inbox. Click the “Hello!” message to examine the content.
NodeMailer Basics
To send emails, you must create a NodeMailer transporter object to define the service type. SMTP is most common, but others are available for alternative services. An authentication user ID and password is usually necessary:
import nodemailer from 'nodemailer';
const transporter = nodemailer.createTransport({
host: 'smtp.yourserver.com',
port: 587,
auth: {
user: 'myid@yourserver.com',
pass: 'my-password'
},
});
You can send emails to one or more recipients using the transporter’s sendMail()
method:
const send = await transporter.sendMail({
from: '"Test Email" <test@email.com>', // sender address
to: 'someone@example.com, sometwo@example.com', // list of receivers
cc: 'somethree@example.com',
bcc: 'somefour@example.com',
subject: 'Hello!', // subject line
text: 'Plain text version of the message', // plain text body
html: '<p>HTML version of the message</p>', // HTML body
});
All email clients support plain text messages. You can also send a rich-formatted version of the same message used when the email client supports HTML (more about that below).
NodeMailer provides plenty of other messaging options, but the most common is attachments. An array of objects defines filenames and content. For example:
const send = await transporter.sendMail({
// ...
attachments: [
{ // get file content from disk
filename: 'text1.txt',
path: '/path/to/file1.txt'
},
{ // get file content from a URL
filename: 'text2.txt',
path: 'https://myserver.com/text2.txt'
},
{ // create file from UTF-8 string
filename: 'text3.txt',
content: 'This is the file content!'
},
{ // create file from data URI
filename: 'text4.txt',
path: 'data:text/plain;base64,SGVsbG8gd29ybGQh'
}
]
});
Sending Services
It’s easy to send simple one-off emails, but please don’t underestimate challenge as your requirements evolve.
You may not have an SMTP server. Not all email services provide SMTP (Google is withdrawing basic SMTP support in Gmail).
Most services limit outgoing emails. If you’re sending many emails, you may hit your provider’s limit. At that point, all emails going through the same service will fail: that’s your newsletter as well as personal and business messages.
You may become a spammer. It’s easy for recipients to mark your email as “junk” — even when it’s not. When enough people do that, you could discover all emails from your domain become blocked across the Internet.
It’s better to use a dedicated email service rather than your own mail server. The following services reduce the potential problems and some offer free plans for those with low usage requirements:
- aws.amazon.com/ses
- brevo.com
- clicksend.com
- mailchimp.com
- mailersend.com
- mailgun.com
- mailjet.com
- mailtrap.io
- postmarkapp.com
- sender.net
- sendgrid.com
Asynchronous application architecture
Sending a single email is often fast, but:
- the SMTP server could be down so retries are necessary, or
- the message could get caught in the middle of a bulk newsletter posting
Rather than sending emails directly within your Node.js application, it’s generally better to send the data to a task queue. The end user need not wait for a response and can continue to use the app.
Another process can monitor the email queue, send the next message, and requeue items when a failure occurs.
Crafting HTML Emails
HTML5 and CSS3 work consistently well in modern browsers. Email clients are another matter, taking us back to the frustrating late 1990s days of tables and inline styles.
These are some of the issues you’ll face:
There are dozens of native and web-based email clients including Gmail, Yahoo Mail, Apple Mail, iOS Mail, Android Mail, Windows Mail, Outlook, Outlook.com, (new) Outlook, Thunderbird, AOL, Claws, RoundCube, and so on.
All use their own weird and wonderful rendering engines with unique issues and bugs. Somewhat bizarrely, Outlook has used Microsoft Word to render HTML since 2007 (although the new preview version is browser based).
Most clients block or limit fonts, images, trackers, media queries, iframes, videos, audio, forms, and scripts.
Even web-based email clients running in the browser must remove HTML, CSS, and JavaScript that’s dangerous or that could affect UI layout. For example, it shouldn’t be possible for an email to auto-click its own links or absolutely position an element over a delete button.
Email clients can reformat your HTML to ensure it’s a single column or adheres with the user’s light/dark mode preferences.
It’s possible to hand-code HTML emails but, unless your layout is simple, it’s a difficult, frustrating, and error-prone. The following sections suggest tools and resources that may make your life easier.
Pre-built email templates
The following sites provide free and commercial robust email templates you can preview, download, and use with minimal effort:
Email template design tools
The following no-code design tools allow you to create your own HTML email templates using a simpler WYSWYG editor:
- beefree
- Blocks Edit
- Campaign Monitor
- chamaileon
- fly-brid
- GrapesJS
- knak
- Mail Developer
- Maily
- postcards
- Psd2Newsletters
- Stensul
- Stripo
- TOPOL.io
- unlayer
Some of these services also provide code validation and testing facilities.
Email template conversion
Premailer is a web tool which takes a page URL or pasted source code and transforms it to email-compatible HTML and plain text templates. There’s a REST API and Node.js premailer-api
module should you need to automate the process.
Similar tools include:
Email template markup tools
Cerberus, Email Framework, Email Skeleton, and Good Email Code provide HTML component snippets you can copy and adapt in your own templates.
HEML and MJML are email markup languages. They’re similar to HTML but prevent typical compatibility issues. Maizzle takes a similar approach using Tailwind CSS.
Parcel is a code editor which understands email formatting and can show previews. You’ll also find plenty of email extensions for VS Code.
caniemail.com is the email equivalent of the web page caniuse.com and reports whether a specific HTML or CSS feature is usable across multiple clients. Finally, Accessible Email provides associated resources and links.
Email testing tools
While an HTML email may work in your own email apps, can you be sure it works in others? The following tools will help, but there’s no substitute for testing a range of real devices, OSes, and email clients.
HTML Email Check and MailTrap validate your source code and report problems you could encounter in specific clients.
emailpreview, Mailosaur, and Email Preview Services provide layout preview facilities so you can check how your design will look on a variety of clients.
Finally, Litmus and Email on Acid have a range of tools to validate code, check accessibility, preview across clients, record analytics, and run full marketing campaigns.
Learn how to code emails the right way
As we’ve seen above, there are many tools that can help you to create email layouts that work across the many email clients out there. But there’s nothing like understanding how to code all by yourself, especially when you need to sort out the inevitable bugs that arise.
If you’d like to learn the ins and outs of email coding (even if it’s just as a backup), check out Crafting HTML Email, by Rémi Parmentier. It covers modern perspectives on building your own email templates, essential best practices, how to add interactivity to emails, and how to make your templates accessible. It even walks you through a case study to see all this in practice.
Reading Incoming Email
Most apps need only send emails, but there may be occasions when you want to examine incoming emails — for things like service registration, unsubscribe handling, automated support, and so on. While it’s beyond the scope of this tutorial, Node.js modules such as ImapFlow allow your application to connect to an IMAP inbox, fetch messages, and process a response:
import ImapFlow from 'imapflow';
const client = new ImapFlow({
host: 'imap.email',
port: 993,
secure: true,
auth: {
user: 'account@imap.email',
pass: 'mypassword'
}
});
try {
// connect
await client.connect();
// select and lock inbox
const lock = await client.getMailboxLock('INBOX');
// get latest message
const msg = await client.fetchOne(client.mailbox.exists, { source: true });
console.log( msg.source.toString() );
// release lock
lock.release();
// log out
await client.logout();
}
catch (e) {
console.log(e);
}
Conclusion
Sending emails from Node.js web apps is easy. Sending emails which look good, work reliably in all email clients, don’t halt the user, and don’t cause spam woes can be considerably more difficult.
I recommend you keep emails simple to start, perhaps opting for infrequent plain text messages. Of course, your clients and marketing department will soon want fancy colors and animations, but you can deliver that tomorrow!
Frequently Asked Questions (FAQs) about Sending Emails Using Node.js
How can I attach files to my emails using Node.js?
Attaching files to your emails using Node.js is quite straightforward. You can use the ‘attachments’ property in the mail options. This property takes an array of attachment options. Each attachment option is an object that contains the filename and path properties. The filename property is the name of the file as it will appear in the email, and the path property is the location of the file on your system.
Here’s an example:
let mailOptions = {
from: 'sender@example.com',
to: 'receiver@example.com',
subject: 'Hello',
text: 'Hello world',
attachments: [
{
filename: 'file.txt',
path: '/path/to/file.txt'
}
]
};
Can I send HTML emails using Node.js?
Yes, you can send HTML emails using Node.js. Instead of using the ‘text’ property in the mail options, you use the ‘html’ property. The value of this property is the HTML content of the email.
Here’s an example:
let mailOptions = {
from: 'sender@example.com',
to: 'receiver@example.com',
subject: 'Hello',
html: '<h1>Hello world</h1>'
};
How can I send emails to multiple recipients?
To send emails to multiple recipients, you can provide a list of email addresses separated by commas in the ‘to’ property of the mail options.
Here’s an example:
let mailOptions = {
from: 'sender@example.com',
to: 'receiver1@example.com, receiver2@example.com',
subject: 'Hello',
text: 'Hello world'
};
How can I handle errors when sending emails?
You can handle errors when sending emails by using a callback function. This function is passed as the second argument to the ‘sendMail’ method. The callback function takes two parameters: an error object and an info object. If an error occurs when sending the email, the error object will contain information about the error.
Here’s an example:
transporter.sendMail(mailOptions, function(error, info){
if (error) { console.log(error); } else {console.log('Email sent: ' + info.response); } });
Can I use a Gmail account to send emails?
Yes, you can use a Gmail account to send emails. However, you need to enable ‘Less secure apps’ in your Gmail account settings. Also, you need to use ‘smtp.gmail.com’ as the host and 587 as the port in the transporter options.
Here’s an example:
let transporter = nodemailer.createTransport({
host: 'smtp.gmail.com',
port: 587,
auth: {
user: 'your-email@gmail.com',
pass: 'your-password'
}
});
How can I send emails asynchronously?
You can send emails asynchronously by using Promises. The ‘sendMail’ method returns a Promise that resolves with an info object when the email is sent.
Here’s an example:
transporter.sendMail(mailOptions)
.then(info => console.log('Email sent: ' + info.response))
.catch(error => console.log(error));
Can I use a custom SMTP server to send emails?
Yes, you can use a custom SMTP server to send emails. You need to provide the host, port, and authentication details of the SMTP server in the transporter options.
Here’s an example:
let transporter = nodemailer.createTransport({
host: 'smtp.example.com',
port: 587,
auth: {
user: 'username',
pass: 'password'
}
});
How can I send emails with a specific charset?
You can send emails with a specific charset by using the ‘charset’ property in the mail options. This property sets the charset of the email.
Here’s an example:
let mailOptions = {
from: 'sender@example.com',
to: 'receiver@example.com',
subject: 'Hello',
text: 'Hello world',
charset: 'UTF-8'
};
Can I send emails with a specific content type?
Yes, you can send emails with a specific content type. You can use the ‘contentType’ property in the mail options. This property sets the content type of the email.
Here’s an example:
let mailOptions = {
from: 'sender@example.com',
to: 'receiver@example.com',
subject: 'Hello',
text: 'Hello world'
contentType: 'text/plain
};
How can I send emails with a specific encoding?
You can send emails with a specific encoding by using the ‘encoding’ property in the mail options. This property sets the encoding of the email.
Here’s an example:
let mailOptions = {
from: 'sender@example.com',
to: 'receiver@example.com',
subject: 'Hello',
text: 'Hello world',
encoding: 'base64'
};
Craig is a freelance UK web consultant who built his first page for IE2.0 in 1995. Since that time he's been advocating standards, accessibility, and best-practice HTML5 techniques. He's created enterprise specifications, websites and online applications for companies and organisations including the UK Parliament, the European Parliament, the Department of Energy & Climate Change, Microsoft, and more. He's written more than 1,000 articles for SitePoint and you can find him @craigbuckler.