Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[YS-330] refactor: 이메일 전송 기능 AWS SES로 마이그레이션 #121

Merged
merged 9 commits into from
Feb 25, 2025
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
implementation("io.awspring.cloud:spring-cloud-starter-aws:2.4.4")
implementation("software.amazon.awssdk:s3:2.20.59")
implementation("software.amazon.awssdk:ses:2.20.100")
implementation("org.springframework.boot:spring-boot-starter-data-redis")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.3")
implementation("org.springframework.boot:spring-boot-starter-aop")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.dobby.backend.infrastructure.config

import com.dobby.backend.infrastructure.config.properties.SESProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider
import software.amazon.awssdk.regions.Region
import software.amazon.awssdk.services.ses.SesAsyncClient
import software.amazon.awssdk.services.ses.SesClient

@Configuration
class SESClientConfig(
private val sesProperties: SESProperties
) {
@Bean
fun sesAsyncClient(): SesAsyncClient {
return SesAsyncClient.builder()
.region(Region.of(sesProperties.region.static))
.credentialsProvider(
StaticCredentialsProvider.create(
AwsBasicCredentials.create(
sesProperties.credentials.accessKey,
sesProperties.credentials.secretKey
)
)
)
.build()
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.dobby.backend.infrastructure.config.properties

import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties(prefix = "aws.ses")
data class SESProperties (
val email: Email,
val region: Region,
val credentials: Credentials
) {
data class Email(
val sender: String
)

data class Region(
val static: String
)

data class Credentials(
val accessKey: String,
val secretKey: String
)
}
Original file line number Diff line number Diff line change
@@ -1,35 +1,47 @@
package com.dobby.backend.infrastructure.gateway.email

import com.dobby.backend.domain.gateway.email.EmailGateway
import com.dobby.backend.infrastructure.config.properties.EmailProperties
import com.dobby.backend.infrastructure.config.properties.SESProperties
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.future.await
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.springframework.mail.SimpleMailMessage
import org.springframework.mail.javamail.JavaMailSender
import org.springframework.mail.javamail.MimeMessageHelper
import org.springframework.stereotype.Component
import software.amazon.awssdk.regions.Region
import software.amazon.awssdk.services.ses.SesAsyncClient
import software.amazon.awssdk.services.ses.SesClient
import software.amazon.awssdk.services.ses.model.*

@Component
class EmailGatewayImpl(
private val emailProperties: EmailProperties,
private val mailSender: JavaMailSender
private val sesAsyncClient: SesAsyncClient,
private val sesProperties: SESProperties
) : EmailGateway {

override suspend fun sendEmail(to: String, subject: String, content: String, isHtml: Boolean) {
withContext(Dispatchers.IO) {
try {
val message = mailSender.createMimeMessage()
val helper = MimeMessageHelper(message, false, "UTF-8")
val body = if(isHtml) {
Body.builder().html(Content.builder().data(content).build()).build()
} else {
Body.builder().text(Content.builder().data(content).build()).build()
}

helper.setTo(to)
helper.setSubject(subject)
helper.setText(content, isHtml)
val request = SendEmailRequest.builder()
.source(sesProperties.email.sender)
.destination(Destination.builder().toAddresses(to).build())
.message(
Message.builder()
.subject(Content.builder().data(subject).build())
.body(body)
.build()
)
.build()

mailSender.send(message)
} catch (ex: Exception) {
throw IllegalStateException("이메일 발송 실패: ${ex.message}")
sesAsyncClient.sendEmail(request)
.whenComplete { _, ex ->
when(ex){
is SesException -> throw IllegalStateException("AWS SES 오류 발생: ${ex.awsErrorDetails()?.errorMessage()}")
is Exception -> throw IllegalStateException("이메일 전송 실패: ${ex.message}") }
}
}
}
}

21 changes: 10 additions & 11 deletions src/test/resources/application-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,6 @@ spring:
google:
client-id: "dummy-client-id"
client-secret: "dummy-client-secret"
mail:
host: smtp.example.com
port: 587
username: [email protected]
password: testpassword
properties:
mail:
smtp:
auth: true
starttls:
enable: true
data:
redis:
host: 127.0.0.1
Expand All @@ -58,5 +47,15 @@ cloud:
access-key: test
secret-key: test

aws:
ses:
email:
sender: test
region:
static: test
credentials:
access-key: test
secret-key: test

discord:
webhook-url: test
Loading