공공부하자개발 · 영어 학습 노트
자바
실무 확장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정리‹ 이전다음 ›

3. 코드 예제

실행 방법입니다. 외부 jar 는 필요 없습니다.

cmd
cd java-src\extension\19_mail
javac -encoding UTF-8 *.java
java -Dstdout.encoding=UTF-8 Main

SmtpClient.send — EHLO~QUIT 대화와 실패 분류

java
String rcptResponse = command(out, in, "RCPT TO:<" + to + ">", -1);
int code = Integer.parseInt(rcptResponse.substring(0, 3));
if (code >= 500) {
    out.print("QUIT\r\n"); out.flush();
    throw new PermanentFailureException(to + " 영구 실패: " + rcptResponse);
}
if (code >= 400) {
    out.print("QUIT\r\n"); out.flush();
    throw new TemporaryFailureException(to + " 일시 실패: " + rcptResponse);
}

응답 코드 첫 자리만 보고 즉시 갈립니다. 500 이상은 PermanentFailureException, 400 이상은 TemporaryFailureException 으로 구분해 호출부가 재시도 여부를 스스로 정하게 합니다.

제목 인코딩과 알림 억제

java
// RFC 2047: 한글 제목을 base64 로 감싼다
private static String encodeSubject(String subject) {
    byte[] bytes = subject.getBytes(StandardCharsets.UTF_8);
    return "=?UTF-8?B?" + Base64.getEncoder().encodeToString(bytes) + "?=";
}

// 같은 오류 키는 창(WINDOW) 안이면 건너뛴다
private static void notifyAlert(Map<String, Long> lastAlertAt,
                                 String errorKey, String message) {
    long now = System.currentTimeMillis();
    Long last = lastAlertAt.get(errorKey);
    if (last != null && now - last < ALERT_WINDOW_MS) {
        System.out.println("억제(스킵): " + message);
        return;
    }
    lastAlertAt.put(errorKey, now);
    System.out.println("알림 발송: " + message);
}

데모 실행 결과

text
가짜 SMTP 서버 시작: 포트 52366

=== 데모 1: 첨부 포함 정상 발송 ===
발송 성공
서버 수신 헤더: Subject: =?UTF-8?B?OeyblCDsoJXsgrAg66as7Y+s7Yq4?=

=== 데모 2: 일시 실패(451) 재시도 후 포기 ===
1번째 시도 일시 실패: [email protected] 일시 실패: 451 ...
2번째 시도 일시 실패: ...
3번째 시도 일시 실패: ...
4번째 시도 일시 실패: ...
최대 재시도 초과, 포기하고 실패 처리

=== 데모 3: 영구 실패(550) 즉시 중단 ===
영구 실패로 즉시 중단: [email protected] 영구 실패: 550 ...

=== 데모 4: 멱등키로 중복 발송 방지 ===
1차 발송: 발송 완료, 키 등록(batch-2026-09:[email protected])
2차 발송(중복): 이미 보낸 키(batch-2026-09:[email protected]), 건너뜀

=== 데모 5: 알림 폭주 억제(같은 오류 5회 호출) ===
알림 발송: 주문 배치 타임아웃 발생 (1번째 감지) (메일 + 사내 메신저 웹훅)
억제(스킵): 주문 배치 타임아웃 발생 (2번째 감지)
억제(스킵): 주문 배치 타임아웃 발생 (3번째 감지)
억제(스킵): 주문 배치 타임아웃 발생 (4번째 감지)
억제(스킵): 주문 배치 타임아웃 발생 (5번째 감지)

가짜 SMTP 서버 종료

첫 시도부터 4번 연속 451 을 받고 포기하는 이유는 초기 시도 1회 + 재시도 3회(BACKOFF_MS.length 만큼)이기 때문입니다. 알림은 5번 호출 중 1번만 실제로 나가고 나머지 4번은 5분 창 안이라 억제됩니다.

예제 직접 실행

아래 폴더를 JDK 21 로 컴파일하고 실행합니다. 외부 jar 를 쓰는 레슨은 java-src/lib 를 클래스패스에 넣습니다.

cd java-src\extension\19_mail
javac -encoding UTF-8 *.java && java Main

:: 외부 jar 가 필요한 레슨
javac -encoding UTF-8 -cp "..\..\lib\*;." *.java && java -cp "..\..\lib\*;." Main
코드 예제
  • SmtpClient.send — EHLO~QUIT 대화와 실패 분류
  • 제목 인코딩과 알림 억제
  • 데모 실행 결과
이전 섹션2 핵심 원리3 / 7다음 섹션4 응용 변형 예제