포폴/일정관리앱

이메일 전송에 대한 후처리

slown 2025. 5. 17. 15:36

목차

1.문제점

2.해결 전략 – 후처리 아키텍처 도입

3.적용 구조 및 구현

4.후기

 

1.문제점

회원가입 시 인증 메일을 전송하거나, 각종 알림 메일을 발송하는 기능은 대부분의 서비스에서 필수입니다.  
하지만 실제 운영 중 다음과 같은 문제가 발생했습니다:

- 메일 서버의 일시적인 장애
- SMTP 인증 실패
- 네트워크 지연 및 타임아웃
- `MessagingException` 등의 예외 발생

이런 예외가 발생했을 때, 시스템이 아무런 후처리를 하지 않는다면 중요한 이메일이 유실되고, 사용자 입장에서는 인증/알림을 받지 못해 혼란을 겪게 됩니다.

결국 이러한 구조는 시스템 신뢰도 저하와 사용자 불만으로 이어질 수 있습니다.

2.해결 전략 – 후처리 아키텍처 도입

이메일 실패를 무시하지 않고, 시스템이 자동으로 복구를 시도하고, 운영자가 추적할 수 있도록 저장하는 구조가 필요하다고 판단했습니다.

목표
- 실패 시 재시도
- 최종 실패 시 DB에 이력 저장
- 정기적으로 재시도 수행
- 성공 시 처리 완료 플래그 갱신

3.적용 구조 및 구현

구현하고자 하는 구조는 아래와 같습니다.

 

 

3-1. 메일 전송 + Retry 처리

@Retryable(
    value = { MessagingException.class, RuntimeException.class },
    maxAttempts = 3,
    backoff = @Backoff(delay = 2000)
)
public void sendHtmlEmail(String to, String subject, String htmlContent) throws MessagingException {
    // 메일 전송 로직
}

 

3-2. 실패 후 DB 저장

@Recover
public void recover(MessagingException e, String to, String subject, String htmlContent) {
    FailEmailModel fail = FailEmailModel.builder()
        .toEmail(to)
        .subject(subject)
        .content(htmlContent)
        .resolved(false)
        .createdAt(LocalDateTime.now())
        .build();

    failEmailOutConnector.createFailEmail(fail);
}

 

3-3. 재처리 스케줄 처리.

@Scheduled(fixedDelay = 10_000)
public void retryFailedEmails() {
    List<FailEmailModel> fails = failEmailOutConnector.findUnresolved();
    for (FailEmailModel fail : fails) {
        try {
            emailService.sendHtmlEmail(fail.getToEmail(), fail.getSubject(), fail.getContent());
            fail.markResolved();
        } catch (Exception e) {
            log.warn("재시도 실패 - id={}, reason={}", fail.getId(), e.getMessage());
        }
    }
    failEmailOutConnector.saveAll(fails);
}

 

설계시 고려했던 포인트는 아래와 같습니다. 

 

  • resolved 플래그를 통해 무한 재시도 방지
  • 후처리 로직은 @Recover로 분리 → 코드 복잡도 낮춤
  • DB 이력 저장을 통해 장애 복구 후에도 재처리 가능
  • OutConnector 패턴을 통해 DB 접근 로직을 모듈화

4.후기

단순히 "이메일을 보냈다" 수준에서 벗어나, 실패를 감지하고 추적/복구하는 아키텍처로 전환을 했고, Retry + 후처리 조합으로 운영 복원력(Resilience) 확보했습니다.

4-1.향후 계획

  • 실패 이력을 관리자 페이지에서 시각화
  • Slack 알림 연동
  • Kafka 기반 메일 큐잉 구조 + Outbox 패턴으로 확장 예정입니다.