■ はじめに
https://dk521123.hatenablog.com/entry/2023/03/24/211033
https://dk521123.hatenablog.com/entry/2023/04/01/002005
https://dk521123.hatenablog.com/entry/2023/04/03/012600
の続き。 今回は、Scala で、AWS SDK for Java を使った Amazon SES (Simple Email Service)によるEmail送信をする
目次
【1】公式ドキュメント 1)サンプル集 【2】インストール 【3】ハマりポイント 1)AWS JDK for Java (SES) が色々ある 2)jakarta.mail の import が変わる 【4】サンプル
【1】公式ドキュメント
https://docs.aws.amazon.com/ja_jp/ses/latest/dg/example_sesv2_SendEmail_section.html
https://docs.aws.amazon.com/ja_jp/sdk-for-java/latest/developer-guide/java_ses_code_examples.html
1)サンプル集
https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2/example_code/ses/src/main/java/com/example
https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2/example_code/ses/src/main/java/com/example/sesv2
2)その他参考になりそうな資料
https://buildersbox.corp-sansan.com/entry/2020/05/18/110000
【2】インストール
libraryDependencies ++= Seq( "software.amazon.awssdk" % "sesv2" % "2.20.47", "com.sun.mail" % "jakarta.mail" % "2.0.1" )
Maven
sesv2
https://mvnrepository.com/artifact/software.amazon.awssdk/sesv2
jakarta.mail
https://mvnrepository.com/artifact/com.sun.mail/jakarta.mail
【3】ハマりポイント
1)AWS JDK for Java (SES) が色々ある
* 1)AWS JDK for Java v1(SES) と v2 (SES/SESv2) がある => こんなん、わかるかい、、、
com.amazonaws / aws-java-sdk-ses
https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk-ses
software.amazon.awssdk / SES
https://mvnrepository.com/artifact/software.amazon.awssdk/ses
software.amazon.awssdk / SESv2
https://mvnrepository.com/artifact/software.amazon.awssdk/sesv2
2)jakarta.mail の import が変わる
* jakarta.mail (以前の javax.mail / com.sun.mail)の import が v1.6 -> v2.0で以下のように変わる => ネットの情報は、大抵、v1.6なので、ここでもハマる
https://stackoverflow.com/questions/68955884/update-jakarta-mail-1-6-5-to-2-0-1
v1.6
import javax.mail.internet.MimeMessage
v2.0
import jakarta.mail.internet.MimeMessage
【4】サンプル
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider import software.amazon.awssdk.core.SdkBytes import software.amazon.awssdk.regions.Region import software.amazon.awssdk.services.sesv2.model.RawMessage import software.amazon.awssdk.services.sesv2.SesV2Client import software.amazon.awssdk.services.sesv2.model.EmailContent import software.amazon.awssdk.services.sesv2.model.SendEmailRequest import software.amazon.awssdk.services.sesv2.model.SesV2Exception import jakarta.activation.DataHandler import jakarta.activation.DataSource import jakarta.mail.Message 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 java.io.ByteArrayOutputStream import java.io.IOException import java.nio.ByteBuffer import java.nio.file.Files import java.util.Properties import java.net.URI case class SesUtils(region: Region = Region.US_WEST_1, isDev: Boolean = false) { val DEFAULT_CHAR_CODE = "UTF-8" val sesClient = createClient(region, isDev) private def createClient( region: Region = Region.US_WEST_1, isDev: Boolean = false ): SesV2Client = { if (isDev) { val accessKey = "dummy" val secretAccessKey = "dummy" val endpoint = "http://localhost:4566" val credentials = AwsBasicCredentials.create(accessKey, secretAccessKey) SesV2Client.builder() .region(region) .credentialsProvider(StaticCredentialsProvider.create(credentials)) .endpointOverride(new URI(endpoint)) .build() } else { SesV2Client.builder() .region(region) .credentialsProvider(ProfileCredentialsProvider.create()) .build() } } def sendEmail( mailFrom: String, mailAddressList: Seq[String], mailTitle: String, mailBody: String, mailFileLocation: Option[String] = None ): Unit = { val mimeMessage = generateMimeMessage() setMailFrom(mimeMessage, mailFrom) setMailTo(mimeMessage, mailAddressList) setMailTitle(mimeMessage, mailTitle) val mimeMultipartForMixed = setMailBody(mimeMessage, mailBody) if (mailFileLocation.isDefined) { setMailAttachment(mimeMessage, mailFileLocation.get, mimeMultipartForMixed) } val request = createRequest(mimeMessage) sesClient.sendEmail(request) } def generateMimeMessage(): MimeMessage = { val session = Session.getDefaultInstance(new Properties()) new MimeMessage(session) } def setMailFrom(mimeMessage: MimeMessage, mailFrom: String): Unit = { mimeMessage.setFrom(new InternetAddress(mailFrom)) } def setMailTo( mimeMessage: MimeMessage, mailAddressList: Seq[String], mailRecipientType: Message.RecipientType = Message.RecipientType.TO ): Unit = { mailAddressList.foreach(mailAddress => mimeMessage.addRecipient( mailRecipientType, new InternetAddress(mailAddress) ) ) } def setMailTitle(mimeMessage: MimeMessage, mailTitle: String): Unit = { mimeMessage.setSubject(mailTitle, DEFAULT_CHAR_CODE) } def setMailBody(mimeMessage: MimeMessage, mailBody: String): MimeMultipart = { // Define the HTML part. val mimeBodyPartForHtml = new MimeBodyPart(); mimeBodyPartForHtml.setContent( mailBody, s"text/html; charset=${DEFAULT_CHAR_CODE}") // Create a multipart/alternative child container. val mimeMultipartForBody = new MimeMultipart("alternative") // Add the HTML parts to the child container. mimeMultipartForBody.addBodyPart(mimeBodyPartForHtml); // Create a wrapper for the HTML and text parts. val mimeBodyPart = new MimeBodyPart() // Add the child container to the wrapper object. mimeBodyPart.setContent(mimeMultipartForBody) // Create a multipart/mixed parent container. val mimeMultipartForMixed = new MimeMultipart("mixed") // Add the parent container to the message. mimeMessage.setContent(mimeMultipartForMixed) // Add the multipart/alternative part to the message. mimeMultipartForMixed.addBodyPart(mimeBodyPart) mimeMultipartForMixed } def setMailAttachment( mimeMessage: MimeMessage, mailFileLocation: String, mimeMultipartForMixed: MimeMultipart, mailAttachmentFileName: String = "test.zip", mailMimeType: String = "application/zip" ): Unit = { val attachmentFile = new java.io.File(mailFileLocation); val fileContentByArrayByte = Files.readAllBytes(attachmentFile.toPath()); // https://spring.pleiades.io/specifications/platform/8/apidocs/javax/mail/util/bytearraydatasource val dataSourceForAttachment = new ByteArrayDataSource( fileContentByArrayByte, mailMimeType ) // Define the attachment. val mimeBodyPartForAttachment = new MimeBodyPart() mimeBodyPartForAttachment.setDataHandler( new DataHandler(dataSourceForAttachment) ) mimeBodyPartForAttachment.setFileName(mailAttachmentFileName) // Add the attachment to the message. mimeMultipartForMixed.addBodyPart(mimeBodyPartForAttachment) } def createRequest(mimeMessage: MimeMessage): SendEmailRequest = { val outputStream = new ByteArrayOutputStream(); mimeMessage.writeTo(outputStream); val outputStreamBuffer = ByteBuffer.wrap(outputStream.toByteArray()) val outputStreamBufferAsByteArray = new Array[Byte](outputStreamBuffer.remaining) outputStreamBuffer.get(outputStreamBufferAsByteArray) val messageData = SdkBytes.fromByteArray(outputStreamBufferAsByteArray) val rawMessage = RawMessage.builder() .data(messageData) .build() val emailContent = EmailContent.builder() .raw(rawMessage) .build() SendEmailRequest.builder() .content(emailContent) .build() } }
補足:デバッグ版
* Localstack で試そうと思ったが、以下のエラー、、、 ~~~~~ [error] (Compile / run) software.amazon.awssdk.services.sesv2.model.SesV2Exception: API action 'SendEmail' for service 'sesv2' not yet implemented or pro feature - check https://docs.localstack.cloud/user-guide/aws/feature -coverage for further information (Service: SesV2, Status Code: 501, Request ID: xxxx) ~~~~~
https://docs.localstack.cloud/user-guide/aws/feature
Simple Storage Service (SES) v2 (Pro) ... 有料版でしか対応していないっぽい、、、
参考文献
https://qiita.com/an_riri88/items/525ea900e5474eeef295
https://qiita.com/bicep_sasa/items/618c8ec728677c625afa
関連記事
Scala ~ 環境構築編 ~
https://dk521123.hatenablog.com/entry/2023/03/10/193805
Scala ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2023/03/12/184331
Scala ~ JSON ~
https://dk521123.hatenablog.com/entry/2023/04/04/000733
Scala ~ AWS SDK ~
https://dk521123.hatenablog.com/entry/2023/03/24/211033
Scala ~ AWS SDK / S3サンプル ~
https://dk521123.hatenablog.com/entry/2023/04/01/002005
Scala ~ AWS SDK / Secrets Managerサンプル ~
https://dk521123.hatenablog.com/entry/2023/04/03/012600
Amazon SES ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2017/04/28/234103
JavaでEmail ~ JavaMail / Text ~
https://dk521123.hatenablog.com/entry/2016/07/16/222422
JavaでEmail ~ JavaMail / 添付ファイル ~
https://dk521123.hatenablog.com/entry/2016/07/17/023459
JavaでEmail ~ SMTP認証 ~
https://dk521123.hatenablog.com/entry/2016/11/07/215251
JavaでEmail ~ SMTP認証 / DIGEST-MD5 ~
https://dk521123.hatenablog.com/entry/2016/12/07/222229
JavaでEmail ~ JavaMail / TLS ~
https://dk521123.hatenablog.com/entry/2017/05/03/163219
JavaでEmail ~ JavaMail / Return-Path・Errors-To ~
https://dk521123.hatenablog.com/entry/2017/05/07/000344
Docker compose ~ LocalStack/Glue4.0 ~
https://dk521123.hatenablog.com/entry/2023/03/25/021432