From eb1323a8a2699616738f974d891591985ee74018 Mon Sep 17 00:00:00 2001 From: brunoabreu-levio Date: Wed, 13 Mar 2024 11:07:54 -0400 Subject: [PATCH] Added the option to add attachments when sending an email response. --- .../events/event.json | 44 ++++++--- .../iac/lambda.tf | 17 +++- .../EmailResponseProcessorFunction/pom.xml | 15 +++ .../awsdemo/emailresponseprocessor/App.java | 15 ++- .../service/EmailService.java | 92 +++++++++++++++---- .../service/S3Service.java | 38 ++++++++ 6 files changed, 186 insertions(+), 35 deletions(-) create mode 100644 lambdas/EmailProcessor/EmailResponseProcessorFunction/src/main/java/com/levio/awsdemo/emailresponseprocessor/service/S3Service.java diff --git a/lambdas/EmailProcessor/EmailResponseProcessorFunction/events/event.json b/lambdas/EmailProcessor/EmailResponseProcessorFunction/events/event.json index e697376..53114a1 100644 --- a/lambdas/EmailProcessor/EmailResponseProcessorFunction/events/event.json +++ b/lambdas/EmailProcessor/EmailResponseProcessorFunction/events/event.json @@ -1,27 +1,49 @@ { "Records": [ { - "messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78", - "receiptHandle": "MessageReceiptHandle", - "body": "Quel est le nombre maximum de jours pour prendre des vacances ?", + "messageId": "9997254c-560c-4e3d-a8bf-442318bf5bf9", + "receiptHandle": "AQEBfui1DK9NTuG5jV+6m/275yYhWfGYS+8UoGEi3M+HLS1BSKGw6YZWCEHb8k9VT4DEg3COU3lCagt+9mgang1/STRoKUEvyFt6ciSYesX2I7VHn75RUY+f0XgHc/7F+p124U3ugTHzmhQASIR1Er0C2lczfoSMhIeVqqtgdFGXjzBGJ6ZTP5YdcOhHWOBeL2LjQcCypTew3T2TE/qSsBO451nLigjPGGgg13sJDGha1uxSy2ILXKrtNBF/ugPn3DZq3c8PFqpCtNzyn2seQzC97bLVWC6J6Tt2zF7/GmZ9IYP5FTik1XU7QDJYksDh35r2G0czubhyP5g1fkrP6YhqGQ==", + "eventSourceARN": "arn:aws:sqs:us-east-1:446872271111:levio-demo-fev-email-response-processor-queue-dev.fifo", + "eventSource": "aws:sqs", + "awsRegion": "us-east-1", + "body": "Resume response", + "md5OfBody": "f3dcab2186d689158fcb2b758759c5f6", + "md5OfMessageAttributes": "3484a7928d62feab81a7f3d085ce9fe2", "attributes": { - "sender": "bruno.abreu@levio.ca", - "subject": "Levio HR" + "ApproximateReceiveCount": "1", + "SentTimestamp": "1710275621941", + "SequenceNumber": "18884574632926447616", + "MessageGroupId": "bruno.abreu@levio.ca", + "SenderId": "AROAWQC5JXED6GYDWVIPM:ResumeRequestProcessor-ResumeRequestProcessorFunct-y6fYGqtNKouH", + "MessageDeduplicationId": "fegu2ar9ktaph6b1o66k8t2eaiqgff1f4s2pfa81", + "ApproximateFirstReceiveTimestamp": "1710275621941" }, "messageAttributes": { "sender": { "stringValue": "bruno.abreu@levio.ca", + "stringListValues": [], + "binaryListValues": [], "dataType": "String" }, "subject": { - "stringValue": "Levio HR", + "stringValue": "Test Resume 2", + "stringListValues": [], + "binaryListValues": [], + "dataType": "String" + }, + "attachment1": { + "stringValue": "s3://levio-demo-fev-esta-ses-bucket-dev/resume/AudioTestResume2-fegu2ar9ktaph6b1o66k8t2eaiqgff1f4s2pfa81.txt", + "stringListValues": [], + "binaryListValues": [], + "dataType": "String" + }, + "attachment0": { + "stringValue": "s3://levio-demo-fev-esta-ses-bucket-dev/resume/dialogue/AudioTestResume2-fegu2ar9ktaph6b1o66k8t2eaiqgff1f4s2pfa81.txt", + "stringListValues": [], + "binaryListValues": [], "dataType": "String" } - }, - "md5OfBody": "{{{md5_of_body}}}", - "eventSource": "aws:sqs", - "eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:MyQueue", - "awsRegion": "us-east-1" + } } ] } \ No newline at end of file diff --git a/lambdas/EmailProcessor/EmailResponseProcessorFunction/iac/lambda.tf b/lambdas/EmailProcessor/EmailResponseProcessorFunction/iac/lambda.tf index 6ab5f1c..cf45a13 100644 --- a/lambdas/EmailProcessor/EmailResponseProcessorFunction/iac/lambda.tf +++ b/lambdas/EmailProcessor/EmailResponseProcessorFunction/iac/lambda.tf @@ -57,12 +57,27 @@ module "lambda_function_container_image" { ses = { effect = "Allow" actions = [ - "ses:SendEmail" + "ses:SendEmail", + "ses:SendRawEmail" ] resources = [ "arn:aws:ses:${var.aws_region}:${data.aws_caller_identity.current.account_id}:*" ] } + + s3 = { + effect = "Allow" + actions = [ + "s3:Get*", + "s3:List*", + "s3:Describe*", + "s3-object-lambda:Get*", + "s3-object-lambda:List*" + ] + resources = [ + "arn:aws:s3:*:*:*" + ] + } } create_current_version_allowed_triggers = false diff --git a/lambdas/EmailProcessor/EmailResponseProcessorFunction/pom.xml b/lambdas/EmailProcessor/EmailResponseProcessorFunction/pom.xml index 3349a4f..658ca65 100644 --- a/lambdas/EmailProcessor/EmailResponseProcessorFunction/pom.xml +++ b/lambdas/EmailProcessor/EmailResponseProcessorFunction/pom.xml @@ -27,11 +27,26 @@ sesv2 2.20.109 + + software.amazon.awssdk + s3 + 2.20.52 + software.amazon.awssdk apache-client 2.20.27 + + com.sun.mail + jakarta.mail + 2.0.1 + + + jakarta.mail + jakarta.mail-api + 2.1.2 + org.projectlombok lombok diff --git a/lambdas/EmailProcessor/EmailResponseProcessorFunction/src/main/java/com/levio/awsdemo/emailresponseprocessor/App.java b/lambdas/EmailProcessor/EmailResponseProcessorFunction/src/main/java/com/levio/awsdemo/emailresponseprocessor/App.java index 7bbb595..70c99ce 100644 --- a/lambdas/EmailProcessor/EmailResponseProcessorFunction/src/main/java/com/levio/awsdemo/emailresponseprocessor/App.java +++ b/lambdas/EmailProcessor/EmailResponseProcessorFunction/src/main/java/com/levio/awsdemo/emailresponseprocessor/App.java @@ -4,15 +4,17 @@ import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.events.SQSEvent; import com.levio.awsdemo.emailresponseprocessor.service.EmailService; +import com.levio.awsdemo.emailresponseprocessor.service.S3Service; -import java.io.IOException; +import java.util.ArrayList; +import java.util.List; public class App implements RequestHandler { private final EmailService emailService; public App() { - this.emailService = new EmailService(); + this.emailService = new EmailService(new S3Service()); } public App(EmailService emailService) { @@ -28,9 +30,16 @@ public Void handleRequest(SQSEvent event, Context context) { SQSEvent.MessageAttribute sender = record.getMessageAttributes().get("sender"); SQSEvent.MessageAttribute subject = record.getMessageAttributes().get("subject"); + List attachments = new ArrayList<>(); + record.getMessageAttributes().forEach((key, value) -> { + if (key.startsWith("attachment")) { + attachments.add(value.getStringValue()); + } + }); + if (!message.isEmpty() && sender != null && subject != null) { try { - emailService.send(message, sender.getStringValue(), subject.getStringValue()); + emailService.send(message, sender.getStringValue(), subject.getStringValue(), attachments); } catch (Exception e) { throw new RuntimeException(e); } diff --git a/lambdas/EmailProcessor/EmailResponseProcessorFunction/src/main/java/com/levio/awsdemo/emailresponseprocessor/service/EmailService.java b/lambdas/EmailProcessor/EmailResponseProcessorFunction/src/main/java/com/levio/awsdemo/emailresponseprocessor/service/EmailService.java index 9469c55..c91e782 100644 --- a/lambdas/EmailProcessor/EmailResponseProcessorFunction/src/main/java/com/levio/awsdemo/emailresponseprocessor/service/EmailService.java +++ b/lambdas/EmailProcessor/EmailResponseProcessorFunction/src/main/java/com/levio/awsdemo/emailresponseprocessor/service/EmailService.java @@ -1,16 +1,33 @@ package com.levio.awsdemo.emailresponseprocessor.service; +import jakarta.activation.DataHandler; +import jakarta.activation.DataSource; +import jakarta.mail.Message; +import jakarta.mail.MessagingException; +import jakarta.mail.Session; +import jakarta.mail.internet.InternetAddress; +import jakarta.mail.internet.MimeBodyPart; +import jakarta.mail.internet.MimeMessage; +import jakarta.mail.internet.MimeMultipart; +import jakarta.mail.util.ByteArrayDataSource; +import lombok.RequiredArgsConstructor; +import software.amazon.awssdk.core.SdkBytes; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.sesv2.SesV2Client; -import software.amazon.awssdk.services.sesv2.model.Body; -import software.amazon.awssdk.services.sesv2.model.Content; import software.amazon.awssdk.services.sesv2.model.Destination; import software.amazon.awssdk.services.sesv2.model.EmailContent; -import software.amazon.awssdk.services.sesv2.model.Message; +import software.amazon.awssdk.services.sesv2.model.RawMessage; import software.amazon.awssdk.services.sesv2.model.SendEmailRequest; import software.amazon.awssdk.services.sesv2.model.SesV2Exception; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Properties; +@RequiredArgsConstructor public class EmailService { private static final String SENDER_EMAIL = System.getenv("SENDER_EMAIL"); @@ -19,30 +36,60 @@ public class EmailService { .region(Region.US_EAST_1) .build(); - public void send(String message, String recipient, String subject) { - Content content = Content.builder() - .data(message) - .build(); + private final S3Service s3Service; - Destination destination = Destination.builder() - .toAddresses(recipient) - .build(); + public void send(String message, String recipient, String subject, List attachments) throws MessagingException, IOException { + Session session = Session.getDefaultInstance(new Properties()); - Content sub = Content.builder() - .data(subject) - .build(); + MimeMessage mimeMessage = new MimeMessage(session); + + mimeMessage.setSubject(subject, "UTF-8"); + mimeMessage.setFrom(new InternetAddress(SENDER_EMAIL)); + mimeMessage.setRecipients(Message.RecipientType.TO, InternetAddress.parse(recipient)); + + MimeMultipart messageBody = new MimeMultipart("alternative"); + + MimeBodyPart wrap = new MimeBodyPart(); + + MimeBodyPart bodyPart = new MimeBodyPart(); + bodyPart.setContent(message, "text/html; charset=UTF-8"); + + messageBody.addBodyPart(bodyPart); + + wrap.setContent(messageBody); + + MimeMultipart mimeMultipart = new MimeMultipart("mixed"); - Body body = Body.builder() - .html(content) + mimeMultipart.addBodyPart(wrap); + + for (String attachment : attachments) { + MimeBodyPart att = new MimeBodyPart(); + + InputStream inputStream = s3Service.getFile(attachment); + byte[] byteArray = inputStream.readAllBytes(); + + DataSource dataSource = new ByteArrayDataSource(byteArray, "application/octet-stream"); + att.setDataHandler(new DataHandler(dataSource)); + att.setFileName(extractFilename(attachment)); + + mimeMultipart.addBodyPart(att); + } + + mimeMessage.setContent(mimeMultipart); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + mimeMessage.writeTo(outputStream); + + RawMessage rawMsg = RawMessage.builder() + .data(SdkBytes.fromByteBuffer(ByteBuffer.wrap(outputStream.toByteArray()))) .build(); - Message msg = Message.builder() - .subject(sub) - .body(body) + Destination destination = Destination.builder() + .toAddresses(recipient) .build(); EmailContent emailContent = EmailContent.builder() - .simple(msg) + .raw(rawMsg) .build(); SendEmailRequest emailRequest = SendEmailRequest.builder() @@ -60,4 +107,9 @@ public void send(String message, String recipient, String subject) { throw e; } } -} + + private String extractFilename(String attachment) { + String[] parts = attachment.split("/"); + return parts[parts.length - 1]; + } +} \ No newline at end of file diff --git a/lambdas/EmailProcessor/EmailResponseProcessorFunction/src/main/java/com/levio/awsdemo/emailresponseprocessor/service/S3Service.java b/lambdas/EmailProcessor/EmailResponseProcessorFunction/src/main/java/com/levio/awsdemo/emailresponseprocessor/service/S3Service.java new file mode 100644 index 0000000..37b3955 --- /dev/null +++ b/lambdas/EmailProcessor/EmailResponseProcessorFunction/src/main/java/com/levio/awsdemo/emailresponseprocessor/service/S3Service.java @@ -0,0 +1,38 @@ +package com.levio.awsdemo.emailresponseprocessor.service; + +import software.amazon.awssdk.core.ResponseBytes; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.GetObjectRequest; +import software.amazon.awssdk.services.s3.model.GetObjectResponse; + +import java.io.InputStream; + +public class S3Service { + + private final S3Client s3 = S3Client.builder() + .region(Region.US_EAST_1) + .build(); + + public InputStream getFile(String uri) { + GetObjectRequest objectRequest = GetObjectRequest + .builder() + .key(extractKey(uri)) + .bucket(extractBucket(uri)) + .build(); + + ResponseBytes objectBytes = s3.getObjectAsBytes(objectRequest); + return objectBytes.asInputStream(); + } + + private String extractBucket(String uri) { + int endIndex = uri.indexOf("/", 5); + return uri.substring(5, endIndex); + } + + private String extractKey(String uri) { + int startIndex = uri.indexOf("/", 5); + return uri.substring(startIndex + 1); + } + +}