Main 의 데모 2 로직을 SmtpClient 안에 sendWithRetry(host, port, from, to, subject, body, attachName, attachBytes, maxRetries) 로 옮기세요. 4xx 는 maxRetries 만큼 재시도하고, 5xx 는 즉시 예외를 던지도록 합니다.
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 하지 않으므로 그대로 던져져 호출부가 즉시 중단합니다.
fail-slow@ 로 끝나는 수신자는 RCPT TO 응답 전에 2.5초를 기다리도록 FakeSmtpServer.handleClient 를 수정하세요. SmtpClient 의 setSoTimeout(3000) 과 비교했을 때 타임아웃 경계를 확인하는 문제입니다.
} else if (line.startsWith("RCPT TO")) {
if (line.contains("fail-slow@")) {
try {
Thread.sleep(2500);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
}
// 이하 기존 분기 그대로2.5초는 클라이언트 타임아웃(3초)보다 짧아 응답은 받지만 대량 발송 시 이런 지연이 누적되면 전체 처리 시간이 크게 늘어납니다.
notifyAlert 가 모든 오류 키에 같은 5분 창을 쓰는 대신, Map<String, Long> windowByKey 를 받아 키마다 다른 억제 시간을 적용하도록 바꾸세요. 심각한 오류는 짧게, 사소한 경고는 길게 두는 정책을 흉내 냅니다.
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)을 그대로 씁니다.