nodemailer vs resend vs @sendgrid/mail vs mailgun-js vs sendgrid
Sending Transactional Emails in Node.js
nodemailerresend@sendgrid/mailmailgun-jssendgridSimilar Packages:

Sending Transactional Emails in Node.js

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.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
nodemailer19,202,52917,644579 kB110 hours agoMIT-0
resend9,301,142939265 kB147 days agoMIT
@sendgrid/mail4,473,6233,05417.4 kB102a year agoMIT
mailgun-js160,760893-528 years agoMIT
sendgrid59,9873,054-1029 years agoMIT

Sending Transactional Emails in Node.js: API SDKs vs SMTP

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.

🔑 Authentication & Setup

@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);

📤 Sending a Basic Email

@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);

📎 Handling Attachments

@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);

⚠️ Maintenance & Architecture

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.

🤝 Similarities: Shared Ground

While the implementation differs, all five libraries share core concepts for defining email messages.

1. 📨 Standard Message Fields

  • All use from, to, subject, and text/html fields.
  • The data structure is nearly identical across packages.
// Common structure across all 5
const msg = {
  from: 'me@example.com',
  to: 'user@example.com',
  subject: 'Test',
  text: 'Hello'
};

2. ⚡ Async/Await Support

  • All support modern JavaScript Promises.
  • You can use await to handle sending logic cleanly.
// All support this pattern
try {
  await client.send(msg);
} catch (error) {
  console.error(error);
}

3. 🔐 Environment Variables

  • All require secrets (API Keys or Passwords).
  • Best practice is to load these from .env files.
// All rely on this
const key = process.env.API_KEY;

📊 Summary: Key Similarities

FeatureShared 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

🆚 Summary: Key Differences

FeatureAPI 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

💡 The Big Picture

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.

How to Choose: nodemailer vs resend vs @sendgrid/mail vs mailgun-js vs sendgrid

  • nodemailer:

    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.

  • resend:

    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.

  • @sendgrid/mail:

    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.

  • mailgun-js:

    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.

  • sendgrid:

    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.

README for nodemailer

Nodemailer

Nodemailer

Send emails from Node.js – easy as cake! 🍰✉️

NPM

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.

Having an issue?

First review the docs

Documentation for Nodemailer can be found at nodemailer.com.

Nodemailer throws a SyntaxError for "..."

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.

I'm having issues with Gmail

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.

I get ETIMEDOUT errors

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.

Nodemailer works on one machine but not in another

It's either a firewall issue, or your SMTP server blocks authentication attempts from some servers.

I get TLS errors

  • If you are running the code on your machine, check your antivirus settings. Antiviruses often mess around with email ports usage. Node.js might not recognize the MITM cert your antivirus is using.
  • Latest Node versions allow only TLS versions 1.2 and higher. Some servers might still use TLS 1.1 or lower. Check Node.js docs on how to get correct TLS support for your app. You can change this with tls.minVersion option
  • You might have the wrong value for the 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.
  • Older Node versions do not fully support the certificate chain of the newest Let's Encrypt certificates. Either set tls.rejectUnauthorized to false to skip chain verification or upgrade your Node version
let configOptions = {
    host: 'smtp.example.com',
    port: 587,
    tls: {
        rejectUnauthorized: true,
        minVersion: 'TLSv1.2'
    }
};

I have issues with DNS / hosts file

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'
    }
};

I have an issue with TypeScript types

Nodemailer has official support for Node.js only. For anything related to TypeScript, you need to directly contact the authors of the type definitions.

I have a different problem

If you are having issues with Nodemailer, then the best way to find help would be Stack Overflow or revisit the docs.

License

Nodemailer is licensed under the MIT No Attribution license


The Nodemailer logo was designed by Sven Kristjansen.