Nodemailer Complete Guide | Send Emails from Node.js

Nodemailer Complete Guide | Send Emails from Node.js

이 글의 핵심

Nodemailer is the most popular email sending library for Node.js. It supports SMTP, attachments, HTML emails, and works with all major email services.

Introduction

Nodemailer is a module for Node.js applications to send emails. It’s easy to use, supports all major email providers, and is production-ready.

Why Nodemailer?

// Before: Manual SMTP is complex
// Dealing with sockets, authentication, headers...

// With Nodemailer: Simple and clean
const transporter = nodemailer.createTransport({ /* config */ });
await transporter.sendMail({ from, to, subject, text });

1. Installation

npm install nodemailer

2. Basic Email

const nodemailer = require('nodemailer');

// Create transporter
const transporter = nodemailer.createTransport({
  service: 'gmail',
  auth: {
    user: '[email protected]',
    pass: 'your-app-password', // Use app password, not account password
  },
});

// Send email
const mailOptions = {
  from: '[email protected]',
  to: '[email protected]',
  subject: 'Hello from Nodemailer',
  text: 'This is a plain text email.',
};

transporter.sendMail(mailOptions, (error, info) => {
  if (error) {
    console.error(error);
  } else {
    console.log('Email sent:', info.response);
  }
});

// Or with async/await
async function sendEmail() {
  try {
    const info = await transporter.sendMail(mailOptions);
    console.log('Email sent:', info.messageId);
  } catch (error) {
    console.error('Error sending email:', error);
  }
}

3. Email Services

Gmail

const transporter = nodemailer.createTransport({
  service: 'gmail',
  auth: {
    user: '[email protected]',
    pass: 'your-app-password', // Generate at myaccount.google.com/apppasswords
  },
});

Outlook/Hotmail

const transporter = nodemailer.createTransport({
  service: 'hotmail',
  auth: {
    user: '[email protected]',
    pass: 'your-password',
  },
});

Custom SMTP

const transporter = nodemailer.createTransport({
  host: 'smtp.example.com',
  port: 587,
  secure: false, // true for 465, false for other ports
  auth: {
    user: 'username',
    pass: 'password',
  },
});

AWS SES

const transporter = nodemailer.createTransport({
  host: 'email-smtp.us-east-1.amazonaws.com',
  port: 587,
  secure: false,
  auth: {
    user: 'YOUR_SMTP_USERNAME',
    pass: 'YOUR_SMTP_PASSWORD',
  },
});

4. HTML Emails

const mailOptions = {
  from: '[email protected]',
  to: '[email protected]',
  subject: 'HTML Email Example',
  html: `
    <h1>Welcome to Our Service!</h1>
    <p>Thank you for signing up.</p>
    <p>Click the button below to verify your email:</p>
    <a href="https://example.com/verify?token=abc123"
       style="display: inline-block; padding: 10px 20px; 
              background: #007bff; color: white; 
              text-decoration: none; border-radius: 5px;">
      Verify Email
    </a>
  `,
};

await transporter.sendMail(mailOptions);

5. Attachments

const mailOptions = {
  from: '[email protected]',
  to: '[email protected]',
  subject: 'Email with Attachments',
  text: 'Please see attached files.',
  attachments: [
    // File path
    {
      filename: 'document.pdf',
      path: './files/document.pdf',
    },
    
    // Buffer
    {
      filename: 'data.txt',
      content: Buffer.from('This is file content'),
    },
    
    // Stream
    {
      filename: 'report.csv',
      content: fs.createReadStream('./files/report.csv'),
    },
    
    // URL
    {
      filename: 'logo.png',
      path: 'https://example.com/logo.png',
    },
  ],
};

await transporter.sendMail(mailOptions);

6. Embedded Images

const mailOptions = {
  from: '[email protected]',
  to: '[email protected]',
  subject: 'Email with Embedded Image',
  html: `
    <h1>Check out our logo!</h1>
    <img src="cid:logo" alt="Logo" width="200">
  `,
  attachments: [
    {
      filename: 'logo.png',
      path: './images/logo.png',
      cid: 'logo', // Same as in <img src="cid:logo">
    },
  ],
};

await transporter.sendMail(mailOptions);

7. Email Templates

Handlebars Template

npm install handlebars
const handlebars = require('handlebars');
const fs = require('fs').promises;

async function sendTemplatedEmail(to, data) {
  // Read template
  const templateSource = await fs.readFile('./templates/welcome.hbs', 'utf8');
  
  // Compile template
  const template = handlebars.compile(templateSource);
  
  // Generate HTML
  const html = template(data);
  
  // Send email
  await transporter.sendMail({
    from: '[email protected]',
    to,
    subject: 'Welcome!',
    html,
  });
}

// Usage
await sendTemplatedEmail('[email protected]', {
  name: 'Alice',
  verifyUrl: 'https://example.com/verify?token=abc123',
});

Template (welcome.hbs):

<!DOCTYPE html>
<html>
<head>
  <style>
    body { font-family: Arial, sans-serif; }
    .button {
      display: inline-block;
      padding: 10px 20px;
      background: #007bff;
      color: white;
      text-decoration: none;
      border-radius: 5px;
    }
  </style>
</head>
<body>
  <h1>Welcome, {{name}}!</h1>
  <p>Thank you for signing up.</p>
  <p>Please verify your email:</p>
  <a href="{{verifyUrl}}" class="button">Verify Email</a>
</body>
</html>

8. Multiple Recipients

// Multiple recipients (comma-separated)
const mailOptions = {
  from: '[email protected]',
  to: '[email protected], [email protected], [email protected]',
  subject: 'Multiple Recipients',
  text: 'This email goes to multiple people.',
};

// CC and BCC
const mailOptions = {
  from: '[email protected]',
  to: '[email protected]',
  cc: '[email protected]',
  bcc: '[email protected]',
  subject: 'CC and BCC Example',
  text: 'CC sees copy@, BCC doesn\'t see others.',
};

9. Sending Bulk Emails

async function sendBulkEmails(recipients) {
  for (const recipient of recipients) {
    try {
      await transporter.sendMail({
        from: '[email protected]',
        to: recipient.email,
        subject: 'Personalized Email',
        html: `<p>Hello ${recipient.name}!</p>`,
      });
      
      console.log(`Email sent to ${recipient.email}`);
      
      // Rate limiting (Gmail: 500 per day, 100 per hour)
      await new Promise(resolve => setTimeout(resolve, 1000)); // 1 second delay
    } catch (error) {
      console.error(`Failed to send to ${recipient.email}:`, error);
    }
  }
}

const recipients = [
  { email: '[email protected]', name: 'Alice' },
  { email: '[email protected]', name: 'Bob' },
  { email: '[email protected]', name: 'Charlie' },
];

await sendBulkEmails(recipients);

10. Express Integration

const express = require('express');
const nodemailer = require('nodemailer');

const app = express();
app.use(express.json());

const transporter = nodemailer.createTransport({ /* config */ });

// Contact form
app.post('/api/contact', async (req, res) => {
  try {
    const { name, email, message } = req.body;
    
    // Validate input
    if (!name || !email || !message) {
      return res.status(400).json({ error: 'All fields required' });
    }
    
    // Send email
    await transporter.sendMail({
      from: process.env.EMAIL_FROM,
      to: process.env.EMAIL_TO,
      replyTo: email,
      subject: `Contact Form: ${name}`,
      text: message,
      html: `
        <h3>New Contact Form Submission</h3>
        <p><strong>From:</strong> ${name} (${email})</p>
        <p><strong>Message:</strong></p>
        <p>${message}</p>
      `,
    });
    
    res.json({ message: 'Email sent successfully' });
  } catch (error) {
    console.error(error);
    res.status(500).json({ error: 'Failed to send email' });
  }
});

app.listen(3000);

11. Verification Emails

const crypto = require('crypto');

async function sendVerificationEmail(user) {
  // Generate verification token
  const token = crypto.randomBytes(32).toString('hex');
  
  // Save token to database
  await db.users.update(user.id, {
    verificationToken: token,
    verificationExpiry: Date.now() + 3600000, // 1 hour
  });
  
  // Send email
  const verifyUrl = `${process.env.APP_URL}/verify?token=${token}`;
  
  await transporter.sendMail({
    from: '[email protected]',
    to: user.email,
    subject: 'Verify Your Email',
    html: `
      <h1>Email Verification</h1>
      <p>Hi ${user.name},</p>
      <p>Please verify your email by clicking the link below:</p>
      <a href="${verifyUrl}">Verify Email</a>
      <p>This link expires in 1 hour.</p>
    `,
  });
}

12. Password Reset

async function sendPasswordResetEmail(user) {
  // Generate reset token
  const token = crypto.randomBytes(32).toString('hex');
  const hash = await bcrypt.hash(token, 10);
  
  // Save hashed token
  await db.users.update(user.id, {
    resetToken: hash,
    resetExpiry: Date.now() + 3600000,
  });
  
  // Send email
  const resetUrl = `${process.env.APP_URL}/reset-password?token=${token}`;
  
  await transporter.sendMail({
    from: '[email protected]',
    to: user.email,
    subject: 'Password Reset Request',
    html: `
      <h1>Password Reset</h1>
      <p>Hi ${user.name},</p>
      <p>You requested a password reset. Click the link below:</p>
      <a href="${resetUrl}">Reset Password</a>
      <p>This link expires in 1 hour.</p>
      <p>If you didn't request this, ignore this email.</p>
    `,
  });
}

13. Testing

// Use Ethereal for testing (fake SMTP)
const nodemailer = require('nodemailer');

async function createTestTransporter() {
  // Generate test account
  const testAccount = await nodemailer.createTestAccount();
  
  // Create transporter
  const transporter = nodemailer.createTransport({
    host: 'smtp.ethereal.email',
    port: 587,
    secure: false,
    auth: {
      user: testAccount.user,
      pass: testAccount.pass,
    },
  });
  
  return transporter;
}

// Usage
const transporter = await createTestTransporter();

const info = await transporter.sendMail({
  from: '[email protected]',
  to: '[email protected]',
  subject: 'Test Email',
  text: 'This is a test email.',
});

// Preview URL
console.log('Preview URL:', nodemailer.getTestMessageUrl(info));
// https://ethereal.email/message/xxx...

14. Best Practices

1. Use Environment Variables

require('dotenv').config();

const transporter = nodemailer.createTransport({
  service: process.env.EMAIL_SERVICE,
  auth: {
    user: process.env.EMAIL_USER,
    pass: process.env.EMAIL_PASSWORD,
  },
});

2. Handle Errors Gracefully

async function sendEmail(options) {
  try {
    const info = await transporter.sendMail(options);
    console.log('Email sent:', info.messageId);
    return { success: true, messageId: info.messageId };
  } catch (error) {
    console.error('Email error:', error);
    // Log to monitoring service
    // Retry logic here
    return { success: false, error: error.message };
  }
}

3. Use Queue for Bulk Emails

const Bull = require('bull');
const emailQueue = new Bull('email');

// Add to queue
emailQueue.add({ to: '[email protected]', subject: '...', html: '...' });

// Process queue
emailQueue.process(async (job) => {
  await transporter.sendMail(job.data);
});

4. Rate Limiting

const rateLimit = require('express-rate-limit');

const emailLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5, // 5 emails per window
  message: 'Too many emails sent, try again later',
});

app.post('/api/contact', emailLimiter, async (req, res) => {
  // Send email
});

Summary

Nodemailer provides reliable email sending:

  • Easy to use with clean API
  • All email services supported
  • HTML and attachments included
  • Templates for consistency
  • Production-ready and widely used

Key Takeaways:

  1. Use app passwords for Gmail
  2. HTML emails for better UX
  3. Templates for consistency
  4. Rate limiting for bulk sends
  5. Test with Ethereal

Next Steps:

  • Queue with Bull
  • Templates with Handlebars
  • Monitor with Winston

Resources: