공공부하자개발 · 영어 학습 노트
자바
실무 확장Excel · 파일 업로드 · DB 연동0/22 완료
  • 01Excel(XLSX) 구조와 순수 JDK로 읽기/쓰기
  • 02Apache POI로 Excel 업로드/다운로드
  • 03파일 업로드/다운로드 서버 (HttpServer)
  • 04JDBC 기초와 트랜잭션 (H2)
  • 05MyBatis 어노테이션 매퍼로 쿼리 연동
  • 06MyBatis XML 매퍼 · Oracle 방언 · PageHelper · Spring Boot
  • 07REST API 서버와 JSON
  • 08Vue 3 SPA 와 Java 서버 연동
  • 09@Scheduled 운영
  • 10로깅 실무: 레벨·계층, MDC 추적, 예외·성능, 마스킹, 롤링, JSON 로그
  • 11외부 API 연동
  • 12테스트 실무
  • 13암호화·개인정보 보호
  • 14인코딩·한글 실무
  • 15@Transactional 심화
  • 16긴 작업 비동기 처리와 진행률
  • 17SFTP·FTP 파일 연계
  • 18로컬 캐시와 @Cacheable
  • 19메일·알림 발송
  • 20웹 보안 체크리스트
  • 21빌드 도구와 폐쇄망 의존성 반입
  • 22성능 측정: p50·p95·p99, 측정 계층, JFR, JMH 함정, 자체 부하 테스트, 병목 순위
사이트 소개개인정보처리방침연락처
© 2026 공부하자
홈 › 실무 확장 › 19 / 22

메일·알림 발송

SMTP 흐름, MIME·한글 제목·첨부, 실패 분류·재시도, 중복 방지, 알림 폭주 억제
섹션 7진행 0 / 22
1왜 배우는가2핵심 원리3코드 예제4응용 변형 예제5자주 하는 실수 (Tip)6연습 문제7정리‹ 이전다음 ›

6. 연습 문제

문제 1. `SmtpClient` 에 재시도 헬퍼 `sendWithRetry` 추가

Main 의 데모 2 로직을 SmtpClient 안에 sendWithRetry(host, port, from, to, subject, body, attachName, attachBytes, maxRetries) 로 옮기세요. 4xx 는 maxRetries 만큼 재시도하고, 5xx 는 즉시 예외를 던지도록 합니다.

java
public static void sendWithRetry(String host, int port, String from, String to,
        String subject, String body, String attachName, byte[] attachBytes,
        int maxRetries) throws Exception {
    int attempt = 0;
    while (true) {
        attempt++;
        try {
            send(host, port, from, to, subject, body, attachName, attachBytes);
            return;
        } catch (TemporaryFailureException e) {
            if (attempt > maxRetries) {
                throw e;
            }
            Thread.sleep(10L * (1L << (attempt - 1))); // 10,20,40...
        }
    }
}

PermanentFailureException 은 catch 하지 않으므로 그대로 던져져 호출부가 즉시 중단합니다.

문제 2. `FakeSmtpServer` 에 수신자별 지연 응답 추가

fail-slow@ 로 끝나는 수신자는 RCPT TO 응답 전에 2.5초를 기다리도록 FakeSmtpServer.handleClient 를 수정하세요. SmtpClient 의 setSoTimeout(3000) 과 비교했을 때 타임아웃 경계를 확인하는 문제입니다.

java
} else if (line.startsWith("RCPT TO")) {
    if (line.contains("fail-slow@")) {
        try {
            Thread.sleep(2500);
        } catch (InterruptedException ignored) {
            Thread.currentThread().interrupt();
        }
    }
    // 이하 기존 분기 그대로

2.5초는 클라이언트 타임아웃(3초)보다 짧아 응답은 받지만 대량 발송 시 이런 지연이 누적되면 전체 처리 시간이 크게 늘어납니다.

문제 3. 알림 억제 창을 오류 키별로 다르게 설정

notifyAlert 가 모든 오류 키에 같은 5분 창을 쓰는 대신, Map<String, Long> windowByKey 를 받아 키마다 다른 억제 시간을 적용하도록 바꾸세요. 심각한 오류는 짧게, 사소한 경고는 길게 두는 정책을 흉내 냅니다.

java
private static void notifyAlert(Map<String, Long> lastAlertAt,
        Map<String, Long> windowByKey, String errorKey, String message) {
    long now = System.currentTimeMillis();
    long window = windowByKey.getOrDefault(errorKey, ALERT_WINDOW_MS);
    Long last = lastAlertAt.get(errorKey);
    if (last != null && now - last < window) {
        System.out.println("억제(스킵): " + message);
        return;
    }
    lastAlertAt.put(errorKey, now);
    System.out.println("알림 발송: " + message);
}

windowByKey 에 값이 없으면 기존 기본값(ALERT_WINDOW_MS)을 그대로 씁니다.

연습 문제
    이전 섹션5 자주 하는 실수 (Tip)6 / 7다음 섹션7 정리