Skip to content

Commit 115bfab

Browse files
committed
feat: 이메일 전송 이력 기능
1 parent ed3a641 commit 115bfab

6 files changed

Lines changed: 171 additions & 6 deletions

File tree

src/main/java/ceos/backend/infra/ses/AwsSESSendMailHandler.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import ceos.backend.global.common.dto.AwsSESMail;
66
import ceos.backend.global.common.dto.AwsSESPasswordMail;
77
import ceos.backend.global.common.dto.AwsSESRecruitMail;
8+
import ceos.backend.infra.ses.domain.EmailType;
89
import lombok.RequiredArgsConstructor;
910
import lombok.extern.slf4j.Slf4j;
1011
import org.springframework.context.event.EventListener;
@@ -26,22 +27,23 @@ public void handle(AwsSESMail awsSESMail) {
2627
final String SUBJECT =
2728
awsSESMailGenerator.generateApplicationMailSubject(awsSESMail.getGeneration());
2829
final Context CONTEXT = awsSESMailGenerator.generateApplicationMailContext(awsSESMail);
29-
awsSesUtils.singleEmailRequest(TO, SUBJECT, "sendApplicationMail", CONTEXT);
30+
awsSesUtils.singleEmailRequest(
31+
TO, SUBJECT, "sendApplicationMail", CONTEXT, EmailType.APPLICATION);
3032
}
3133

3234
@EventListener(AwsSESPasswordMail.class)
3335
public void handle(AwsSESPasswordMail awsSESPasswordMail) {
3436
final String TO = awsSESPasswordMail.getEmail();
3537
final String SUBJECT = awsSESMailGenerator.generatePasswordMailSubject();
3638
final Context CONTEXT = awsSESMailGenerator.generatePasswordMailContext(awsSESPasswordMail);
37-
awsSesUtils.singleEmailRequest(TO, SUBJECT, "sendPasswordMail", CONTEXT);
39+
awsSesUtils.singleEmailRequest(TO, SUBJECT, "sendPasswordMail", CONTEXT, EmailType.PASSWORD);
3840
}
3941

4042
@EventListener(AwsSESRecruitMail.class)
4143
public void handle(AwsSESRecruitMail awsSESRecruitMail) {
4244
final String TO = awsSESRecruitMail.getEmail();
4345
final String SUBJECT = awsSESMailGenerator.generateRecruitMailSubject();
4446
final Context CONTEXT = awsSESMailGenerator.generateRecruitMailContext(awsSESRecruitMail);
45-
awsSesUtils.singleEmailRequest(TO, SUBJECT, "sendRecruitMail", CONTEXT);
47+
awsSesUtils.singleEmailRequest(TO, SUBJECT, "sendRecruitMail", CONTEXT, EmailType.RECRUIT);
4648
}
4749
}
Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,42 @@
11
package ceos.backend.infra.ses;
22

33

4+
import ceos.backend.infra.ses.domain.EmailSendHistory;
5+
import ceos.backend.infra.ses.domain.EmailType;
6+
import ceos.backend.infra.ses.repository.EmailSendHistoryRepository;
47
import lombok.RequiredArgsConstructor;
8+
import lombok.extern.slf4j.Slf4j;
59
import org.springframework.stereotype.Component;
610
import org.thymeleaf.context.Context;
711
import org.thymeleaf.spring6.SpringTemplateEngine;
812
import software.amazon.awssdk.services.ses.SesAsyncClient;
913
import software.amazon.awssdk.services.ses.model.*;
1014

15+
import java.util.concurrent.CompletableFuture;
16+
17+
@Slf4j
1118
@Component
1219
@RequiredArgsConstructor
1320
public class AwsSESUtils {
1421
private final SesAsyncClient sesAsyncClient;
1522
private final SpringTemplateEngine templateEngine;
23+
private final EmailSendHistoryRepository emailSendHistoryRepository;
1624

17-
public void singleEmailRequest(String to, String subject, String template, Context context) {
25+
public void singleEmailRequest(
26+
String to, String subject, String template, Context context, EmailType emailType) {
1827
final String html = templateEngine.process(template, context);
1928

2029
final SendEmailRequest.Builder sendEmailRequestBuilder = SendEmailRequest.builder();
2130
sendEmailRequestBuilder.destination(Destination.builder().toAddresses(to).build());
22-
sendEmailRequestBuilder
31+
32+
SendEmailRequest request = sendEmailRequestBuilder
2333
.message(newMessage(subject, html))
2434
.source("ceos@ceos-sinchon.com")
2535
.build();
2636

27-
sesAsyncClient.sendEmail(sendEmailRequestBuilder.build());
37+
CompletableFuture<SendEmailResponse> future = sesAsyncClient.sendEmail(request);
38+
39+
saveHistory(to, subject, template, emailType, future);
2840
}
2941

3042
private Message newMessage(String subject, String html) {
@@ -34,4 +46,41 @@ private Message newMessage(String subject, String html) {
3446
.body(Body.builder().html(builder -> builder.data(html)).build())
3547
.build();
3648
}
49+
50+
private void saveHistory(String to, String subject, String template, EmailType emailType, CompletableFuture<SendEmailResponse> future) {
51+
future.whenComplete(
52+
(response, exception) -> {
53+
if (exception != null) {
54+
log.error("Failed to send email to: {}", to, exception);
55+
saveFailureHistory(to, subject, template, emailType, exception);
56+
} else {
57+
log.info("Successfully sent email to: {}, messageId: {}", to, response.messageId());
58+
saveSuccessHistory(to, subject, template, emailType, response.messageId());
59+
}
60+
});
61+
}
62+
63+
private void saveSuccessHistory(
64+
String to, String subject, String template, EmailType emailType, String messageId) {
65+
try {
66+
EmailSendHistory history =
67+
EmailSendHistory.createSuccess(to, subject, template, emailType, messageId);
68+
emailSendHistoryRepository.save(history);
69+
} catch (Exception e) {
70+
log.error("Failed to save email send success history", e);
71+
}
72+
}
73+
74+
private void saveFailureHistory(
75+
String to, String subject, String template, EmailType emailType, Throwable exception) {
76+
try {
77+
EmailSendHistory history =
78+
EmailSendHistory.createFailure(
79+
to, subject, template, emailType, exception.getMessage());
80+
emailSendHistoryRepository.save(history);
81+
} catch (Exception e) {
82+
log.error("Failed to save email send failure history", e);
83+
}
84+
}
85+
3786
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package ceos.backend.infra.ses.domain;
2+
3+
4+
import ceos.backend.global.common.entity.BaseEntity;
5+
import jakarta.persistence.*;
6+
import lombok.AccessLevel;
7+
import lombok.Builder;
8+
import lombok.Getter;
9+
import lombok.NoArgsConstructor;
10+
11+
@Entity
12+
@Getter
13+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
14+
@Table(name = "email_send_history")
15+
public class EmailSendHistory extends BaseEntity {
16+
17+
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
18+
private Long id;
19+
20+
@Column(name = "recipient_email")
21+
private String recipientEmail;
22+
23+
@Column(name = "subject")
24+
private String subject;
25+
26+
@Column(name = "template_name")
27+
private String templateName;
28+
29+
@Enumerated(EnumType.STRING)
30+
@Column(name = "email_type")
31+
private EmailType emailType;
32+
33+
@Enumerated(EnumType.STRING)
34+
@Column(name = "send_status")
35+
private SendStatus sendStatus;
36+
37+
@Column(name = "message_id", length = 255)
38+
private String messageId;
39+
40+
@Column(name = "error_message", length = 1000)
41+
private String errorMessage;
42+
43+
@Builder(access = AccessLevel.PRIVATE)
44+
private EmailSendHistory(
45+
String recipientEmail,
46+
String subject,
47+
String templateName,
48+
EmailType emailType,
49+
SendStatus sendStatus,
50+
String messageId,
51+
String errorMessage) {
52+
this.recipientEmail = recipientEmail;
53+
this.subject = subject;
54+
this.templateName = templateName;
55+
this.emailType = emailType;
56+
this.sendStatus = sendStatus;
57+
this.messageId = messageId;
58+
this.errorMessage = errorMessage;
59+
}
60+
61+
public static EmailSendHistory createSuccess(
62+
String recipientEmail,
63+
String subject,
64+
String templateName,
65+
EmailType emailType,
66+
String messageId) {
67+
return EmailSendHistory.builder()
68+
.recipientEmail(recipientEmail)
69+
.subject(subject)
70+
.templateName(templateName)
71+
.emailType(emailType)
72+
.sendStatus(SendStatus.SUCCESS)
73+
.messageId(messageId)
74+
.build();
75+
}
76+
77+
public static EmailSendHistory createFailure(
78+
String recipientEmail,
79+
String subject,
80+
String templateName,
81+
EmailType emailType,
82+
String errorMessage) {
83+
return EmailSendHistory.builder()
84+
.recipientEmail(recipientEmail)
85+
.subject(subject)
86+
.templateName(templateName)
87+
.emailType(emailType)
88+
.sendStatus(SendStatus.FAILURE)
89+
.errorMessage(errorMessage)
90+
.build();
91+
}
92+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package ceos.backend.infra.ses.domain;
2+
3+
4+
public enum EmailType {
5+
APPLICATION, // 지원서 접수 확인
6+
PASSWORD, // 임시 비밀번호 발급
7+
RECRUIT // 리크루팅 안내
8+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package ceos.backend.infra.ses.domain;
2+
3+
4+
public enum SendStatus {
5+
SUCCESS, // 전송 성공
6+
FAILURE // 전송 실패
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package ceos.backend.infra.ses.repository;
2+
3+
4+
import ceos.backend.infra.ses.domain.EmailSendHistory;
5+
import org.springframework.data.jpa.repository.JpaRepository;
6+
7+
public interface EmailSendHistoryRepository extends JpaRepository<EmailSendHistory, Long> {}

0 commit comments

Comments
 (0)