email callback 처리 정리

박화랑·2025년 7월 24일

Spring_6기

목록 보기
31/32

API 요청이 실패했을 때 아무런 안내 없이 사용자에게 500 에러를 던지는 건 친절하지 않다.
실제 요청한 사용자의 이메일로 실패 이유를 직접 안내하는 구조를 아래와 같이 구현했다.


인증 사용자 기반 이메일 전송 구조

컨트롤러 예시

@GetMapping("/brave/search")
public ResponseEntity<ApiResponse<BraveSearchResponseDto>> searchBrave(
    @RequestParam String query,
    @AuthenticationPrincipal CustomUserPrincipal customUserPrincipal) {

    String email = customUserPrincipal.getEmail();
    BraveSearchResponseDto result = braveSearchService.search(query, email);
    return ResponseEntity.ok(ApiResponse.ok(result));
}

@AuthenticationPrincipal을 통해 로그인한 사용자의 이메일을 추출하고,
그 이메일을 BraveSearchService.search(query, email)에 전달한다.


💣 BraveSearchService - 실패 시 사용자 이메일로 알림

public BraveSearchResponseDto search(String query, String email) {
    HttpHeaders headers = new HttpHeaders();
    headers.set("Accept", "application/json");
    headers.set("X-Subscription-Token", braveSearchProperties.getNextKey());

    HttpEntity<Void> requestEntity = new HttpEntity<>(headers);
    String url = braveSearchProperties.getUrl() + "?q=" + query + "&count=5";

    try {
        ResponseEntity<BraveSearchResponseDto> response = restTemplate.exchange(
                url, HttpMethod.GET, requestEntity, BraveSearchResponseDto.class);
        return response.getBody();
    } catch (Exception e) {
        notifyCallback(email, "Brave Search 요청 실패: " + e.getMessage());
        throw e;
    }
}

실패 알림 콜백 전송 메서드

private void notifyCallback(String email, String reason) {
    Map<String, String> body = Map.of("email", email, "reason", reason);

    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);

    HttpEntity<Map<String, String>> request = new HttpEntity<>(body, headers);

    try {
        restTemplate.postForEntity("http://localhost:8081/internal/failure-callback", request, Void.class);
    } catch (Exception ex) {
        System.err.println("콜백 전송 실패: " + ex.getMessage());
    }
}

콜백 수신 서버 구성

콜백 요청 수신 컨트롤러

@RestController
@RequestMapping("/internal")
public class CallbackController {

    private final CallbackMailService callbackMailService;

    @PostMapping("/failure-callback")
    public ResponseEntity<Void> receiveFailureCallback(@RequestBody FailureCallbackRequest request) {
        callbackMailService.sendFailureEmail(request.email(), request.reason());
        return ResponseEntity.ok().build();
    }
}

콜백 Request record

public record FailureCallbackRequest(String email, String reason) {}

사용자 이메일로 실패 안내 전송

@Service
@RequiredArgsConstructor
public class CallbackMailService {

    private final JavaMailSender mailSender;

    public void sendFailureEmail(String to, String reason) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setTo(to);
        message.setSubject("[DevMountain] 요청 실패 안내");
        message.setText(reason);
        mailSender.send(message);
    }
}

이메일 전송 설정 (application.yml)

spring:
  mail:
    host: smtp.gmail.com
    port: 587
    username: your-email@gmail.com
    password: your-app-password
    properties:
      mail.smtp.auth: true
      mail.smtp.starttls.enable: true

✅ 마무리 요약

항목내용
실패 감지 위치BraveSearchService에서 직접 감지
알림 대상요청을 보낸 인증 사용자
메일 전송 시점API 호출 실패 시 즉시 콜백
포맷사용자 이메일 + 실패 사유 전송
장점사용자 입장에서 무응답보다 훨씬 낫고, 시스템 신뢰도 향상

실제 적용 예시 흐름

  1. 로그인된 사용자 A가 /brave/search?q=Spring 강의 요청
  2. BraveSearchService에서 Brave API 호출 실패
  3. Callback 서버로 A의 이메일과 실패 사유 전달
  4. A에게 "[DevMountain] 요청 실패 안내" 메일 전송

이렇게 구성하면 서비스 운영 신뢰도가 확실히 높아진다. 피드백 내용에 맞춰 실패할 경우에 예외 처리 사항으로 구현하면 될 듯 하다. 지금까지 구현을 중심으로만 생각한 자신에게 좀 더 생각해 볼 여지를 만들 수 있었다. 기본적인 기능이지만 사용자 중심 대응으로 매우 유의미한 개선이다.

profile
개발자 희망생

0개의 댓글