fitlien-services/functions/src/utils/emailService.ts
Allen T J a3241afd45
All checks were successful
Deploy FitLien services to Dev / Deploy to Dev (push) Successful in 4m0s
phonepe (#66)
Co-authored-by: AllenTJ7 <163137620+AllenTJ7@users.noreply.github.com>
Reviewed-on: #66
2025-06-25 14:28:07 +00:00

187 lines
5.6 KiB
TypeScript

import { getLogger } from "../shared/config";
import { SESClient } from "@aws-sdk/client-ses";
import { SendEmailCommand, SendRawEmailCommand } from "@aws-sdk/client-ses";
import * as mime from 'mime-types';
import axios from 'axios';
const logger = getLogger();
interface EmailRequest {
to: string | string[];
subject: string;
html: string;
text?: string;
from: string;
replyTo?: string;
attachments?: Attachment[];
fileUrl?: string;
fileName?: string;
}
interface Attachment {
filename: string;
content: string | Buffer;
contentType?: string;
}
const stripHtml = (html: string): string => {
if (!html) return '';
return html.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
}
async function sendSimpleEmail(data: EmailRequest, recipients: string[]) {
const ses = new SESClient({
region: process.env.AWS_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID || '',
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || ''
}
});
const command = new SendEmailCommand({
Source: data.from,
Destination: { ToAddresses: recipients },
Message: {
Subject: { Data: data.subject },
Body: {
Html: { Data: data.html },
Text: { Data: data.text || stripHtml(data.html) }
}
},
ReplyToAddresses: data.replyTo ? [data.replyTo] : undefined,
});
const result = await ses.send(command);
return { messageId: result.MessageId };
}
async function sendEmailWithAttachments(data: EmailRequest, recipients: string[]) {
const ses = new SESClient({
region: process.env.AWS_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID || '',
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || ''
}
});
const boundary = `boundary_${Math.random().toString(16).substr(2)}`;
let rawMessage = `From: ${data.from}\n`;
rawMessage += `To: ${recipients.join(', ')}\n`;
rawMessage += `Subject: ${data.subject}\n`;
rawMessage += `MIME-Version: 1.0\n`;
rawMessage += `Content-Type: multipart/mixed; boundary="${boundary}"\n\n`;
rawMessage += `--${boundary}\n`;
rawMessage += `Content-Type: multipart/alternative; boundary="alt_${boundary}"\n\n`;
if (data.text) {
rawMessage += `--alt_${boundary}\n`;
rawMessage += `Content-Type: text/plain; charset=UTF-8\n\n`;
rawMessage += `${data.text}\n\n`;
}
rawMessage += `--alt_${boundary}\n`;
rawMessage += `Content-Type: text/html; charset=UTF-8\n\n`;
rawMessage += `${data.html}\n\n`;
rawMessage += `--alt_${boundary}--\n\n`;
for (const attachment of data.attachments || []) {
const contentType = attachment.contentType ||
mime.lookup(attachment.filename) ||
'application/octet-stream';
rawMessage += `--${boundary}\n`;
rawMessage += `Content-Type: ${contentType}; name="${attachment.filename}"\n`;
rawMessage += `Content-Disposition: attachment; filename="${attachment.filename}"\n`;
rawMessage += `Content-Transfer-Encoding: base64\n\n`;
const contentBuffer = typeof attachment.content === 'string'
? Buffer.from(attachment.content, 'base64')
: attachment.content;
rawMessage += contentBuffer.toString('base64') + '\n\n';
}
rawMessage += `--${boundary}--`;
const command = new SendRawEmailCommand({
RawMessage: { Data: Buffer.from(rawMessage) }
});
const result = await ses.send(command);
return { messageId: result.MessageId };
}
async function downloadFileFromUrl(url: string): Promise<Buffer> {
try {
const response = await axios.get(url, { responseType: 'arraybuffer' });
return Buffer.from(response.data);
} catch (error) {
logger.error(`Error downloading file from URL: ${error}`);
throw new Error(`Failed to download file: ${error}`);
}
}
export async function sendEmailWithAttachmentUtil(
toAddress: string,
subject: string,
message: string,
fileUrl: string,
fileName?: string
): Promise<any> {
try {
logger.info(`Sending email with attachment to: ${toAddress}`);
const data: EmailRequest = {
to: toAddress,
html: message,
subject: subject,
text: stripHtml(message),
from: process.env.SES_FROM_EMAIL || 'support@fitlien.com',
replyTo: process.env.SES_REPLY_TO_EMAIL || 'support@fitlien.com',
attachments: []
};
if (fileUrl && fileName) {
logger.info(`Downloading attachment from URL: ${fileUrl}`);
try {
const fileContent = await downloadFileFromUrl(fileUrl);
data.attachments!.push({
filename: fileName,
content: fileContent,
contentType: mime.lookup(fileName) || 'application/octet-stream'
});
logger.info(`Successfully downloaded attachment: ${fileName}`);
} catch (downloadError) {
logger.error(`Failed to download attachment: ${downloadError}`);
throw new Error(`Failed to process attachment: ${downloadError}`);
}
}
if (!data.to || !data.subject || !data.html || !data.from) {
throw new Error('Missing required email fields');
}
logger.info(`Sending Email '${data.subject}' to '${data.to}' from '${data.from}'`);
const recipients = Array.isArray(data.to) ? data.to : [data.to];
let result;
if (data.attachments && data.attachments.length > 0) {
result = await sendEmailWithAttachments(data, recipients);
} else {
result = await sendSimpleEmail(data, recipients);
}
logger.info('Email sent successfully via SES');
return { success: true, result };
} catch (error) {
logger.error('Error sending email with attachment via SES:', error);
throw error;
}
}