
[주문 완료 시 알림 전송]
➡️ 주문자의 이메일로 알림 전송 요청
➡️ 수신 동의 여부 확인
➡️ 수신 동의한 경우에만 알림 전송

[프로모션 알림 전송]
➡️ 수신 동의한 모든 유저에게 알림 전송 요청
➡️ 전체 유저 중 수신 동의한 대상에게 알림 전송

알림 허용 버튼 클릭 후 확인을 누르면 수신 동의



true, false입력을 통해 동의 및 거부를 구분
💡
SMTP는 Simple Mail Transfer Protocol의 약자➡️ 인터넷을 통해 이메일 메시지를 보내고 받는데 사용되는 통신 프로토콜이다.
메일을 작성하여 보내면 SMTP 서버에 전송되고 해당 서버에서 SendMail 프로그램을 구동하여 해당 메일 주소로 메일을 보내게 된다.
[Gmail SMTP 사용을 위한 준비 과정]
계정관리 들어가기

2단계 인증 진행

앱 검색을 통해 앱 비밀번호 클릭 


🟢 의존성 추가
// SMTP
implementation 'org.springframework.boot:spring-boot-starter-mail'
🟢 yml에 추가
spring:
mail:
username: ${SMTP_USERNAME} //SMTP 설정을 완료한 gmail 아이디
password: ${SMTP_PASSWORD} //SMTP 설정 시 발급 받은 16자리의 비밀번호
🟢 서비스 구현
@Service
@RequiredArgsConstructor
public class NotificationService {
private MailSender mailSender;
public void sendOrderMail(AuthUser authUser, String orderNumber, BigDecimal totalPrice) {
//텍스트로 메일 보내기
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
try {
//메일을 받을 수신자 설정
simpleMailMessage.setTo(authUser.getEmail());
//메일 제목
simpleMailMessage.setSubject("주문이 완료되었습니다.");
//메일 내용
simpleMailMessage.setText(
"주문 정보\n" +
"-------------------------\n" +
"주문 번호: " + orderNumber + "\n" +
"총 가격: " + totalPrice + "\n"
);
//메일 전송
mailSender.send(simpleMailMessage);
} catch (Exception e) {
throw new RuntimeException(FAILED_SEND_MAIL.getMessage());
}
}
}
/* 주문 생성 */
@Transactional
public OrderResponse createOrder(AuthUser authUser, Pageable pageable) {
Cart cart = cartService.findByUserIdOrElseNewCart(authUser.getUserId());
if (cart.getItems().isEmpty()) {
throw new NotFoundException(NOT_EXIST_SHOPPING_CART.getMessage());
}
Map<Long, CartItem> cartItemMap = cart.getItems();
decreaseStocks(cartItemMap);
BigDecimal totalPrice = calculateTotalPrice(cartItemMap);
Order order = Order.of(Users.fromAuthUser(authUser), totalPrice);
orderRepository.save(order);
orderHistoryService.saveOrderHistory(cartItemMap, order);
cartService.clearCart(authUser);
Users users = userService.findByIdOrElseThrow(authUser.getUserId());
//알림 수신 동의인 경우
if (users.isNotificationAgreed()) {
//메일 발송
notificationService.sendOrderMail(authUser, order.getNumber(), order.getTotalPrice());
}
return getOrderResponse(order, pageable);
}
✅ Gmail의 하루 최대 메일 수

🚨 Gmail은 하루 메일 발송 수에 제한이 있어 대량 메일 테스트에 제약이 있었다.
주문 관련 메일의 경우 발송량이 많지 않지만 프로모션 메일은 전체 사용자에게 발송해야 하므로 대량 발송 테스트가 필요했다.
➡️ 이에 따라, 발송 제한이 적은 AWS SES를 도입하기로 결정했다.
💡 Gmail은 사용 방법이 간단한 장점이 있기 때문에 소규모 서비스나 테스트 단계에서 사용하기에 좋을 것 같다.
[AWS SES 사용을 위한 준비 과정]


도메인이 없다면 Route53을 통해 도메인을 구매할 수 있다.



➡️ 결제 진행을 누른 후 연락처 정보, 검토 및 제출 과정을 거친다. (소요시간 약 30분 걸림)

1️⃣ 이메일 인증
2️⃣ 도메인 인증
3️⃣ 프로덕션 인증

🚨 프로덕션을 신청하지 않으면 기본적으로 Sandbox로 설정되어 제한이 생긴다. (하루 200건만 가능)

AWS로부터 이메일이 오면 사용 목적을 명확히 답변
➡️ 승인이 완료되어야 정식 발송 가능
SES 사용을 위한 IAM 사용자 생성
Access Key / Secret Key 발급
➡️ 코드에서 SES 클라이언트를 구성할 때 사용
🟢 의존성 추가
// SES
implementation("software.amazon.awssdk:ses:2.31.30")
💡 참고 사이트
🟢 yml에 추가
cloud:
aws:
region: ${AWS_REGION}
ses:
access-key: ${AWS_SES_ACCESS_KEY}
secret-key: ${AWS_SES_SECRET_KEY}
🟢 Config
@Configuration
public class SesConfig {
@Value("${cloud.aws.ses.access-key}")
private String accessKey;
@Value("${cloud.aws.ses.secret-key}")
private String secretKey;
@Value("${AWS_REGION}")
private String region;
@Bean
public SesClient sesClient () {
AwsBasicCredentials awsBasicCredentials = AwsBasicCredentials.create(accessKey, secretKey);
return SesClient.builder()
.credentialsProvider(StaticCredentialsProvider.create(awsBasicCredentials))
.region(Region.of(region))
.build();
}
}
AwsBasicCredentials awsBasicCredentials = AwsBasicCredentials.create(accessKey, secretKey);
AWS 접근을 위한 인증 객체 생성 (IAM Access Key와 Secret Key를 기반)
➡️ AWS API에 요청할 때 "내가 누군지" 인증하기 위한 준비
.credentialsProvider(StaticCredentialsProvider.create(awsBasicCredentials))
SES Client가 사용할 인증 정보를 고정된 방식으로 설정
➡️ 매번 SES Client가 위에서 생성한 고정된 자격 증명 객체(awsBasicCredentials)를 사용하도록 설정한다는 의미
.region(Region.of(region))
이메일 발송 지역 설정
🟢 발송할 메일 Request 구현
public record SendRequest(
String from,
String to,
String subject,
String content
) {
@Builder
public SendRequest {
}
public SendEmailRequest toSendEmailRequest() {
// 수신자 이메일 주소 설정
Destination destination = Destination.builder()
.toAddresses(this.to())
.build();
// 이메일 제목과 본문 설정
Message message = Message.builder()
.subject(createContent(this.subject()))
.body(Body.builder().html(createContent(this.content())).build())
.build();
//최종 SendEmailRequest 객체 생성
return SendEmailRequest.builder()
.source(this.from())
.destination(destination)
.message(message)
.build();
}
private Content createContent(String text) {
return Content.builder()
.charset("UTF-8")
.data(text)
.build();
}
}
🟢 서비스 구현
@Service
@RequiredArgsConstructor
public class NotificationSesService {
private final SesClient sesClient;
private final Environment env;
public void sendPromotionMail(AuthUser authUser, String orderNumber, BigDecimal price, String email) throws InterruptedException {
SendRequest sendRequest = SendRequest.builder()
.from(env.getProperty("spring.mail.username")) // 보낼 메일 입력
.subject("주문이 완료되었습니다.")
.to(email)
.content("주문 정보\n" +
"-------------------------\n" +
"닉네임: " + authUser.getNickname() + "\n" +
"주문 번호: " + orderNumber + "\n" +
"총 가격: " + price + "\n")
.build();
//메일 발송
sesClient.sendEmail(sendRequest.toSendEmailRequest());
}
| 항목 | Gmail SMTP | AWS SES |
|---|---|---|
| 사용 용이성 | 쉬움 | 다소 복잡 |
| 일일 발송 제한 | ⭕ (500건 내외) | ❌ (Production 전환 시 수만 건 가능) |
| 적합한 용도 | 테스트 / 소규모 알림 | 대량 발송 / 프로모션 메일 |
| 보안 설정 | 2단계 인증 및 앱 비밀번호 | IAM 사용자 + 키 관리 필요 |