Skip to main content
Technology & EngineeringEmail Services224 lines

Nodemailer

Send email with Nodemailer in Node.js. Use this skill when the project needs to send email via SMTP, integrate with any SMTP relay (Gmail, Outlook, SES, custom), send attachments, embed images, use HTML templates, or build custom email infrastructure without a managed email API. Covers SMTP transports, pooled connections, attachments, HTML templating, OAuth2, and common relay configurations.

Quick Summary27 lines
You are an email operations specialist who integrates Nodemailer into Node.js
projects. Nodemailer is the de facto standard for sending email from Node.js
via SMTP. It works with any SMTP server — managed services (SES, SendGrid SMTP,
Gmail) or self-hosted (Postfix, Dovecot).

## Key Points

- Use pooled connections for sending more than a few emails per minute
- Always include both HTML and plain-text content
- Verify the SMTP connection at startup with `transporter.verify()`
- Use OAuth2 instead of app passwords for Gmail and Outlook
- Close the transporter when your application shuts down
- Use connection timeouts to prevent hanging sends
- Build a send queue with retry logic for production use
- Creating a new transporter for every email — reuse transporters
- Using Gmail SMTP for production transactional email — rate limits are low
- Not handling rejected recipients from `info.rejected`
- Sending high-volume email without connection pooling
- Storing SMTP passwords in code instead of environment variables

## Quick Example

```bash
npm install nodemailer
```
skilldb get email-services-skills/nodemailerFull skill: 224 lines
Paste into your CLAUDE.md or agent config

Nodemailer Email Integration

You are an email operations specialist who integrates Nodemailer into Node.js projects. Nodemailer is the de facto standard for sending email from Node.js via SMTP. It works with any SMTP server — managed services (SES, SendGrid SMTP, Gmail) or self-hosted (Postfix, Dovecot).

Core Philosophy

SMTP is universal

Every email service supports SMTP. Nodemailer lets you switch providers by changing transport configuration. No vendor-specific SDK lock-in.

Bring your own everything

Nodemailer sends email. It doesn't manage contacts, track opens, handle bounces, or provide analytics. You build those layers. This is the right choice when you need full control or when budget constraints rule out managed APIs.

Connection pooling for throughput

For high-volume sending, use pooled SMTP connections. A pool of persistent connections avoids the overhead of handshake per message and dramatically improves throughput.

Setup

Install

npm install nodemailer

Basic SMTP transport

import nodemailer from 'nodemailer';

const transporter = nodemailer.createTransport({
  host: process.env.SMTP_HOST,
  port: Number(process.env.SMTP_PORT) || 587,
  secure: false, // true for 465, false for 587 (STARTTLS)
  auth: {
    user: process.env.SMTP_USER,
    pass: process.env.SMTP_PASS,
  },
});

Key Techniques

Simple send

const info = await transporter.sendMail({
  from: '"App" <noreply@yourdomain.com>',
  to: 'user@example.com',
  subject: 'Your password reset link',
  text: 'Reset: https://...',
  html: '<p>Click <a href="...">here</a> to reset.</p>',
});

console.log('Message sent:', info.messageId);

Attachments

await transporter.sendMail({
  from: '"App" <noreply@yourdomain.com>',
  to: 'user@example.com',
  subject: 'Your invoice',
  html: '<p>Please find your invoice attached.</p>',
  attachments: [
    { filename: 'invoice.pdf', path: '/tmp/invoice-123.pdf' },
    { filename: 'logo.png', path: '/assets/logo.png', cid: 'logo@company' },
  ],
});

Embedded images (CID)

await transporter.sendMail({
  from: '"App" <noreply@yourdomain.com>',
  to: 'user@example.com',
  subject: 'Welcome',
  html: '<img src="cid:logo@company" /><p>Welcome!</p>',
  attachments: [
    { filename: 'logo.png', path: '/assets/logo.png', cid: 'logo@company' },
  ],
});

Pooled connections (high volume)

const transporter = nodemailer.createTransport({
  pool: true,
  host: process.env.SMTP_HOST,
  port: 587,
  auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS },
  maxConnections: 5,
  maxMessages: 100, // Messages per connection before reconnect
});

OAuth2 (Gmail, Outlook)

const transporter = nodemailer.createTransport({
  service: 'gmail',
  auth: {
    type: 'OAuth2',
    user: 'you@gmail.com',
    clientId: process.env.GOOGLE_CLIENT_ID,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    refreshToken: process.env.GOOGLE_REFRESH_TOKEN,
  },
});

Common SMTP configurations

AWS SES:

const transporter = nodemailer.createTransport({
  host: `email-smtp.${process.env.AWS_REGION}.amazonaws.com`,
  port: 587,
  auth: {
    user: process.env.SES_SMTP_USER,
    pass: process.env.SES_SMTP_PASS,
  },
});

SendGrid SMTP:

const transporter = nodemailer.createTransport({
  host: 'smtp.sendgrid.net',
  port: 587,
  auth: {
    user: 'apikey',
    pass: process.env.SENDGRID_API_KEY,
  },
});

Postmark SMTP:

const transporter = nodemailer.createTransport({
  host: 'smtp.postmarkapp.com',
  port: 587,
  auth: {
    user: process.env.POSTMARK_SERVER_TOKEN,
    pass: process.env.POSTMARK_SERVER_TOKEN,
  },
});

HTML templating with Handlebars

import Handlebars from 'handlebars';
import fs from 'fs';

const source = fs.readFileSync('./templates/welcome.hbs', 'utf-8');
const template = Handlebars.compile(source);

const html = template({ name: 'Alice', url: 'https://...' });

await transporter.sendMail({
  from: '"App" <noreply@yourdomain.com>',
  to: 'user@example.com',
  subject: 'Welcome',
  html,
});

Verify connection

try {
  await transporter.verify();
  console.log('SMTP connection verified');
} catch (err) {
  console.error('SMTP connection failed:', err);
}

Error Handling

try {
  const info = await transporter.sendMail(message);
  // info.accepted — addresses that accepted the message
  // info.rejected — addresses that rejected the message
  // info.messageId — the Message-ID header
} catch (err) {
  if (err.responseCode === 421) {
    // Service temporarily unavailable — retry with backoff
  } else if (err.responseCode >= 500) {
    // Permanent failure — do not retry
  }
}

Best Practices

  • Use pooled connections for sending more than a few emails per minute
  • Always include both HTML and plain-text content
  • Verify the SMTP connection at startup with transporter.verify()
  • Use OAuth2 instead of app passwords for Gmail and Outlook
  • Close the transporter when your application shuts down
  • Use connection timeouts to prevent hanging sends
  • Build a send queue with retry logic for production use

Anti-Patterns

  • Creating a new transporter for every email — reuse transporters
  • Using Gmail SMTP for production transactional email — rate limits are low
  • Not handling rejected recipients from info.rejected
  • Sending high-volume email without connection pooling
  • Storing SMTP passwords in code instead of environment variables
  • Not implementing retry logic — SMTP connections are inherently unreliable
  • Using Nodemailer when a managed API (Resend, SendGrid, etc.) would be simpler

Install this skill directly: skilldb add email-services-skills

Get CLI access →

Related Skills

Plunk

Send transactional and marketing email with Plunk. Use this skill when the project needs to integrate Plunk for open-source email, transactional sends, automated sequences, contact management, or self-hosted email infrastructure. Covers the Plunk API, events-based email triggers, automations, contacts, and self-hosting.

Email Services160L

Postmark

Send transactional email with Postmark. Use this skill when the project needs to integrate Postmark for high-deliverability transactional email, server-side templates, inbound email processing, message streams, bounce handling, or DMARC reporting. Covers the Postmark API, template system, message streams, webhooks, and deliverability features.

Email Services211L

React Email

Build email templates with React Email. Use this skill when the project needs to create type-safe, component-based email templates using React, preview emails during development, render to HTML for any email provider, or build a shared email design system. Covers React Email components, preview server, rendering, and integration with Resend, SendGrid, Postmark, AWS SES, and Nodemailer.

Email Services233L

Resend

Send transactional and marketing email with Resend. Use this skill when the project needs to integrate Resend for sending email, managing contacts, handling webhooks, building templates, or setting up mailing lists and broadcasts. Covers single sends, batch sends, scheduling, React Email templates, domain verification, webhook processing, idempotency, deliverability, and compliance.

Email Services244L

Sendgrid

Send transactional and marketing email with SendGrid (Twilio). Use this skill when the project needs to integrate SendGrid for sending email via API or SMTP, dynamic templates, contact management, marketing campaigns, email validation, webhook event processing, or deliverability optimization. Covers the v3 Mail Send API, dynamic transactional templates, Marketing Campaigns, suppressions, and Event Webhook.

Email Services220L

Sparkpost

Send transactional and marketing email with SparkPost (now MessageBird). Use this skill when the project needs to integrate SparkPost for high-volume email delivery via REST API or SMTP, recipient lists, stored templates, A/B testing, engagement tracking, suppression management, webhooks, and deliverability analytics.

Email Services276L