Chapter 4.3☕ 20 min read

Email Notifications — Order Confirmation with Nodemailer

Email notifications are like Swiggy sending you "Your order is confirmed!" messages. Nodemailer is the Node.js library that makes sending emails easy.

01What is Nodemailer? — Your Biryani Confirmation ka Courier

Nodemailer is a Node.js module that allows you to send emails from your server. Think of it as your app's personal courier service — whenever someone orders biryani, nodemailer delivers the confirmation email.

Why do we need email notifications?

  • Confirm orders — "Your biryani is being cooked! 🍔"
  • Send receipts — Payment confirmation with order details
  • Notify restaurants — "New order received!"
  • Password reset — "Forgot your password? Click here"
  • Welcome emails — "Welcome to our biryani family!"

How email sending works:

  1. Your Node.js server connects to an SMTP server (like Gmail, SendGrid, Mailgun)
  2. You provide your email credentials (username + App Password)
  3. You compose the email (to, subject, body — plain text or HTML)
  4. Nodemailer sends the email through the SMTP server
  5. The SMTP server delivers the email to the recipient's inbox

Important: You should NOT send emails synchronously in API routes (waiting for email to send before responding). Always send emails asynchronously or use a queue. We'll cover this in Section 4.

Let's set up nodemailer and send our first "Your Biryani is Confirmed!" email! 🎉

02SMTP Setup — Gmail ko Node.js se Connect Karna

To send emails, you need an SMTP (Simple Mail Transfer Protocol) server. For development, we'll use Gmail's SMTP server. For production, use services like SendGrid, Mailgun, or AWS SES.

npm install nodemailer

Gmail SMTP Configuration:

// config/email.config.js — Nodemailer SMTP Setup
import nodemailer from 'nodemailer';

const transporter = nodemailer.createTransport({
  host: 'smtp.gmail.com',
  port: 587,           // 587 for TLS, 465 for SSL
  secure: false,        // true for 465, false for other ports
  auth: {
    user: process.env.EMAIL_USER,     // Your Gmail address
    pass: process.env.EMAIL_PASSWORD  // Gmail App Password (NOT your regular password!)
  },
  // Optional: Add connection timeout
  connectionTimeout: 10000, // 10 seconds
});

// Verify connection configuration
const verifyConnection = async () => {
  try {
    await transporter.verify();
    console.log('✅ SMTP connection successful! Server email ready hai!');
  } catch (error) {
    console.error('❌ SMTP connection failed:', error.message);
  }
};

verifyConnection();

export default transporter;

Gmail App Password — How to get it:

  1. Go to your Google Account → Security → 2-Step Verification (enable it)
  2. Go to Security → App Passwords
  3. Select "Mail" and "Other (Custom name)" → name it "DevInHyderabad"
  4. Copy the 16-character App Password
  5. Add to .env: EMAIL_PASSWORD=abcd efgh ijkl mnop

Never use your regular Gmail password! Always use an App Password. Regular passwords don't work for SMTP when 2FA is enabled (which it should be).

Alternative SMTP providers:

ProviderFree TierSMTP HostBest For
Gmail500 emails/daysmtp.gmail.comDevelopment, small apps
SendGrid100 emails/day freesmtp.sendgrid.netProduction, transactional
Mailgun5000 emails/month freesmtp.mailgun.orgProduction, marketing
AWS SES62000 emails/month freeemail-smtp.region.amazonaws.comEnterprise scale
03Sending Emails — "Your Biryani is Confirmed" Email

Now let's create the email service that sends "Your Biryani is Confirmed" emails when someone places an order.

// services/email.service.js — Biryani Order Confirmation Email
import transporter from '../config/email.config.js';

// Format order details into a nice email
const ORDER_CONFIRMED_SUBJECT = '🎉 Biryani Order Confirmed! 🍔';

const sendOrderConfirmation = async (order, user) => {
  try {
    const mailOptions = {
      from: '"Swiggy Biryani" <' + process.env.EMAIL_USER + '>',
      to: user.email,
      subject: ORDER_CONFIRMED_SUBJECT,
      text: generatePlainTextEmail(order, user),
      html: generateHtmlEmail(order, user)
    };

    const info = await transporter.sendMail(mailOptions);

    console.log('✅ Email sent to ' + user.email + ': ' + info.messageId);
    return { success: true, messageId: info.messageId };
  } catch (error) {
    console.error('❌ Email sending failed:', error.message);
    // Don't throw — email failure shouldn't break the order!
    return { success: false, error: error.message };
  }
};

// Plain text version (for email clients that don't support HTML)
const generatePlainTextEmail = (order, user) => {
  return 'Hey ' + user.name + '! 👋

' +
    'Your Biryani order has been confirmed! 🎉

' +
    '📋 Order Summary:
' +
    '━━━━━━━━━━━━━━━━━━
' +
    'Order ID: ' + order._id + '
' +
    'Items:
' +
    order.items.map(item => '  • ' + item.name + ' x ' + item.qty + ' — Rs.' + (item.price * item.qty)).join('
') + '
' +
    'Total: Rs.' + order.total + '
' +
    'Status: ' + order.status + '
' +
    'Estimated Delivery: ' + order.estimatedDelivery + '
' +
    '━━━━━━━━━━━━━━━━━━

' +
    'Your biryani is being prepared! Swiggy delivery partner will be at your door soon.

' +
    'Hungry? Track your order: ' + process.env.APP_URL + '/orders/' + order._id + '

' +
    '🍔 Thanks for ordering!
' +
    '— DevInHyderabad Swiggy Team';
};

// HTML version (beautiful, styled email)
const generateHtmlEmail = (order, user) => {
  return '' +
    '' +
    '
' + '

🎉 Order Confirmed! 🍔

Your biryani is on its way!

' + '
' + '

Hey ' + user.name + '! 👋

' + '

Your biryani order has been confirmed. Ab biryani prepare ho rahi hai! 🎉

' + '' + '' + order.items.map(item => '' ).join('') + '' + '
ItemQtyPrice
' + item.name + 'x' + item.qty + 'Rs.' + (item.price * item.qty) + '
TotalRs.' + order.total + '
' + '

Status: ' + order.status + '

' + '

Estimated Delivery: ' + order.estimatedDelivery + '

' + '

Track your order: Click here

' + '
' + '' + '
'; }; export { sendOrderConfirmation };
04Async Email Sending — API Ko Block Mat Karo

Never block your API response waiting for an email to send! Email delivery can take 1-5 seconds (or more). If you wait for the email before sending the HTTP 200 response, your API becomes slow and unreliable. If the email server is down, your order endpoint also fails.

BAD — Blocking the API response:

// ❌ BAD: Email bhejne tak response block karo
app.post('/api/orders', async (req, res) => {
  const order = await Order.create(req.body);

  // WAITING for email... (1-5 seconds!)
  await transporter.sendMail(mailOptions);

  // Response sent AFTER email is done
  res.json({ order }); // User waits 5 seconds! 😭
});

GOOD — Async (fire and forget):

// ✅ GOOD: Pehle response bhejo, fir email bhejo
app.post('/api/orders', async (req, res) => {
  const order = await Order.create(req.body);

  // IMMEDIATELY send response! (no await on email)
  res.status(201).json({ order });

  // Email bhejna — response ke BAAD, no blocking!
  sendOrderConfirmation(order, user)
    .then(result => console.log('Email result:', result))
    .catch(err => console.error('Email failed:', err.message));
  // Even if email fails, order is still confirmed!
});

BEST — Use a queue (production-ready):

// services/email.queue.js — Bull Queue for Async Email Processing
import Queue from 'bull';

// Redis-backed email queue
const emailQueue = new Queue('email-notifications', process.env.REDIS_URL);

// Process emails in background
emailQueue.process(async (job) => {
  const { order, user, type } = job.data;

  if (type === 'order-confirmation') {
    await sendOrderConfirmation(order, user);
  } else if (type === 'password-reset') {
    await sendPasswordResetEmail(user, job.data.token);
  }
});

// Usage in routes — just add to queue, response is instant!
app.post('/api/orders', async (req, res) => {
  const order = await Order.create(req.body);

  // Add email job to queue (instant — ~1ms)
  await emailQueue.add({
    type: 'order-confirmation',
    order,
    user
  });

  // Response sent immediately
  res.status(201).json({ order });
});

Why use a queue?

  • Speed — API response in milliseconds, not seconds
  • Reliability — If email fails, queue retries automatically
  • Scalability — Queue worker can run on a separate server
  • Back-pressure — Queue handles email spikes gracefully

For this chapter, we'll use the simple "fire and forget" async approach. In production, always use a queue system (Bull, RabbitMQ, AWS SQS).

05Email Templates — Beautiful HTML Emails

Plain text emails are boring. HTML emails look professional and build trust. Let's create reusable email templates.

// services/email.templates.js — Beautiful Email Templates
const styles = {
  container: 'max-width:600px; margin:0 auto; background:white; border-radius:12px; overflow:hidden; box-shadow:0 2px 8px rgba(0,0,0,0.1);',
  header: 'background:#16a34a; color:white; padding:30px; text-align:center;',
  content: 'padding:30px;',
  button: 'display:inline-block; background:#16a34a; color:white; padding:12px 24px; text-decoration:none; border-radius:8px; font-weight:bold;',
  footer: 'background:#f3f4f6; padding:20px; text-align:center; color:#6b7280; font-size:12px;'
};

// Welcome email template
export const welcomeEmail = (user) => ({
  subject: '🎉 Welcome to DevInHyderabad Swiggy!',
  html: '
' + '
' + '

Welcome to the Family! 🎉

' + '
' + '

Hey ' + user.name + '! 👋

' + '

Welcome to DevInHyderabad Swiggy — Hyderabadi ka sabse authentic biryani delivery service!

' + '

Your account has been created successfully. Ab aap biryani order kar sakte hain! 🍔

' + '

' + 'Order Biryani Now!

' + '

Happy eating! 🍛

' + '
DevInHyderabad — Hyderabadi Biryani, Delivered with ❤️
' }); // Password reset template export const passwordResetEmail = (user, resetToken) => ({ subject: '🔑 Password Reset - DevInHyderabad', html: '
' + '
' + '

Password Reset 🔑

' + '
' + '

Hey ' + user.name + '! 👋

' + '

Someone requested a password reset for your account. If this was you, click the button below:

' + '

' + 'Reset Password

' + '

This link expires in 1 hour. If you didn't request this, ignore this email.

' + '
' + '
DevInHyderabad — Your security matters to us! 🔒
' }); // Order status update template export const orderStatusEmail = (order, user) => ({ subject: '🍔 Order Update: ' + order.status.toUpperCase(), html: '
' + '
' + '

Order Update! 🍔

' + '
' + '

Hey ' + user.name + '!

' + '

Your order status has been updated:

' + '

' + order.status.toUpperCase() + '

' + '

Order ID: ' + order._id + '

' + '

Track your order: ' + process.env.APP_URL + '/orders/' + order._id + '

' + '
' + '
🍔 DevInHyderabad — Biryani delivered hot & fresh!
' });

Complete email service integration with order:

// controllers/order.controller.js — Order + Email Integration
import { sendOrderConfirmation } from '../services/email.service.js';

export const placeOrder = async (req, res) => {
  try {
    const { items, deliveryAddress } = req.body;

    // Calculate total
    const total = items.reduce((sum, item) => sum + (item.price * item.qty), 0);

    // Create order in DB
    const order = await Order.create({
      user: req.user.id,
      items,
      total,
      deliveryAddress,
      status: 'confirmed',
      estimatedDelivery: '30-40 minutes'
    });

    // ✅ Send response FIRST (user doesn't wait for email)
    res.status(201).json({
      success: true,
      message: 'Order confirmed! Biryani prepare ho rahi hai! 🍔',
      data: order
    });

    // 🔔 Send email ASYNCHRONOUSLY (fire and forget)
    const user = await User.findById(req.user.id);
    sendOrderConfirmation(order, user)
      .then(result => {
        if (result.success) {
          console.log('✅ Confirmation email sent to ' + user.email);
        }
      })
      .catch(err => {
        console.error('❌ Email failed (order still placed):', err.message);
        // Log to monitoring service in production
      });

  } catch (error) {
    res.status(500).json({ success: false, message: error.message });
  }
};

Key Takeaways

  • ✅ Nodemailer is the standard Node.js library for sending emails
  • ✅ Use Gmail SMTP for development with App Passwords (not your regular password)
  • ✅ Configure SMTP: host, port, auth (user + app password) in email.config.js
  • ✅ Send HTML emails with inline styles — CSS classes don't work in email clients
  • ✅ NEVER block API response waiting for email — send email asynchronously
  • ✅ "Fire and forget": respond first (200 OK), then send email in background
  • ✅ In production, use a queue (Bull, RabbitMQ) for reliable email delivery
  • ✅ Always handle email failures gracefully — don't let email failure break order placement
Course Search
Search across all chapters & stages
📖

Search the course

Type any topic — branching, stash, rebase, hooks — and jump straight to that chapter.

merge branchesgit stashundo commitrebase