Skip to content

fix: 발신 주소 설정 필수화 - #134

Merged
yessjun merged 2 commits into
mainfrom
fix/mail-from-validation
Sep 17, 2026
Merged

yessjun merged 2 commits into
mainfrom
fix/mail-from-validation

Conversation

@yessjun

@yessjun yessjun commented Sep 17, 2026

Copy link
Copy Markdown
Member

📝 작업내용

  • pickle.mail.from${spring.mail.username} 폴백 제거, PICKLE_MAIL_FROM 필수화
  • 호스티드 발송 서비스의 SMTP 사용자 이름은 주소가 아니라 자격증명이므로, 폴백이 만드는 From은 항상 무효
  • 무효한 From의 결과는 알림 3회 재시도 후 FAILED, 재시도 없는 계정 메일은 유실
  • 빈 생성 시점 주소 파싱으로 기동 거부, 잘못된 값이 첫 발송까지 숨지 않음
  • strict 파싱이 도메인 없는 로컬 파트를 통과시키므로 validate() 추가
  • From을 문자열로 보관, 파싱은 MimeMessageHelper에 위임해 표시 이름 charset 재인코딩 유지
  • 예외 메시지에 값 대신 값의 모양만 기재
  • 거부된 값이 기동 저널에 남지 않도록 AddressException 연결 해제, 그 원인 메시지가 거부한 문자열을 인용
  • 로그 마스킹은 key=value 모양이라 인용된 단일 토큰을 걸러내지 못함
  • 메일 전송 연결, 읽기, 쓰기 타임아웃 각 10초 설정
  • JavaMail 기본 타임아웃은 무한, 알림 디스패처는 예정 스레드에서 배치를 순차 처리하므로 핸드셰이크 하나가 뒤의 알림 전체 정지
  • 계정 메일은 별도 2스레드 풀이고 정지 시 큐가 차면 거부, 헬스 지시자는 실제 연결을 열어 함께 정지
  • README.mdPICKLE_MAIL_FROM 행을 필수, 기본값 없음으로 정정
  • 같은 표의 _HOST, _USERNAME, _PASSWORD 행은 미실측이라 무수정

⭐️ 검증

  • 테스트 1554개 통과, 빌드와 jar 패키징
  • SmtpMailSenderTest 7건: 빈 값 거부, 도메인 없는 토큰 거부, 주소 둘 거부, 표시 이름 RFC 2047 인코딩
  • 거부 테스트 둘은 예외 종류만이 아니라 어느 분기가 거부했는지 단정, 하나는 메시지에 값이 없음도 단정
  • 독립 검토가 실제 컴파일된 클래스에 반사로 확인한 결과 연결된 원인이 값을 인용하던 사실 재현
  • 기존 단정 셋(multipart 구성, 파트 순서, 단일 텍스트 파트) 무변경 통과
  • 표시 이름 인코딩 단정은 문자열 보관을 되돌리면 깨지는 위치

💬 리뷰 포인트

  • From 보관 타입, 문자열과 InternetAddress의 갈림
  • 메일 전송 타임아웃 10초라는 값

The SMTP username fallback only held while that username was itself an
address. With a hosted sending service it is a credential, so the fallback
is guaranteed to produce a From that is not an address: notifications would
retry three times and park FAILED, and account mails, which have no retry,
would be lost outright. The value is now validated when the bean is built,
so a bad one fails the context refresh and the deploy script's readiness
gate rolls back instead.

Strict parsing accepts a bare local part with no domain, which is exactly
the shape a credential has, so validate() is what actually rejects it. The
configured string is kept and parsed by MimeMessageHelper rather than
pre-parsed here, because the helper re-encodes the display name in its own
charset and a pre-parsed address would carry raw bytes into the header.

Also sets connection, read and write timeouts on the mail session. JavaMail
waits forever by default, and the notification dispatcher sends on a single
thread, so one stalled handshake holds every queued mail and the mail health
indicator with it.
The guard's javadoc claimed nothing carries the value, and that was
false: the chained AddressException quotes the string it rejected, that
cause is what the journal renders, and the log masking is key=value
shaped so it does not catch a quoted bare token. In the one
misconfiguration this guard exists for, that token is a credential. The
cause is dropped; the message already names the required shape.

Two tests now pin which branch rejected rather than only the exception
type, one of them asserting the value does not appear in the message.

Also corrects the timeout comment. The notification dispatcher does
drain its batch serially, but account mail runs on a pool of two, and
the comment as written read as though mail were single-threaded
system-wide.
@yessjun
yessjun merged commit a52d7fe into main Sep 17, 2026
1 check passed
@yessjun
yessjun deleted the fix/mail-from-validation branch September 17, 2026 13:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant