These five libraries enable Node.js applications to send emails, but they use different underlying methods. nodemailer is a standalone SMTP client that works with any email server. @sendgrid/mail, mailgun-js, resend, and sendgrid are SDKs for specific Email API services. The API-based tools handle delivery infrastructure for you, while nodemailer requires you to manage an SMTP server or relay. Choosing the right one depends on whether you need a specific provider's features or a generic SMTP solution.
These five libraries solve the same problem — sending emails from a Node.js backend — but they take different paths. nodemailer uses the SMTP protocol to talk to any mail server. The others (@sendgrid/mail, mailgun-js, resend, sendgrid) are HTTP API clients for specific email delivery services. Let's compare how they handle setup, sending, and attachments.
@sendgrid/mail uses an API Key set globally or per request.
const sgMail = require('@sendgrid/mail');
sgMail.setApiKey(process.env.SENDGRID_API_KEY);
mailgun-js requires your domain and API Key during initialization.
const mailgun = require('mailgun-js');
const mg = mailgun({ apiKey: process.env.MAILGUN_KEY, domain: 'yourdomain.com' });
nodemailer needs SMTP credentials (host, port, user, pass).
const nodemailer = require('nodemailer');
const transporter = nodemailer.createTransport({
host: 'smtp.example.com',
port: 587,
auth: { user: 'user', pass: 'pass' }
});
resend initializes with an API Key to create a client instance.
const { Resend } = require('resend');
const resend = new Resend(process.env.RESEND_API_KEY);
sendgrid (Legacy) uses a constructor with the API Key.
const sendgrid = require('sendgrid');
const sg = new sendgrid.SendGrid(process.env.SENDGRID_API_KEY);
@sendgrid/mail sends a message object with to, from, subject, and content.
await sgMail.send({
to: 'user@example.com',
from: 'me@example.com',
subject: 'Hello',
text: 'World',
html: '<strong>World</strong>'
});
mailgun-js uses a messages().send() method with form-style data.
const data = {
from: 'Me <me@example.com>',
to: 'user@example.com',
subject: 'Hello',
text: 'World',
html: '<strong>World</strong>'
};
await mg.messages().send(data);
nodemailer uses sendMail with a similar object structure.
await transporter.sendMail({
from: 'me@example.com',
to: 'user@example.com',
subject: 'Hello',
text: 'World',
html: '<strong>World</strong>'
});
resend uses a clean emails.send method with arrays for recipients.
await resend.emails.send({
from: 'me@example.com',
to: ['user@example.com'],
subject: 'Hello',
text: 'World',
html: '<strong>World</strong>'
});
sendgrid (Legacy) uses a SendGrid instance and a Email object.
const email = new sendgrid.Email({
to: 'user@example.com',
from: 'me@example.com',
subject: 'Hello',
text: 'World',
html: '<strong>World</strong>'
});
sg.send(email);
@sendgrid/mail expects attachments as an array of objects with content (base64).
const attachment = {
content: Buffer.from('file content').toString('base64'),
filename: 'doc.pdf',
type: 'application/pdf'
};
await sgMail.send({ to: '...', from: '...', subject: '...', attachments: [attachment] });
mailgun-js accepts file paths or streams in the attachment array.
const data = {
to: 'user@example.com',
from: 'me@example.com',
subject: 'File',
text: 'See attached',
attachment: ['/path/to/file.pdf']
};
await mg.messages().send(data);
nodemailer handles attachments flexibly with paths or buffers.
await transporter.sendMail({
to: 'user@example.com',
from: 'me@example.com',
subject: 'File',
text: 'See attached',
attachments: [{ path: '/path/to/file.pdf' }]
});
resend uses an array of objects with content (buffer or string) and filename.
await resend.emails.send({
to: ['user@example.com'],
from: 'me@example.com',
subject: 'File',
text: 'See attached',
attachments: [{ filename: 'doc.pdf', content: Buffer.from('file') }]
});
sendgrid (Legacy) uses the addFile method on the Email object.
const email = new sendgrid.Email({ /*...*/ });
email.addFile('/path/to/file.pdf');
sg.send(email);
sendgrid is deprecated. It was the v2 SDK and is no longer updated. Using it in new projects is risky because it lacks security patches and new features. You should treat it as legacy code that needs migration to @sendgrid/mail.
mailgun-js is a community-maintained wrapper. The official Mailgun SDK is now mailgun.js. While mailgun-js still works, it may not support the latest API endpoints. For long-term stability, verify if mailgun.js fits your needs better.
nodemailer is protocol-based. It does not depend on a specific company's API uptime. This makes it very stable for local testing or self-hosted servers. However, you must manage your own SMTP relay for production delivery to avoid spam folders.
@sendgrid/mail and resend are modern API clients. They abstract away SMTP entirely. This reduces setup time but locks you into their delivery infrastructure. resend is newer and focuses heavily on developer experience, while @sendgrid/mail is an enterprise standard.
While the implementation differs, all five libraries share core concepts for defining email messages.
from, to, subject, and text/html fields.// Common structure across all 5
const msg = {
from: 'me@example.com',
to: 'user@example.com',
subject: 'Test',
text: 'Hello'
};
await to handle sending logic cleanly.// All support this pattern
try {
await client.send(msg);
} catch (error) {
console.error(error);
}
.env files.// All rely on this
const key = process.env.API_KEY;
| Feature | Shared by All 5 Packages |
|---|---|
| Core Fields | 📨 From, To, Subject, Body |
| Async Model | ⚡ Promises / Async-Await |
| Security | 🔐 API Keys or SMTP Auth |
| Attachments | 📎 Supported (varying formats) |
| Node Version | 🟢 Compatible with modern Node.js |
| Feature | API SDKs (@sendgrid/mail, resend, mailgun-js, sendgrid) | SMTP (nodemailer) |
|---|---|---|
| Protocol | 🌐 HTTP REST API | 📠 SMTP Protocol |
| Infrastructure | ☁️ Managed by Provider | 🖥️ You Manage Relay |
| Setup Time | ⚡ Fast (API Key only) | 🐢 Slower (SMTP Config) |
| Vendor Lock-in | 🔒 High (Specific Provider) | 🔓 Low (Any SMTP Server) |
| Status | ⚠️ sendgrid is Deprecated | ✅ Stable |
nodemailer is the universal adapter 🔌. Use it when you need flexibility, local testing without external dependencies, or when you already have an SMTP server. It keeps your code portable between providers.
@sendgrid/mail and resend are the modern specialists 🚀. Use them when you want a managed service that handles deliverability, tracking, and templates for you. resend is great for new apps wanting a fresh DX; @sendgrid/mail is best for enterprise needs.
mailgun-js and sendgrid are the legacy options 🕰️. Avoid sendgrid entirely in new work. Use mailgun-js only if you are stuck with it; otherwise, look at mailgun.js or switch providers.
Final Thought: For most modern web apps, an API-based service like resend or @sendgrid/mail saves the most time. If you need total control or offline capability, nodemailer remains the gold standard for SMTP.
Choose nodemailer if you need to send emails via SMTP without locking into a specific API provider. It is ideal for local development, self-hosted mail servers, or when you want to switch email providers without changing code. It is the most flexible option for generic email sending needs.
Choose resend if you want a modern, developer-first email API with a clean SDK and React email template support. It is excellent for new projects that prioritize ease of use, strong TypeScript types, and a simplified setup process. This is a top pick for startups and teams wanting a fresh alternative to legacy providers.
Choose @sendgrid/mail if you are using SendGrid as your email provider and want the official, maintained SDK. It offers full access to SendGrid features like templates, tracking, and batch sending. This is the standard choice for teams already invested in the SendGrid ecosystem who need reliable API integration.
Choose mailgun-js only if you are maintaining a legacy project that already depends on it. For new projects, prefer the newer mailgun.js SDK or another provider, as this package has seen less active maintenance recently. It is suitable for Mailgun users who need a quick integration but should be evaluated against modern alternatives.
Do NOT choose sendgrid for new projects as it is the deprecated legacy v2 SDK. It lacks support for newer SendGrid features and is no longer maintained. You should migrate any existing usage to @sendgrid/mail to ensure security and compatibility with current API standards.
Send emails from Node.js – easy as cake! 🍰✉️
See nodemailer.com for documentation and terms.
[!TIP] Check out EmailEngine – a self-hosted email gateway that allows making REST requests against IMAP and SMTP servers. EmailEngine also sends webhooks whenever something changes on the registered accounts.
Using the email accounts registered with EmailEngine, you can receive and send emails. EmailEngine supports OAuth2, delayed sends, opens and clicks tracking, bounce detection, etc. All on top of regular email accounts without an external MTA service.
Documentation for Nodemailer can be found at nodemailer.com.
You are using an older Node.js version than v6.0. Upgrade Node.js to get support for the spread operator. Nodemailer supports all Node.js versions starting from Node.js@v6.0.0.
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.
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 has official support for Node.js only. For anything related to TypeScript, you need to directly contact the authors of the type definitions.
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.