주문, 프로모션 알림 기능 적용 과정 (Gmail SMTP, AWS SES)

늘보·2025년 4월 29일

Spring

목록 보기
23/24
post-thumbnail

📋알림의 전체적인 흐름 플로우 차트

[주문 완료 시 알림 전송]

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


[프로모션 알림 전송]

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


알림 허용

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

erd 작성

api 명세서 작성

true, false입력을 통해 동의 및 거부를 구분


알림 전송 Google SMTP

💡 SMTPSimple Mail Transfer Protocol의 약자

➡️ 인터넷을 통해 이메일 메시지를 보내고 받는데 사용되는 통신 프로토콜이다.

SMTP란 무엇인가요?


Gmail SMTP

메일을 작성하여 보내면 SMTP 서버에 전송되고 해당 서버에서 SendMail 프로그램을 구동하여 해당 메일 주소로 메일을 보내게 된다.

[Gmail SMTP 사용을 위한 준비 과정]

  • 계정관리 들어가기

  • 2단계 인증 진행

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

  • 앱 만들기

  • 앱 비밀번호 발급 완료

Gmail 코드 작성 과정

🟢 의존성 추가

// 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 SMTP의 아쉬웠던 점

Gmail의 하루 최대 메일 수

🚨 Gmail은 하루 메일 발송 수에 제한이 있어 대량 메일 테스트에 제약이 있었다.

주문 관련 메일의 경우 발송량이 많지 않지만 프로모션 메일은 전체 사용자에게 발송해야 하므로 대량 발송 테스트가 필요했다.

➡️ 이에 따라, 발송 제한이 적은 AWS SES를 도입하기로 결정했다.


💡 Gmail은 사용 방법이 간단한 장점이 있기 때문에 소규모 서비스나 테스트 단계에서 사용하기에 좋을 것 같다.



알림 전송 AWS SES

💡 ses 신청 관련 영상

AWS SES

[AWS SES 사용을 위한 준비 과정]

1. SES 시작하기

  • 접속 후 시작하기 클릭

  • 이메일 입력

  • 도메인 입력

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

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


  • 전송 도메인 입력


2. 자격 증명 단계

1️⃣ 이메일 인증

2️⃣ 도메인 인증

3️⃣ 프로덕션 인증

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


  • 승인 메일

AWS로부터 이메일이 오면 사용 목적을 명확히 답변
➡️ 승인이 완료되어야 정식 발송 가능


3. IAM 사용자 생성 및 자격 증명 발급

SES 사용을 위한 IAM 사용자 생성

Access Key / Secret Key 발급
➡️ 코드에서 SES 클라이언트를 구성할 때 사용


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 VS AWS SES

항목Gmail SMTPAWS SES
사용 용이성쉬움다소 복잡
일일 발송 제한⭕ (500건 내외)❌ (Production 전환 시 수만 건 가능)
적합한 용도 테스트 / 소규모 알림대량 발송 / 프로모션 메일
보안 설정 2단계 인증 및 앱 비밀번호IAM 사용자 + 키 관리 필요
profile
누워만 있지 말고 제발 뭐라도 하자.

0개의 댓글