All checks were successful
Deploy FitLien services to Dev / Deploy to Dev (push) Successful in 4m4s
Reviewed-on: #68 Co-authored-by: DhanshCOSQ <dhanshas@cosq.net> Co-committed-by: DhanshCOSQ <dhanshas@cosq.net>
209 lines
7.4 KiB
TypeScript
209 lines
7.4 KiB
TypeScript
import { getLogger } from "../shared/config";
|
|
import { getCorsHandler } from "../shared/middleware";
|
|
import { onRequest } from "firebase-functions/v2/https";
|
|
import { Request } from "firebase-functions/v2/https";
|
|
import { Response } from "express";
|
|
import { SESClient } from "@aws-sdk/client-ses";
|
|
import { SendEmailCommand, SendRawEmailCommand } from "@aws-sdk/client-ses";
|
|
import { HttpsError } from "firebase-functions/v2/https";
|
|
import * as mime from 'mime-types';
|
|
import axios from 'axios';
|
|
|
|
const logger = getLogger();
|
|
const corsHandler = getCorsHandler();
|
|
|
|
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`;
|
|
|
|
// Add email body (multipart/alternative)
|
|
rawMessage += `--${boundary}\n`;
|
|
rawMessage += `Content-Type: multipart/alternative; boundary="alt_${boundary}"\n\n`;
|
|
|
|
// Text part
|
|
if (data.text) {
|
|
rawMessage += `--alt_${boundary}\n`;
|
|
rawMessage += `Content-Type: text/plain; charset=UTF-8\n\n`;
|
|
rawMessage += `${data.text}\n\n`;
|
|
}
|
|
|
|
// HTML part
|
|
rawMessage += `--alt_${boundary}\n`;
|
|
rawMessage += `Content-Type: text/html; charset=UTF-8\n\n`;
|
|
rawMessage += `${data.html}\n\n`;
|
|
|
|
// Close alternative part
|
|
rawMessage += `--alt_${boundary}--\n\n`;
|
|
|
|
// Add attachments
|
|
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';
|
|
}
|
|
|
|
// Close message
|
|
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 const sendEmailSES = onRequest({
|
|
region: '#{SERVICES_RGN}#'
|
|
}, (request: Request, response: Response) => {
|
|
return corsHandler(request, response, async () => {
|
|
try {
|
|
const toAddress = request.body.toAddress;
|
|
const subject = request.body.subject;
|
|
const message = request.body.message;
|
|
|
|
// Initialize data with basic fields
|
|
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: request.body.attachments as Attachment[] || []
|
|
};
|
|
|
|
// Handle file URL if provided
|
|
if (request.body.fileUrl && request.body.fileName) {
|
|
logger.info(`Downloading attachment from URL: ${request.body.fileUrl}`);
|
|
try {
|
|
const fileContent = await downloadFileFromUrl(request.body.fileUrl);
|
|
|
|
// If attachments array doesn't exist, create it
|
|
if (!data.attachments) {
|
|
data.attachments = [];
|
|
}
|
|
|
|
// Add the downloaded file as an attachment
|
|
data.attachments.push({
|
|
filename: request.body.fileName,
|
|
content: fileContent,
|
|
contentType: mime.lookup(request.body.fileName) || 'application/octet-stream'
|
|
});
|
|
|
|
logger.info(`Successfully downloaded attachment: ${request.body.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 HttpsError(
|
|
'invalid-argument',
|
|
'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];
|
|
|
|
if (data.attachments && data.attachments.length > 0) {
|
|
const messageResult = await sendEmailWithAttachments(data, recipients);
|
|
response.status(200).json(messageResult);
|
|
} else {
|
|
const messageResult = await sendSimpleEmail(data, recipients);
|
|
response.status(200).json(messageResult);
|
|
}
|
|
} catch (e) {
|
|
logger.error(`Error while sending E-mail. Error: ${e}`);
|
|
console.error(`Error while sending E-mail. Error: ${e}`);
|
|
response.status(500).json({
|
|
success: false,
|
|
error: 'Error while sending E-mail'
|
|
});
|
|
}
|
|
});
|
|
}); |