const { SESClient, SendEmailCommand } = require('@aws-sdk/client-ses'); const { S3Client, GetObjectCommand, PutObjectCommand } = require('@aws-sdk/client-s3'); const { getSignedUrl } = require('@aws-sdk/s3-request-presigner'); const { simpleParser } = require('mailparser'); // Initialize clients const sesClient = new SESClient({ region: process.env.AWS_REGION || 'us-west-2' }); const s3Client = new S3Client({ region: process.env.AWS_REGION || 'us-west-2' }); exports.handler = async (event) => { try { // Extract S3 event information if (!event.Records || !event.Records[0] || !event.Records[0].s3) { throw new Error('Invalid S3 event structure'); } const s3Record = event.Records[0].s3; const bucketName = s3Record.bucket.name; const objectKey = s3Record.object.key; // Filter out attachment files to prevent infinite loops if (objectKey.startsWith('attachments/')) { console.log(`Skipping attachment file: ${objectKey}`); return { statusCode: 200, body: JSON.stringify({ message: 'Skipped attachment file', reason: 'Object is in attachments/ prefix', objectKey: objectKey }) }; } console.log(`Processing email: ${objectKey}`); // Get the email content from S3 const getObjectParams = { Bucket: bucketName, Key: objectKey }; const s3Response = await s3Client.send(new GetObjectCommand(getObjectParams)); const emailContent = await s3Response.Body.transformToString(); // Extract recipient from the object key path const pathParts = objectKey.split('/'); const recipient = pathParts[0]; // e.g., "calculator" const forwardEmail = process.env.FORWARD_EMAIL; const domainName = process.env.DOMAIN_NAME || '127local.net'; const environment = process.env.ENVIRONMENT || 'production'; // Parse the email using mailparser const parsedEmail = await simpleParser(emailContent); // Extract email details const subject = parsedEmail.subject || 'No Subject'; const from = parsedEmail.from?.text || 'unknown@example.com'; const emailBody = parsedEmail.text || parsedEmail.html || 'No body content'; // Create a clean, properly formatted forwarded email const forwardSubject = `[${environment.toUpperCase()}] FWD: ${subject}`; // Build the email body with proper formatting let forwardBody = `Email forwarded from ${from} to ${recipient}@${domainName} (${environment.toUpperCase()})\n`; forwardBody += `Forwarded at: ${new Date().toISOString()}\n`; forwardBody += `\n--- Original Email ---\n\n`; forwardBody += `Subject: ${subject}\n`; forwardBody += `From: ${from}\n`; forwardBody += `\n${emailBody}`; // Process attachments and generate download links let attachmentLinks = []; if (parsedEmail.attachments && parsedEmail.attachments.length > 0) { console.log(`Processing ${parsedEmail.attachments.length} attachments...`); for (const attachment of parsedEmail.attachments) { try { // Create unique filename for S3 const timestamp = Date.now(); const safeFilename = attachment.filename.replace(/[^a-zA-Z0-9.-]/g, '_'); const s3Key = `attachments/${recipient}/${timestamp}-${safeFilename}`; // Upload attachment to S3 const uploadParams = { Bucket: bucketName, Key: s3Key, Body: attachment.content, ContentType: attachment.contentType, Metadata: { originalEmail: objectKey, recipient: recipient, sender: from, originalFilename: attachment.filename } }; await s3Client.send(new PutObjectCommand(uploadParams)); console.log(`Uploaded: ${attachment.filename}`); // Generate pre-signed download URL (expires in 7 days) const downloadUrl = await getSignedUrl( s3Client, new GetObjectCommand({ Bucket: bucketName, Key: s3Key }), { expiresIn: 604800 } // 7 days ); attachmentLinks.push({ filename: attachment.filename, type: attachment.contentType, size: attachment.size, downloadUrl: downloadUrl }); } catch (error) { console.error(`Failed to process attachment ${attachment.filename}:`, error); // Continue with other attachments } } } // Add attachment information with download links if (attachmentLinks.length > 0) { forwardBody += `\n\n--- Attachments (${attachmentLinks.length}) ---\n`; attachmentLinks.forEach((attachment) => { forwardBody += `• ${attachment.filename} (${attachment.type}, ${attachment.size} bytes)\n`; forwardBody += ` Download: ${attachment.downloadUrl}\n`; forwardBody += ` Note: Download link expires in 7 days\n\n`; }); } // Create the forwarded email using SendEmail const forwardParams = { Source: `noreply@${domainName}`, Destination: { ToAddresses: [forwardEmail] }, Message: { Subject: { Data: forwardSubject }, Body: { Text: { Data: forwardBody } } } }; // Send the properly formatted email const result = await sesClient.send(new SendEmailCommand(forwardParams)); console.log(`Email forwarded successfully: ${result.MessageId}`); return { statusCode: 200, body: JSON.stringify({ message: 'Email forwarded successfully', messageId: result.MessageId, originalRecipient: recipient, forwardedTo: forwardEmail, s3Location: `${bucketName}/${objectKey}` }) }; } catch (error) { console.error('Error processing email:', error); return { statusCode: 500, body: JSON.stringify({ error: 'Failed to process email', message: error.message, stack: error.stack }) }; } };