All files / resources emailHandler.ts

0% Statements 0/66
0% Branches 0/1
0% Functions 0/1
0% Lines 0/66

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94                                                                                                                                                                                           
import {
  GetObjectCommand, S3Client
} from '@aws-sdk/client-s3';
import {
  SESv2Client,
  SendEmailCommand
} from '@aws-sdk/client-sesv2';
import {
  SESEvent, SESEventRecord
} from 'aws-lambda';
import PostalMime from 'postal-mime';
 
import {
  BUCKET_EMAIL, EMAIL_SOURCE
} from '@/types/backend/environment';
import {
  BILLING_EMAIL_ADDRESS, FORWARD_EMAIL_TO
} from '@/utils/backend/hidden-constants';
import {
  LogLevel,
  getLogger
} from '@/utils/common/logger';
 
const logger = getLogger('resources/emailHandler');
const s3 = new S3Client();
const ses = new SESv2Client();
 
async function parseEvent(record: SESEventRecord) {
  logger.trace('parseEvent', ...arguments);
  logger.info('Record', record);
 
  // Pull out the email information
  const email = record.ses.mail;
  logger.info(`Getting email from ${BUCKET_EMAIL()} with key /emails/${email.messageId}`);
  const rawData = await s3.send(new GetObjectCommand({
    Bucket: BUCKET_EMAIL(),
    Key: `emails/${email.messageId}`,
  }));
  if (typeof rawData.Body === 'undefined') {
    throw new Error('Email body is undefined');
  }
  const emailBody = await rawData.Body.transformToString('utf-8');
 
  // Parse the body
  const emailParsed = await PostalMime.parse(emailBody);
 
  // Forward the email
  await ses.send(new SendEmailCommand({
    Destination: {
      ToAddresses: [ FORWARD_EMAIL_TO, ],
    },
    ReplyToAddresses: [ emailParsed.from?.address || BILLING_EMAIL_ADDRESS, ],
    FromEmailAddress: `COFRN Billing <${BILLING_EMAIL_ADDRESS}>`,
    FromEmailAddressIdentityArn: EMAIL_SOURCE(),
    Content: {
      Simple: {
        Subject: {
          Data: `[COFRN] ${emailParsed.subject || 'No Subject'}`,
        },
        Body: {
          ...emailParsed.html
            ? { Html: { Data: emailParsed.html, }, }
            : {},
          ...emailParsed.text
            ? { Text: { Data: emailParsed.text, }, }
            : {},
        },
        ...emailParsed.attachments && emailParsed.attachments.length > 0
          ? {
            Attachments: emailParsed.attachments.map(attachment => ({
              FileName: attachment.filename || 'attachment',
              ContentType: attachment.mimeType,
              ContentDisposition: attachment.disposition === 'inline' ? 'INLINE' : 'ATTACHMENT',
              ContentId: attachment.contentId,
              RawContent: typeof attachment.content === 'string'
                ? Buffer.from(attachment.content, attachment.encoding || 'utf8')
                : new Uint8Array(attachment.content),
            })),
          }
          : {},
      },
    },
  }));
}
 
export async function main(event: SESEvent) {
  logger.trace('main', ...arguments);
  logger.setLevel(LogLevel.Info);
 
  // Get the S3 events
  const s3EventRecords: SESEventRecord[] = event.Records || [];
  await Promise.all(s3EventRecords.map(parseEvent));
}