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
- Messages are added to the SQS Queue.
- AWS Lambda triggers on new messages.
- Message schema is validated using Joi.
- Emails are sent via Nodemailer.
- 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
3. Environment Variables
Define these variables in sls/custom.yml based on your deployment stage:
-
SMTP_HOST
: smtp.gmail.com -
SMTP_PORT
: 465 -
SMTP_USER
: your_email@gmail.com -
SMTP_PASS
: your_app_password -
EMAIL_FROM
: your_email@gmail.com -
AWS_REGION
: ap-south-1 -
S3_BUCKET_NAME
: dev-file-upload-v2
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();
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();
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,
});
}
}
};
7. Deployment
- Install dependencies:
npm install
- Deploy to AWS:
npx serverless deploy --stage dev
- Check logs:
npx serverless logs -f sendEmail -s dev
8. Testing
Sample SQS Message Payload
{
"to": "recipient@example.com",
"subject": "Welcome to Our Platform",
"templateContent": "<h1>Hello, Welcome!</h1>"
}
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)