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.
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.
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.
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.