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


