nodemailer, sendgrid (@sendgrid/mail), mailgun-js, and emailjs are all tools used to send emails from JavaScript applications, but they operate in different ways. nodemailer is a module for Node.js applications to send emails via SMTP, supporting various transports and OAuth2. sendgrid and mailgun-js are official SDKs for specific email service providers (ESPs) that use HTTP APIs instead of SMTP. emailjs refers to an SMTP client for Node.js (distinct from the browser-focused emailjs-com), offering a lighter alternative to nodemailer. Choosing between them depends on whether you need a generic SMTP solution or a tightly integrated API for a specific email provider.
When building backend services in Node.js, sending emails is a common requirement for things like password resets, notifications, and newsletters. You generally have two paths: use a generic SMTP library like nodemailer or emailjs, or use a specific provider's API SDK like sendgrid or mailgun-js. Each approach has trade-offs in setup, flexibility, and vendor lock-in. Let's break down how they handle the core tasks of sending email.
The biggest difference lies in how these libraries talk to the mail server.
nodemailer uses SMTP (Simple Mail Transfer Protocol).
// nodemailer: SMTP Transport
const transporter = nodemailer.createTransport({
host: 'smtp.example.com',
port: 587,
secure: false,
auth: { user: 'user', pass: 'pass' }
});
await transporter.sendMail({ from, to, subject, text });
emailjs also uses SMTP but aims for a lighter footprint.
nodemailer but with a different API structure.// emailjs: SMTP Client
const server = email.server.connect({
host: 'smtp.example.com',
ssl: true,
user: 'user',
password: 'pass'
});
server.send({ from, to, subject, text }, callback);
sendgrid uses Twilio SendGrid's HTTP API.
// sendgrid (@sendgrid/mail): HTTP API
const msg = {
to: 'recipient@example.com',
from: 'sender@example.com',
subject: 'Hello',
text: 'World',
};
await sgMail.send(msg);
mailgun-js uses Mailgun's HTTP API.
// mailgun-js: HTTP API
const formData = {
from: 'You <mailgun@yourdomain.com>',
to: 'recipient@example.com',
subject: 'Hello',
text: 'World'
};
mailgun.messages().send(formData, callback);
How you build the email content varies slightly between libraries.
nodemailer uses a straightforward object for message data.
// nodemailer: Message Object
const mailOptions = {
from: 'sender@example.com',
to: 'recipient@example.com',
subject: 'Test',
html: '<b>Hello</b>',
attachments: [{ path: 'file.pdf' }]
};
emailjs uses a similar object but with slightly different keys.
// emailjs: Message Object
const message = {
from: 'sender@example.com',
to: 'recipient@example.com',
subject: 'Test',
text: 'Hello',
attachment: [{ data: fs.readFileSync('file.pdf') }]
};
sendgrid uses a structured object tailored to SendGrid features.
// sendgrid: Message Object
const msg = {
to: 'recipient@example.com',
from: 'sender@example.com',
templateId: 'd-your-template-id',
dynamic_template_data: { name: 'User' },
attachments: [{ content: base64Data, filename: 'file.pdf' }]
};
mailgun-js uses form-data style properties.
o: or passed in h: headers.// mailgun-js: Message Data
const data = {
from: 'sender@yourdomain.com',
to: 'recipient@example.com',
subject: 'Test',
text: 'Hello',
inline: fs.readFileSync('image.png')
};
Setting up these libraries ranges from installing a package to configuring DNS records.
nodemailer requires zero external dependencies beyond the package.
// nodemailer: Install and Init
// npm install nodemailer
import nodemailer from 'nodemailer';
// Configure transport as shown above
emailjs is also a simple npm install.
nodemailer.// emailjs: Install and Init
// npm install emailjs
import email from 'emailjs';
// Connect to server as shown above
sendgrid requires an API Key from the SendGrid dashboard.
// sendgrid: Install and Init
// npm install @sendgrid/mail
import sgMail from '@sendgrid/mail';
sgMail.setApiKey(process.env.SENDGRID_API_KEY);
mailgun-js requires an API Key and a verified Domain.
// mailgun-js: Install and Init
// npm install mailgun-js
const mailgun = require('mailgun-js')({ apiKey, domain });
This is a critical factor for long-term projects.
nodemailer is actively maintained and widely adopted.
emailjs has slower update cycles and a smaller community.
nodemailer if you want long-term security.sendgrid is actively maintained by Twilio.
@sendgrid/mail package is the current standard.mailgun-js is considered legacy; the new package is mailgun.js.
mailgun-js in new projects is not recommended.mailgun.js client instead.You need to send welcome emails quickly without paying for a service.
nodemailer// nodemailer with Gmail
const transporter = nodemailer.createTransport({
service: 'gmail',
auth: { user, pass }
});
You need to send password resets for thousands of users daily with tracking.
sendgrid// sendgrid with tracking
const msg = { ... , trackingSettings: { clickTracking: { enable: true } } };
You are fixing bugs in an older app that already uses Mailgun.
mailgun-js (or migrate to mailgun.js)// mailgun-js existing code
mailgun.messages().send(data, ...);
You have a small containerized service that just needs to alert admins.
emailjs// emailjs simple alert
server.send(message, callback);
| Feature | nodemailer | emailjs | sendgrid | mailgun-js |
|---|---|---|---|---|
| Protocol | SMTP | SMTP | HTTP API | HTTP API |
| Vendor Lock-in | None (Generic) | None (Generic) | High (Twilio) | High (Mailgun) |
| Maintenance | ✅ Active | ⚠️ Slow | ✅ Active | ⚠️ Legacy (Use mailgun.js) |
| Setup Complexity | Low | Low | Medium (API Key) | High (DNS + API) |
| Best For | General Purpose | Simple SMTP | Transactional | Marketing/Devops |
For most Node.js developers, nodemailer is the safest and most flexible choice. It lets you change email providers without rewriting your code, which is a huge benefit as your project grows. It is the standard for a reason — it works everywhere.
If you need advanced features like detailed analytics, template management, or guaranteed deliverability at scale, pick a provider-specific SDK like sendgrid. Just remember that you are locking yourself into their platform.
Avoid mailgun-js for new work; use mailgun.js if you choose Mailgun. Use emailjs only if you have a specific reason to prefer it over nodemailer, as the community support is much smaller.
Final Thought: Email is critical infrastructure. Don't over-optimize for bundle size here — optimize for deliverability and maintainability. Pick the tool that ensures your emails actually reach the inbox.
Choose nodemailer if you need a flexible, provider-agnostic solution that works with any SMTP server, including Gmail, AWS SES, or your own mail server. It is the industry standard for Node.js email sending, offering a rich plugin ecosystem for templates, OAuth2, and custom transports. This is the best choice for applications that might switch email providers later or need full control over the SMTP connection without being locked into a specific vendor's API.
Choose emailjs if you need a lightweight SMTP client for Node.js and prefer a simpler API than nodemailer for basic use cases. It is suitable for small projects where you manage your own SMTP server or use a standard provider without needing advanced plugins. However, be aware that it has a smaller community and fewer features compared to nodemailer, so verify its maintenance status before committing to large-scale production use.
Choose mailgun-js only if you are maintaining a legacy project that already depends on it, as the official library has moved to mailgun.js. For new projects, avoid this package in favor of the updated mailgun.js SDK or other alternatives. It connects to the Mailgun HTTP API, which is useful if you need advanced tracking and templating features specific to Mailgun, but ensure you are using the actively maintained version.
Choose sendgrid (@sendgrid/mail) if you are using Twilio SendGrid as your email provider and want official support for their HTTP API features. It simplifies sending transactional emails with built-in support for templates, tracking, and attachments without managing SMTP credentials. This is ideal for teams already invested in the SendGrid ecosystem who need reliable delivery and detailed analytics provided directly through the API.
Send emails from Node.js – easy as cake! 🍰✉️
See nodemailer.com for documentation and terms.
Nodemailer is developed by the team behind EmailEngine, a self-hosted email API that turns any Gmail, Microsoft 365, or IMAP account into a REST endpoint, with managed OAuth2, webhooks for incoming mail, and built-in sending. If you would rather call an HTTP API than maintain IMAP and SMTP connections yourself, that is what it is for.
Nodemailer targets Node.js 20 and later. The ES module build also runs on Bun (tested against the latest release) and on Cloudflare Workers with the nodejs_compat compatibility flag. On Workers only the SMTP based transports apply, sendmail needs a child process, and the runtime does not allow turning certificate validation off, so tls.rejectUnauthorized: false fails there with an error.
Documentation for Nodemailer can be found at nodemailer.com.
Nodemailer 10 and later require Node.js 20 or newer. The package ships both ES module and CommonJS builds, so import nodemailer from 'nodemailer' and const nodemailer = require('nodemailer') both work. If you need to run on an older Node.js version, stay on the 9.x line.
Gmail either works well, or it does not work at all. It is probably easier to switch to an alternative service instead of fixing issues with Gmail. If Gmail does not work for you, then don't use it. Read more about it here. If the blocker is OAuth2 setup rather than Gmail itself, EmailEngine handles the OAuth2 flow and token refresh for you.
Check your firewall settings. Timeout usually occurs when you try to open a connection to a firewalled port either on the server or on your machine. Some ISPs also block email ports to prevent spamming.
It's either a firewall issue, or your SMTP server blocks authentication attempts from some servers.
secure option. This should be set to true only for port 465. For every other port, it should be false. Setting it to false does not mean that Nodemailer would not use TLS. Nodemailer would still try to upgrade the connection to use TLS if the server supports it.false to skip chain verification or upgrade your Node versionlet configOptions = {
host: 'smtp.example.com',
port: 587,
tls: {
rejectUnauthorized: true,
minVersion: 'TLSv1.2'
}
};
Node.js uses c-ares to resolve domain names, not the DNS library provided by the system, so if you have some custom DNS routing set up, it might be ignored. Nodemailer runs dns.resolve4() and dns.resolve6() to resolve hostname into an IP address. If both calls fail, then Nodemailer will fall back to dns.lookup(). If this does not work for you, you can hard code the IP address into the configuration like shown below. In that case, Nodemailer would not perform any DNS lookups.
let configOptions = {
host: '1.2.3.4',
port: 465,
secure: true,
tls: {
// must provide server name, otherwise TLS certificate check will fail
servername: 'example.com'
}
};
Nodemailer 10 and later are written in TypeScript and ship their own type definitions, so @types/nodemailer is no longer needed and should be removed from your project to avoid conflicting declarations. The type names follow the layout of the old definitions, so references such as Mail.Options, SMTPTransport.Options or Transporter<SMTPTransport.SentMessageInfo> keep compiling, and the most used types (SendMailOptions, Transporter, SentMessageInfo, Attachment, Address) are exported from the package root. For older Nodemailer versions, the community maintained type definitions still apply.
If you are having issues with Nodemailer, then the best way to find help would be Stack Overflow or revisit the docs.
Nodemailer is licensed under the MIT No Attribution license
The Nodemailer logo was designed by Sven Kristjansen.