Facebook Pixel

Sending Emails with Nodemailer and Amazon SES in Node.js

Learn how to send emails from your Node.js application using Nodemailer with Amazon SES SMTP including HTML emails, templates, and error handling.

Sending Emails with Nodemailer and Amazon SES

Nodemailer is the most popular Node.js email library. Combined with Amazon SES SMTP, it provides a reliable way to send transactional emails.

Step 1: Install Nodemailer

npm install nodemailer

Step 2: Create the Email Transport

// src/utils/emailService.js const nodemailer = require("nodemailer"); const transporter = nodemailer.createTransport({ host: process.env.SES_HOST, port: process.env.SES_PORT, secure: false, // true for 465, false for 587 auth: { user: process.env.SES_USER, pass: process.env.SES_PASS } }); module.exports = transporter;

Step 3: Send a Plain Text Email

const transporter = require("./utils/emailService"); const sendWelcomeEmail = async (email, firstName) => { try { const info = await transporter.sendMail({ from: process.env.SES_FROM, to: email, subject: "Welcome to DevTinder!", text: `Hi ${firstName},\n\nWelcome to DevTinder! Your account has been created successfully.\n\nBest regards,\nThe DevTinder Team` }); console.log("Email sent:", info.messageId); return info; } catch (err) { console.error("Email error:", err); throw err; } };

Step 4: Send an HTML Email

const sendConnectionRequestEmail = async (toEmail, fromUser) => { const html = ` <!DOCTYPE html> <html> <head> <style> body { font-family: Arial, sans-serif; color: #333; } .container { max-width: 600px; margin: 0 auto; padding: 20px; } .header { background: #fd267d; color: white; padding: 20px; text-align: center; } .content { padding: 20px; } .button { background: #fd267d; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; } </style> </head> <body> <div class="container"> <div class="header"> <h1>New Connection Request</h1> </div> <div class="content"> <p><strong>${fromUser.firstName} ${fromUser.lastName}</strong> wants to connect with you!</p> <p>View their profile and accept or reject the request:</p> <a href="https://app.yourdomain.com/requests" class="button">View Request</a> </div> </div> </body> </html> `; await transporter.sendMail({ from: process.env.SES_FROM, to: toEmail, subject: "You have a new connection request!", html: html, text: `${fromUser.firstName} ${fromUser.lastName} wants to connect with you. Visit https://app.yourdomain.com/requests to view the request.` }); };

Always include a plain text fallback alongside HTML.

Step 5: Create Email Templates

// src/utils/emailTemplates.js const templates = { welcome: (firstName) => ({ subject: "Welcome to DevTinder!", html: `<h1>Welcome, ${firstName}!</h1><p>Your account is ready.</p>`, text: `Welcome, ${firstName}! Your account is ready.` }), passwordReset: (resetLink) => ({ subject: "Reset your DevTinder password", html: `<p>Click <a href="${resetLink}">here</a> to reset your password. Link expires in 1 hour.</p>`, text: `Reset your password: ${resetLink} (expires in 1 hour)` }), connectionRequest: (fromUser) => ({ subject: "New connection request on DevTinder", html: `<p><strong>${fromUser.firstName}</strong> wants to connect!</p>`, text: `${fromUser.firstName} wants to connect with you.` }), newMessage: (fromUser, message) => ({ subject: "New message on DevTinder", html: `<p><strong>${fromUser.firstName}</strong>: ${message}</p>`, text: `${fromUser.firstName}: ${message}` }) }; const sendEmail = async (to, templateName, data) => { const template = templates[templateName](data); await transporter.sendMail({ from: process.env.SES_FROM, to, ...template }); }; module.exports = { sendEmail, templates };

Usage:

const { sendEmail } = require("./utils/emailTemplates"); // Send welcome email await sendEmail(user.email, "welcome", user.firstName); // Send password reset await sendEmail(user.email, "passwordReset", resetLink);

Step 6: Error Handling

const sendEmailWithErrorHandling = async (to, subject, html, text) => { try { const info = await transporter.sendMail({ from: process.env.SES_FROM, to, subject, html, text }); return { success: true, messageId: info.messageId }; } catch (err) { console.error("Email sending failed:", err.message); // Common errors if (err.code === "EAUTH") { throw new Error("Email authentication failed. Check SES credentials."); } if (err.responseCode === 554) { throw new Error("Email rejected by SES. Check if the recipient is verified (sandbox mode)."); } return { success: false, error: err.message }; } };

The Takeaway

Sending emails with Nodemailer and SES involves: creating a transporter with SES SMTP credentials, sending emails with sendMail (supporting both text and HTML), creating reusable email templates for common emails (welcome, password reset, notifications), and handling errors gracefully. Always include a plain text fallback with HTML emails.

Create a transporter with nodemailer.createTransport using SES SMTP credentials (host, port, user, pass). Then call transporter.sendMail with from, to, subject, and text or html. Always handle errors with try-catch.

Send both. Use the html field for the HTML version and the text field for a plain text fallback. Some email clients and accessibility tools prefer plain text. Nodemailer supports both in a single sendMail call.

Create a templates object with functions for each email type (welcome, passwordReset, connectionRequest). Each function takes data and returns subject, html, and text. Use a sendEmail function that looks up the template and sends it.

EAUTH means authentication failed (check SMTP credentials). 554 means the email was rejected (check if recipient is verified in sandbox mode). Timeout means the SMTP connection failed. Always log errors and provide user-friendly messages.

Use inline CSS (not external stylesheets) since most email clients strip <style> tags. Use tables for layout (many clients don't support flexbox). Keep it simple: header with brand color, content section, and a call-to-action button. Test with mail-tester.com for spam score.

Ready to master Node.js completely?

Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course to dive deeper with high-quality video tutorials, solve interview questions, and a premium community.

Please Login.
Please Login.
Please Login.
Please Login.