How Email Sending Works in a Node.js Application

Your MERN Blog needs to send emails for several reasons: a welcome email after registration, an email verification link, a password reset link, and optionally a notification when someone comments on a post. In Node.js, the standard library for sending email is Nodemailer โ€” it creates a connection to an email server using SMTP (or other transports) and delivers the message on your behalf. Understanding how email sending works in a MERN application โ€” the flow from your Express code to the recipient’s inbox โ€” is the foundation for building every email feature in this chapter.

How Email Sending Works

Your Express Application
        โ”‚
        โ–ผ
   Nodemailer (Node.js library)
        โ”‚  creates SMTP connection
        โ–ผ
Email Service / SMTP Server
  (Gmail, SendGrid, Resend, Mailgun...)
        โ”‚  delivers the message
        โ–ผ
Recipient's Email Server (e.g. Outlook, Yahoo)
        โ”‚
        โ–ผ
Recipient's Inbox

Key concepts:
  SMTP         โ€” Simple Mail Transfer Protocol, the standard protocol
                  for sending email between servers
  Transporter  โ€” Nodemailer's connection to an SMTP server,
                  reused across multiple sends
  Transport    โ€” The method of delivery (SMTP, SES, sendmail...)
  Transactional email โ€” triggered by user actions (register, reset password)
                        vs bulk/marketing email (newsletters, announcements)
Note: Nodemailer sends emails โ€” it does not receive them. Incoming email (support replies, inbound contact forms) requires a different service entirely (Mailgun Inbound Routing, SendGrid Inbound Parse, etc.). For the MERN Blog, you only need outbound sending: welcome emails, verification links, and password reset links โ€” all triggered by user actions on your Express API.
Tip: For development and testing, use Ethereal Email โ€” a fake SMTP service that captures emails without delivering them. Nodemailer has built-in support for creating an Ethereal test account. Every email your development app sends is captured at the Ethereal website (https://ethereal.email) where you can view it โ€” no real emails sent, no risk of spamming real users during development.
Warning: Never use your personal email account’s regular password in Nodemailer configuration. Email providers like Gmail require an App Password โ€” a 16-character password generated specifically for app access โ€” or OAuth2 authentication. Putting your real password in a .env file is dangerous. If the file is accidentally committed, your account is compromised. Always use App Passwords or dedicated API keys from email services.

Types of Emails in the MERN Blog

Email Type Trigger Content Time Sensitivity
Welcome POST /api/auth/register Greeting + verification link Low โ€” decorative
Email verification POST /api/auth/register Verification link with token Medium โ€” token expires in 24h
Password reset POST /api/auth/forgot-password Reset link with token High โ€” token expires in 1h
Password changed POST /api/auth/reset-password Security alert Low โ€” informational
New comment POST /api/posts/:id/comments Comment notification Low โ€” can be delayed

What You Need to Send an Email

// Every email requires these five things:
{
  from:    '"MERN Blog" <noreply@mernblog.com>',  // sender display name + address
  to:      'user@example.com',                      // recipient address
  subject: 'Verify your MERN Blog account',         // subject line
  text:    'Click here to verify: https://...',     // plain text fallback
  html:    '<p>Click here to verify: ...</p>',    // HTML version (preferred)
}

// Emails should always include both text and html โ€” email clients
// that cannot render HTML fall back to the plain text version.

Security Considerations

Practice Why
Use environment variables for all credentials Keep secrets out of source control
Use Gmail App Passwords, not your real password Limits access scope; revocable
Rate-limit password reset requests Prevents abuse / email flooding
Hash reset tokens before storing in MongoDB If DB is breached, tokens are useless
Set short expiry on reset tokens (1 hour) Limits window of vulnerability
Send emails asynchronously after the HTTP response Slow email delivery does not block the API
Never reveal whether an email address is registered Prevents email enumeration attacks

Common Mistakes

Mistake 1 โ€” Blocking the HTTP response while waiting for email to send

โŒ Wrong โ€” user waits for email delivery before getting a response:

const register = asyncHandler(async (req, res) => {
  const user = await User.create({ ... });
  await sendEmail({ to: user.email, ... }); // blocks โ€” slow email servers delay response!
  res.status(201).json({ success: true });
});

โœ… Correct โ€” send email after responding, fire-and-forget for non-critical emails:

const register = asyncHandler(async (req, res) => {
  const user = await User.create({ ... });
  res.status(201).json({ success: true }); // respond immediately
  // Send email after โ€” failure does not affect the registration response
  sendEmail({ to: user.email, ... }).catch(err => console.error('Email failed:', err));
});

Mistake 2 โ€” Revealing whether an email is registered during password reset

โŒ Wrong โ€” different response for known vs unknown email:

const user = await User.findOne({ email });
if (!user) throw new AppError('No account with that email', 404); // reveals registration status!

โœ… Correct โ€” always return the same response regardless:

const user = await User.findOne({ email });
// If user doesn't exist โ€” still return success (same message)
res.json({ success: true, message: 'If that email is registered, a reset link has been sent.' });
if (!user) return; // exit silently without sending email
// If user exists โ€” proceed to send the email

Mistake 3 โ€” Storing raw reset tokens in MongoDB

โŒ Wrong โ€” plaintext token in the database:

const token = crypto.randomBytes(32).toString('hex');
user.passwordResetToken = token; // if DB is breached, attacker can reset any password

โœ… Correct โ€” store the SHA-256 hash, send the raw token in the email:

const token = crypto.randomBytes(32).toString('hex');
user.passwordResetToken = crypto.createHash('sha256').update(token).digest('hex'); // hashed
await user.save();
sendEmail({ resetUrl: `${CLIENT_URL}/reset-password/${token}` }); // raw token in email โœ“

Quick Reference

Concept Key Point
Nodemailer role Bridge between your Express code and an SMTP server
Transporter Reusable SMTP connection object created once
Development testing Use Ethereal Email โ€” catches emails without delivering
Gmail auth App Password or OAuth2 โ€” never your real password
Token security Store SHA-256 hash in DB, send raw token in email
Email timing Respond first, send email after (fire-and-forget)

🧠 Test Yourself

Your password reset endpoint stores the raw token in MongoDB and sends the same raw token in the email. A database breach occurs. What can an attacker do?