Back to Blog
Sending Emails in Node.js with Nodemailer and SMTP
backendnodejsemailsmtpnodemailer

Sending Emails in Node.js with Nodemailer and SMTP

A practical guide to setting up transactional email in Node.js using Nodemailer, Gmail SMTP, and environment-based configuration — no third-party email service required.

Why SMTP Over an Email Service?

Most tutorials jump straight to SendGrid or Resend. Those are fine services, but understanding raw SMTP gives you a foundation that transfers to any provider. I wanted to know exactly what was happening when an email got sent.

This guide walks through setting up Nodemailer with Gmail SMTP in a Node.js backend.

Setup

npm install nodemailer
npm install dotenv

Your .env file:

SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your.email@gmail.com
SMTP_PASS=your_app_password

Important: Use a Gmail App Password, not your account password. Generate one at myaccount.google.com → Security → App Passwords.

Creating the Transporter

import nodemailer from 'nodemailer';

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

Sending a Basic Email

async function sendEmail({ to, subject, html }) {
  const info = await transporter.sendMail({
    from: `"Debanjan" <${process.env.SMTP_USER}>`,
    to,
    subject,
    html,
  });

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

HTML Templates with Inline Styles

For transactional emails, keep HTML simple and use inline styles — most email clients strip external CSS.

const html = `
  <div style="font-family: sans-serif; max-width: 600px; margin: 0 auto;">
    <h2 style="color: #1a1a2e;">Welcome aboard 👋</h2>
    <p style="color: #555;">Thanks for signing up. Here's your verification link:</p>
    <a href="${verifyUrl}" style="background: #38bdf8; color: white; padding: 10px 20px; border-radius: 4px; text-decoration: none;">
      Verify Email
    </a>
  </div>
`;

Common Gotchas

  • Port 465 requires secure: true. Port 587 uses STARTTLS (secure: false).
  • Gmail limits sends to ~500/day. Use a dedicated provider (Resend, Brevo) for production volume.
  • Always verify the transporter connection on startup with transporter.verify().
  • Rate-limit your email endpoints to prevent abuse.

Read the Full Article

This article is hosted on Hashnode. Read it here →

Related Posts

Building PixForge: A Self-Hosted Image Upload & Transformation API

Building PixForge: A Self-Hosted Image Upload & Transformation API

How I built a full-stack image upload service with MinIO, Amazon S3, Docker, and Express.js — and what I learned about object storage along the way.

backendnodejsdocker+2 more
Read More
Running MongoDB in Docker: A Practical Guide for Backend Developers

Running MongoDB in Docker: A Practical Guide for Backend Developers

How to run MongoDB locally using Docker and Docker Compose, connect it to a Node.js app, and manage data with persistent volumes — no local MongoDB install needed.

backenddockermongodb+2 more
Read More

Designed & Built by Debanjan Das
© 2026. All rights reserved.