DEV Community

Mohin Sheikh
Mohin Sheikh

Posted on

Serverless Email Service with AWS Lambda, Node.js, and SQS πŸ“§

Image description

1. Overview

This documentation outlines the setup and implementation of an Email Service using AWS Lambda, SQS (Simple Queue Service), and Node.js with the Serverless Framework. The service leverages Nodemailer for email delivery, ensuring scalability, efficiency, and configuration flexibility.

πŸ› οΈ Key Components

  • AWS Lambda: Processes email messages asynchronously.
  • AWS SQS: Queues email messages for Lambda to process.
  • Nodemailer: Handles email delivery via SMTP.
  • Serverless Framework: Infrastructure as Code (IaC) management.
  • Node.js & TypeScript: Backend runtime with type safety.

πŸ”„ Workflow

  1. Messages are added to the SQS Queue.
  2. AWS Lambda triggers on new messages.
  3. Message schema is validated using Joi.
  4. Emails are sent via Nodemailer.
  5. Success and failure logs are recorded using a Logger.

2. Project Structure

β”œβ”€β”€ src
β”‚   β”œβ”€β”€ services
β”‚   β”‚   β”œβ”€β”€ EmailService.ts   # Core email service using Nodemailer
β”‚   β”œβ”€β”€ controllers
β”‚   β”‚   β”œβ”€β”€ emailController.ts   # Controls email operations
β”‚   β”œβ”€β”€ handlers
β”‚   β”‚   β”œβ”€β”€ sendEmailHandler.ts   # Lambda handler for processing SQS
β”‚   β”œβ”€β”€ utils
β”‚   β”‚   β”œβ”€β”€ logger.ts   # Logger for structured logging
β”‚   β”‚   β”œβ”€β”€ validate.ts # Environment validation helper
β”œβ”€β”€ sls
β”‚   β”œβ”€β”€ custom.yml    # Custom configuration
β”‚   β”œβ”€β”€ functions.yml # Lambda function definitions
β”‚   β”œβ”€β”€ resources.yml # AWS resource definitions
β”œβ”€β”€ serverless.yml   # Main Serverless Framework configuration
Enter fullscreen mode Exit fullscreen mode

3. Environment Variables

Define these variables in sls/custom.yml based on your deployment stage:

Use the getEnvVariable helper for validation.


4. Email Service Implementation

πŸ“ src/services/EmailService.ts

import nodemailer from 'nodemailer';
import logger from '../utils/logger';
import { getEnvVariable } from '../utils/validate';

class EmailService {
  private transporter: nodemailer.Transporter;
  private smtpHost: string;
  private smtpPort: number;
  private smtpUser: string;
  private smtpPass: string;
  private smtpFrom: string;

  constructor() {
    this.smtpHost = getEnvVariable('SMTP_HOST');
    this.smtpPort = parseInt(getEnvVariable('SMTP_PORT', '587'));
    this.smtpUser = getEnvVariable('SMTP_USER');
    this.smtpPass = getEnvVariable('SMTP_PASS');
    this.smtpFrom = process.env['EMAIL_FROM'] || this.smtpUser;

    this.transporter = nodemailer.createTransport({
      host: this.smtpHost,
      port: this.smtpPort,
      secure: this.smtpPort === 465,
      auth: {
        user: this.smtpUser,
        pass: this.smtpPass,
      },
    });

    this.verifyConnection();
  }

  private async verifyConnection() {
    try {
      await this.transporter.verify();
      logger.info('SMTP server is ready to send emails');
    } catch (error) {
      logger.error('Failed to verify SMTP connection:', error);
      throw new Error('Unable to connect to SMTP server');
    }
  }

  async sendEmail(to: string, subject: string, htmlContent: string): Promise<void> {
    const mailOptions = {
      from: this.smtpFrom,
      to,
      subject,
      html: htmlContent,
    };

    try {
      const info = await this.transporter.sendMail(mailOptions);
      logger.info('Email sent successfully:', { messageId: info.messageId, to });
    } catch (error: any) {
      logger.error(`Error sending email: ${error.message}`, error);
      throw new Error('Email sending failed');
    }
  }
}

export default new EmailService();
Enter fullscreen mode Exit fullscreen mode

5. Email Controller

πŸ“ src/controllers/emailController.ts

import logger from '../utils/logger';
import EmailService from '../services/EmailService';

class EmailController {
  async sendCustomEmail(to: string, subject: string, templateContent: string) {
    try {
      logger.info(`Sending email to ${to} with subject "${subject}"`);
      await EmailService.sendEmail(to, subject, templateContent);
      logger.info(`Email sent successfully to ${to}`);
      return { success: true, message: 'Email sent successfully' };
    } catch (error: any) {
      logger.error(`Failed to send email to ${to} - ${error.message}`);
      throw new Error(`Failed to send custom email - ${error.message}`);
    }
  }
}

export default new EmailController();
Enter fullscreen mode Exit fullscreen mode

6. Lambda Handler

πŸ“ src/handlers/sendEmailHandler.ts

import Joi from 'joi';
import EmailController from '../controllers/emailController';
import logger from '../utils/logger';
import { Handler } from 'aws-lambda';

const emailSchema = Joi.object({
  to: Joi.string().email().required(),
  subject: Joi.string().required(),
  templateContent: Joi.string().required(),
});

export const handler: Handler = async (event) => {
  for (const record of event.Records) {
    try {
      const message = JSON.parse(record.body);
      const { error, value } = emailSchema.validate(message);

      if (error) {
        logger.warn('Validation failed', {
          recordId: record.messageId,
          errors: error.details.map(e => e.message),
        });
        continue;
      }

      const { to, subject, templateContent } = value;
      await EmailController.sendCustomEmail(to, subject, templateContent);
      logger.info('Email sent successfully', { recordId: record.messageId, to });
    } catch (error: any) {
      logger.error('Failed to process SQS message', {
        recordId: record.messageId,
        error: error.message,
      });
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

7. Deployment

  1. Install dependencies:
npm install
Enter fullscreen mode Exit fullscreen mode
  1. Deploy to AWS:
npx serverless deploy --stage dev
Enter fullscreen mode Exit fullscreen mode
  1. Check logs:
npx serverless logs -f sendEmail -s dev
Enter fullscreen mode Exit fullscreen mode

8. Testing

Sample SQS Message Payload

{
  "to": "recipient@example.com",
  "subject": "Welcome to Our Platform",
  "templateContent": "<h1>Hello, Welcome!</h1>"
}
Enter fullscreen mode Exit fullscreen mode

Add this payload to your SQS Queue.


9. Conclusion

Your Serverless Email Service is now ready! πŸš€ Manage and monitor emails efficiently using AWS Lambda, SQS, and Nodemailer.


Author: Mohin Sheikh

Follow me on GitHub for more insights!

Top comments (0)