Email Service

The Email Service component provides a production-ready solution for sending emails in ServerCN using nodemailer. It supports multiple email providers including Gmail, SMTP servers, SendGrid, and more.

Features

  • Multiple email providers - Gmail, SMTP, SendGrid, AWS SES, and more
  • HTML and plain text emails - Support for both HTML and text content
  • Attachments - Send files as email attachments
  • Multiple recipients - Support for CC, BCC, and multiple recipients
  • Type-safe - Full TypeScript support
  • Error handling - Comprehensive error handling and validation
  • Email verification - Verify email configuration before sending

Installation Guide

For a fully structured setup with consistent error handling and standardized API responses:

  • HTTP Status Codes:
npx servercn add http-status-codes

Documentation: HTTP Status Codes

  • Api Response Handler:
npx servercn add response-formatter

Documentation: Api Response Handler:

  • Api Error Handler:
npx servercn add error-handler

Documentation: Api Error Handler:

  • Async Handler:
npx servercn add async-handler

Documentation: Async Handler

npx servercn add email-service

Prerequisites

  1. Enable 2-Step Verification on your Google Account
  2. Generate an App Password:
    • Go to Google Account → Security → 2-Step Verification → App passwords
    • Create a new app password for "Mail"
    • Copy the generated password

Add the following to your .env file:

EMAIL_HOST="smtp.gmail.com"
EMAIL_PORT="587"
EMAIL_SECURE="false"
EMAIL_USER="your-email@gmail.com"
EMAIL_PASSWORD="your-app-password"
EMAIL_FROM="your-email@gmail.com"

SMTP Server

EMAIL_HOST="smtp.example.com"
EMAIL_PORT="587"
EMAIL_SECURE="false"
EMAIL_USER="your-username"
EMAIL_PASSWORD="your-password"
EMAIL_FROM="noreply@example.com"

SendGrid

EMAIL_HOST="smtp.sendgrid.net"
EMAIL_PORT="587"
EMAIL_SECURE="false"
EMAIL_USER="apikey"
EMAIL_PASSWORD="your-sendgrid-api-key"
EMAIL_FROM="noreply@yourdomain.com"

AWS SES

EMAIL_HOST="email-smtp.us-east-1.amazonaws.com"
EMAIL_PORT="587"
EMAIL_SECURE="false"
EMAIL_USER="your-aws-access-key-id"
EMAIL_PASSWORD="your-aws-secret-access-key"
EMAIL_FROM="noreply@yourdomain.com"

Basic Implementation

Create email service in src/services/email.service.ts:

import nodemailer from "nodemailer";
import env from "../configs/env";
 
const transporter = nodemailer.createTransport({
  host: env.EMAIL_HOST,
  port: env.EMAIL_PORT,
  secure: env.EMAIL_SECURE,
  auth: {
    user: env.EMAIL_USER,
    pass: env.EMAIL_PASSWORD
  }
});
 
export async function sendEmail(options: {
  to: string | string[];
  subject: string;
  text?: string;
  html?: string;
}) {
  const info = await transporter.sendMail({
    from: env.EMAIL_FROM,
    to: Array.isArray(options.to) ? options.to.join(", ") : options.to,
    subject: options.subject,
    text: options.text,
    html: options.html
  });
 
  return info;
}

For feature-based architecture, place service in src/modules/email/email.service.ts:

import nodemailer from "nodemailer";
 
const transporter = nodemailer.createTransport({
  host: process.env.EMAIL_HOST!,
  port: Number(process.env.EMAIL_PORT) || 587,
  secure: process.env.EMAIL_SECURE === "true",
  auth: {
    user: process.env.EMAIL_USER!,
    pass: process.env.EMAIL_PASSWORD!
  }
});
 
// Same functions as MVC structure

Usage Examples

import { sendTextEmail } from "../services/email.service";
 
await sendTextEmail(
  "user@example.com",
  "Welcome to ServerCN",
  "Thank you for joining us!"
);
import { sendHtmlEmail } from "../services/email.service";
 
const htmlContent = `
  <h1>Welcome to ServerCN</h1>
  <p>Thank you for joining us!</p>
  <a href="https://servercn.com">Visit our website</a>
`;
 
await sendHtmlEmail("user@example.com", "Welcome to ServerCN", htmlContent);
import { sendEmail } from "../services/email.service";
 
await sendEmail({
  to: ["user1@example.com", "user2@example.com"],
  subject: "Team Update",
  html: "<p>This is a team update.</p>",
  cc: ["manager@example.com"],
  bcc: ["archive@example.com"]
});
import { sendEmail } from "../services/email.service";
import fs from "fs";
 
await sendEmail({
  to: "user@example.com",
  subject: "Invoice",
  html: "<p>Please find your invoice attached.</p>",
  attachments: [
    {
      filename: "invoice.pdf",
      path: "./invoices/invoice-123.pdf"
    },
    {
      filename: "logo.png",
      content: fs.readFileSync("./assets/logo.png"),
      contentType: "image/png"
    }
  ]
});
import { Request, Response } from "express";
import { asyncHandler } from "../middlewares/async-handler";
import { sendHtmlEmail } from "../services/email.service";
import { ApiResponse } from "../utils/api-response";
import { ApiError } from "../utils/error-handler";
import { STATUS_CODES } from "../constants/status-codes";
 
export const sendWelcomeEmail = asyncHandler(
  async (req: Request, res: Response) => {
    const { email, name } = req.body;
 
    if (!email) {
      throw new ApiError(STATUS_CODES.BAD_REQUEST, "Email address is required");
    }
 
    const htmlContent = `
      <h1>Welcome ${name || "User"}!</h1>
      <p>Thank you for joining ServerCN.</p>
    `;
 
    await sendHtmlEmail(email, "Welcome to ServerCN", htmlContent);
 
    return res
      .status(STATUS_CODES.OK)
      .json(ApiResponse.success(null, "Welcome email sent successfully"));
  }
);
import { verifyEmailConfig } from "../services/email.service";
 
const isValid = await verifyEmailConfig();
if (!isValid) {
  console.error("Email configuration is invalid");
}
import { sendHtmlEmail } from "../services/email.service";
 
// Password reset email
export async function sendPasswordResetEmail(
  email: string,
  resetToken: string
) {
  const resetUrl = `https://yourapp.com/reset-password?token=${resetToken}`;
  const html = `
    <h2>Password Reset Request</h2>
    <p>Click the link below to reset your password:</p>
    <a href="${resetUrl}">Reset Password</a>
    <p>This link will expire in 1 hour.</p>
  `;
 
  await sendHtmlEmail(email, "Reset Your Password", html);
}
 
// Email verification
export async function sendVerificationEmail(
  email: string,
  verificationToken: string
) {
  const verifyUrl = `https://yourapp.com/verify-email?token=${verificationToken}`;
  const html = `
    <h2>Verify Your Email</h2>
    <p>Click the link below to verify your email address:</p>
    <a href="${verifyUrl}">Verify Email</a>
  `;
 
  await sendHtmlEmail(email, "Verify Your Email", html);
}

Email Templates

You can use template engines like Handlebars, EJS, or Pug:

import { sendHtmlEmail } from "../services/email.service";
import handlebars from "handlebars";
import fs from "fs";
 
const templateSource = fs.readFileSync("./templates/welcome.hbs", "utf8");
const template = handlebars.compile(templateSource);
 
const html = template({
  name: "John Doe",
  activationLink: "https://yourapp.com/activate"
});
 
await sendHtmlEmail("user@example.com", "Welcome!", html);

Error Handling

The service includes comprehensive error handling:

import { sendEmail } from "../services/email.service";
import { ApiError } from "../utils/error-handler";
 
try {
  await sendEmail({
    to: "user@example.com",
    subject: "Test",
    html: "<p>Test email</p>"
  });
} catch (error) {
  if (error instanceof ApiError) {
    // Handle API errors
    console.error("Email error:", error.message);
  } else {
    // Handle other errors
    console.error("Unexpected error:", error);
  }
}

Best Practices

  1. Use environment variables - Never hardcode email credentials
  2. Validate email addresses - Validate recipient emails before sending
  3. Handle errors gracefully - Implement proper error handling and logging
  4. Use HTML templates - Use template engines for consistent email design
  5. Rate limiting - Implement rate limiting for email endpoints
  6. Email queuing - Use job queues (Bull, BullMQ) for bulk emails
  7. Test emails - Use services like Mailtrap for testing in development

Common Email Providers Configuration

EMAIL_HOST="smtp.gmail.com"
EMAIL_PORT="587"
EMAIL_SECURE="false"
EMAIL_HOST="smtp.office365.com"
EMAIL_PORT="587"
EMAIL_SECURE="false"
EMAIL_HOST="smtp.mail.yahoo.com"
EMAIL_PORT="587"
EMAIL_SECURE="false"
EMAIL_HOST="smtp.yourdomain.com"
EMAIL_PORT="587"
EMAIL_SECURE="false"

Security Considerations

  1. App Passwords - Use app-specific passwords for Gmail
  2. Environment Variables - Store credentials securely
  3. Rate Limiting - Prevent email spam and abuse
  4. Input Validation - Validate email addresses and content
  5. HTTPS - Use secure connections (EMAIL_SECURE=true for port 465)

Troubleshooting

  • Verify your email credentials
  • For Gmail, ensure you're using an App Password, not your regular password
  • Check that 2-Step Verification is enabled
  • Verify EMAIL_HOST and EMAIL_PORT are correct
  • Check firewall settings
  • Ensure EMAIL_SECURE matches your port (true for 465, false for 587)
  • Check spam/junk folder
  • Verify EMAIL_FROM address
  • Check email provider's sending limits
  • Verify recipient email address is valid

File & Folder Structure

Select a file to view its contents

Installation

npx servercn add email-service