
Spring Boot의 SMTP 내용을 바탕으로 작성하였습니다.
회원가입과 로그인까지 구현하고 나면 비밀번호 재설정, 이메일 인증, 알림 발송처럼
서버가 사용자에게 메일을 보내야 하는 상황을 만나게 됩니다.
이번 글에서는 코드를 작성하기 전에 SMTP가 어떤 역할을 하고,
Spring Boot 애플리케이션에서 메일 한 통이 어떤 경계를 지나 전달되는지 정리해보겠습니다.
SMTP는 Simple Mail Transfer Protocol의 약자로, 네트워크를 통해 이메일을 전송하고 메일 서버 사이에서 중계하기 위한 표준 프로토콜입니다.
메일을 보낸다는 것은 애플리케이션이 곧바로 상대방의 받은편지함에 데이터를 넣는다는 뜻이 아닙니다.
일반적인 흐름은 다음과 같습니다.

Spring Boot 애플리케이션
↓ SMTP 연결·인증
발신 SMTP 서버
↓ 메일 중계
수신자 메일 서버
↓ 스팸·정책 검사 후 저장
수신자의 받은편지함
우리 서버는 먼저 Gmail 같은 발신 SMTP 서버에 메일 전송을 요청합니다. 그 뒤 발신 서버가 수신자의 메일 서버를 찾고 메시지를 전달합니다. 마지막으로 수신자 서버가 메시지를 받아 사서함에 저장해야 사용자가 받은편지함에서 확인할 수 있습니다.
메일 관련 설정 화면을 보다 보면 SMTP, IMAP, POP3가 함께 등장하기 때문에 역할을 혼동하기 쉽습니다.
| 프로토콜 | 주된 역할 | 예시 |
|---|---|---|
| SMTP | 메일 전송·중계 | Spring Boot 서버가 비밀번호 재설정 메일을 보냄 |
| IMAP | 서버의 메일함을 여러 기기에서 동기화하며 조회 | 휴대전화와 PC에서 같은 받은편지함 확인 |
| POP3 | 메일을 클라이언트로 내려받아 조회 | 로컬 메일 프로그램에 메시지 다운로드 |
이번 실습의 목적은 서버에서 메일을 보내는 것이므로 SMTP가 핵심입니다.
Gmail의 전달 및 POP/IMAP 화면에서 IMAP을 활성화하는 과정은 Spring Boot의 SMTP 발송에 필요한 단계가 아닙니다.

비밀번호 재설정 메일을 예로 들어 조금 더 자세히 살펴보겠습니다.
Spring Boot 애플리케이션은 다음과 같은 값을 조합합니다.
From: 발신자 주소
To: 수신자 주소
Subject: 비밀번호 재설정 안내
Body: 재설정 링크와 유효 시간
이때 메일 내용은 단순 문자열일 수도 있고, HTML 템플릿일 수도 있습니다.
애플리케이션은 smtp.gmail.com 같은 SMTP 서버의 주소와 포트를 사용해 연결합니다. Gmail을 STARTTLS 방식으로 연결할 때는 일반적으로 587 포트를 사용합니다.
Host: smtp.gmail.com
Port: 587
Authentication: true
STARTTLS: true
SMTP는 메일을 전송하는 프로토콜이고, 전송 구간의 암호화는 TLS 또는 STARTTLS 설정으로 보완합니다. 따라서 단순히 “SMTP를 사용하므로 안전하다”라고 보기보다 인증 정보와 TLS 설정을 함께 확인해야 합니다.
SMTP 서버는 아무 서버나 특정 Gmail 주소를 발신자로 사용할 수 없도록 인증을 요구합니다. 실습에서는 일반 Google 계정 비밀번호를 소스에 넣는 대신 2단계 인증 후 발급한 앱 비밀번호를 사용합니다.
SPRING_MAIL_USERNAME=your-account@gmail.com
SPRING_MAIL_PASSWORD=your-app-password
이 값은 소스 코드가 아니라 .env 또는 배포 환경의 Secret에 저장해야 합니다.
Spring의 JavaMailSender.send()가 오류 없이 반환되면, 애플리케이션은 적어도 연결한 SMTP 서버가 해당 전송 요청을 거절하지 않았다는 사실을 확인할 수 있습니다.
하지만 이것만으로 다음까지 보장되지는 않습니다.
받은편지함 도착
스팸함으로 분류되지 않음
수신자 서버가 이후 반송하지 않음
SPF·DKIM·DMARC 정책 통과

따라서 API의 200 OK는 “메일이 사용자의 받은편지함에 확실히 도착했다”가 아니라, 이 실습 기준으로는 SMTP 서버가 요청을 정상적으로 수락했다는 범위로 해석해야 합니다.
Spring Boot는 spring-boot-starter-mail 의존성을 추가하고 spring.mail 설정을 제공하면 JavaMailSender를 자동 구성합니다.
// build.gradle.kts
implementation("org.springframework.boot:spring-boot-starter-mail")
spring:
mail:
host: ${SPRING_MAIL_HOST}
port: ${SPRING_MAIL_PORT}
username: ${SPRING_MAIL_USERNAME}
password: ${SPRING_MAIL_PASSWORD}
이후 애플리케이션 코드에서는 SMTP의 저수준 명령을 직접 작성하지 않고 JavaMailSender 추상화를 사용할 수 있습니다.
@Component
class SmtpMailSender(
private val javaMailSender: JavaMailSender
) {
fun send(message: SimpleMailMessage) {
javaMailSender.send(message)
}
}
JavaMailSender는 “메일을 보낼 수 있는 인터페이스”를 제공하고,
실제 SMTP 호스트·포트·인증·TLS 설정은 외부 설정으로 분리합니다.
덕분에 코드가 Gmail이라는 구체적인 메일 서비스에 과도하게 결합되는 것을 줄일 수 있습니다.
가장 간단한 방법은 SimpleMailMessage로 텍스트 메일을 보내는 것입니다.
val message = SimpleMailMessage().apply {
from = "no-reply@example.com"
setTo("user@example.com")
subject = "비밀번호 재설정 안내"
text = "아래 링크에서 비밀번호를 변경해주세요."
}
조금 더 보기 좋은 HTML 메일이 필요하다면 MimeMessage와 템플릿 엔진을 사용할 수 있습니다.
Thymeleaf를 이용하면 HTML 파일의 변수에 값을 넣을 수 있도록 도와줍니다.
<a th:href="${resetLink}">비밀번호 재설정하기</a>
다만 이번 05-answer 브랜치의 실제 구현은 Thymeleaf HTML 메일이 아니라 SimpleMailMessage 기반의 텍스트 메일입니다.
따라서 실습 글에서는 저장소의 현재 코드에 맞춰 텍스트 메일을 먼저 구현하고,
HTML 템플릿은 확장 과제로 분리하는 편이 자연스럽습니다.
메일을 보내는 코드만 동작한다고 계정 복구 기능이 안전해지는 것은 아닙니다.
비밀번호 재설정 기능에서는 다음 정책이 함께 필요합니다.
사용자에게 보내는 원본 토큰은 메일 링크에만 넣고, 데이터베이스에는 SHA-256 해시를 저장합니다.
메일 링크: raw token
데이터베이스: SHA-256(raw token)
데이터베이스가 노출되더라도 저장된 값만으로 즉시 재설정 링크를 사용할 수 없게 하기 위한 경계입니다.
이번 실습에서는 토큰을 15분 후 만료시키며, 정상적으로 비밀번호가 변경되면 사용 시각을 기록합니다.
재발급하면 기존 토큰도 무효화합니다.
메일 전송은 외부 시스템 비용과 스팸 위험을 동반합니다.
사용자별 재요청 제한, IP·장치 단위 제한, CAPTCHA 같은 추가 정책을 상황에 맞게 고려해야 합니다.
토큰을 저장하기 전에 메일부터 보내면, 메일에는 링크가 전달됐지만 데
이터베이스 트랜잭션이 실패해 링크가 무효가 되는 문제가 생길 수 있습니다.
이번 실습에서는 다음 순서를 사용합니다.
1. 재설정 토큰 생성
2. 토큰 해시 저장 및 트랜잭션 commit
3. SMTP 메일 전송
4. 성공이면 200
5. SMTP 실패이면 방금 만든 미사용 토큰 정리 후 오류 반환
원인 예시
- 일반 계정 비밀번호를 사용함
- 앱 비밀번호가 잘못됨
- 발신 주소와 SMTP 인증 계정이 다름
- 조직 계정 정책이 앱 비밀번호 사용을 차단함
SMTP 서버가 응답하지 않을 때 기본 타임아웃을 무한대로 두면 요청 스레드가 장시간 막힐 수 있습니다. 연결·읽기·쓰기 타임아웃을 유한한 값으로 설정하는 것이 좋습니다.
SPRING_MAIL_PROPERTIES_MAIL_SMTP_CONNECTIONTIMEOUT=5000
SPRING_MAIL_PROPERTIES_MAIL_SMTP_TIMEOUT=5000
SPRING_MAIL_PROPERTIES_MAIL_SMTP_WRITETIMEOUT=5000
- 스팸함·프로모션함 확인
- 수신 주소 오타 확인
- 발신 도메인의 SPF·DKIM·DMARC 확인
- 메일 원본에서 From, Return-Path, mailed-by, signed-by 확인
SMTP 서버의 수락과 최종 전달은 서로 다른 증거라는 점을 기억해야 합니다.
SMTP는 애플리케이션이 메일을 작성해 발신 서버로 전달하고, 메일 서버끼리 메시지를 중계하기 위한 프로토콜입니다.
Spring Boot에서는spring-boot-starter-mail과JavaMailSender를 통해 이를 비교적 간단하게 사용할 수 있습니다.그러나 실제 계정 복구 기능에서는 SMTP 연결만큼이나
Secret 관리, TLS, 타임아웃, 토큰 해시 저장, 만료·단일 사용, 재요청 제한, 전송 실패 보상 처리가 중요합니다. 다음 글에서는 이 원리를 바탕으로 Gmail SMTP와 Spring Boot를 연결해 비밀번호 재설정 메일을 직접 보내보겠습니다.