phonepe (#51)
All checks were successful
Deploy FitLien services to Dev / Deploy to Dev (push) Successful in 3m42s
All checks were successful
Deploy FitLien services to Dev / Deploy to Dev (push) Successful in 3m42s
Co-authored-by: AllenTJ7 <163137620+AllenTJ7@users.noreply.github.com> Reviewed-on: #51
This commit is contained in:
parent
59c4f88ff0
commit
d9cd772f6e
@ -1,15 +1,135 @@
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as https from 'https';
|
||||
import { getLogger } from "../shared/config";
|
||||
import formData from 'form-data';
|
||||
import Mailgun from 'mailgun.js';
|
||||
const { convert } = require('html-to-text');
|
||||
|
||||
const mailgun = new Mailgun(formData);
|
||||
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; // Base64 encoded string or 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: 'ap-south-1',
|
||||
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: 'ap-south-1',
|
||||
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 async function sendEmailWithAttachmentUtil(
|
||||
toAddress: string,
|
||||
subject: string,
|
||||
@ -18,53 +138,58 @@ export async function sendEmailWithAttachmentUtil(
|
||||
fileName?: string
|
||||
): Promise<any> {
|
||||
try {
|
||||
const tempFilePath = path.join(os.tmpdir(), fileName || 'attachment.pdf');
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const file = fs.createWriteStream(tempFilePath);
|
||||
https.get(fileUrl, (res) => {
|
||||
res.pipe(file);
|
||||
file.on('finish', () => {
|
||||
file.close();
|
||||
resolve();
|
||||
logger.info(`Sending email with attachment to: ${toAddress}`);
|
||||
|
||||
// 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: []
|
||||
};
|
||||
|
||||
// Handle file URL if provided
|
||||
if (fileUrl && fileName) {
|
||||
logger.info(`Downloading attachment from URL: ${fileUrl}`);
|
||||
try {
|
||||
const fileContent = await downloadFileFromUrl(fileUrl);
|
||||
|
||||
// Add the downloaded file as an attachment
|
||||
data.attachments!.push({
|
||||
filename: fileName,
|
||||
content: fileContent,
|
||||
contentType: mime.lookup(fileName) || 'application/octet-stream'
|
||||
});
|
||||
}).on('error', (err) => {
|
||||
fs.unlink(tempFilePath, () => {});
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
const client = mailgun.client({ username: 'api', key: process.env.MAILGUN_API_KEY! });
|
||||
const options = {
|
||||
wordwrap: 130,
|
||||
};
|
||||
const textMessage = convert(message, options);
|
||||
const fileBuffer = fs.readFileSync(tempFilePath);
|
||||
const attachmentFilename = fileName || path.basename(fileUrl.split('?')[0]);
|
||||
|
||||
const data = {
|
||||
from: process.env.MAILGUN_FROM_ADDRESS,
|
||||
to: toAddress,
|
||||
subject: subject,
|
||||
text: textMessage,
|
||||
html: message,
|
||||
attachment: {
|
||||
data: fileBuffer,
|
||||
filename: attachmentFilename,
|
||||
contentType: 'application/pdf',
|
||||
}
|
||||
};
|
||||
|
||||
const result = await client.messages.create(process.env.MAILGUN_SERVER!, data);
|
||||
fs.unlinkSync(tempFilePath);
|
||||
logger.info('Email with attachment from URL sent successfully');
|
||||
return { success: true, result };
|
||||
} catch (e) {
|
||||
logger.error(`Error while sending E-mail. Error: ${e}`);
|
||||
throw e;
|
||||
|
||||
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 from URL:', error);
|
||||
logger.error('Error sending email with attachment via SES:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user