서비스에 회원가입 인증 메일과 주문 완료 SMS 발송 기능이 추가됐습니다. 개발팀은 코드를 다 짰는데 실제로 메일이 스팸함으로 들어가고, SMS는 간헐적으로 실패합니다. 팀장이 "SPF 설정했어요? SMTP relay 포트 맞아요?"라고 묻는데 막막합니다.
메일이 수신자에게 도착하는 경로, 릴레이 서버 설정, 스팸 방지 DNS 레코드, SMS 게이트웨이 연동 — 이 모듈에서 실무에서 반드시 알아야 할 핵심만 짚습니다.
- 1MTA/MDA/MUA의 역할 분담을 설명하고 메일 전달 경로를 그릴 수 있다
- 2Postfix relay 설정(relayhost, smtp_sasl_auth_enable)을 작성할 수 있다
- 3SPF/DKIM/DMARC의 역할 차이를 설명하고 DNS 설정 여부를 확인할 수 있다
- 4swaks 또는 telnet으로 SMTP 연결을 테스트하고 메일 큐 상태를 확인할 수 있다
- 5SMS 게이트웨이 HTTP API 호출 구조와 발송 실패 대응 흐름을 설명할 수 있다
메일 전달 구조 이해
MTA·MDA·MUA — 메일은 세 주체가 릴레이한다
메일 발송이 실패하거나 스팸함에 들어갔을 때 "어디가 문제인지"를 모르는 이유는 메일 전달 구조를 모르기 때문입니다. 메일은 발신자 단말에서 수신자 단말까지 한 번에 가지 않고, 세 역할을 거쳐 릴레이됩니다.

| 역할 | 전체 이름 | 예시 | 하는 일 |
|---|---|---|---|
| MUA | Mail User Agent | Outlook, Gmail 웹 | 사용자가 메일을 작성·읽는 클라이언트 |
| MTA | Mail Transfer Agent | Postfix, Sendmail | 서버 간 메일을 라우팅·전달 |
| MDA | Mail Delivery Agent | Dovecot, Procmail | 최종 수신자 메일함에 저장 |
[앱 서버] → MUA(SMTP 587) → [릴레이 MTA] → [수신 MTA] → MDA → [수신자 메일함]
발송 요청 SMTP 25/587 DNS MX 조회 저장
실무에서 가장 흔한 구성:
- 앱 서버는 직접 외부에 SMTP를 보내지 않고 사내 릴레이 MTA나 클라우드 SMTP 서비스(AWS SES, SendGrid 등)를 경유
- 이유: 직접 25번 발송 시 대부분의 ISP/클라우드가 차단, IP 평판 관리 어려움
Bounce(반송) 유형:
| 유형 | 코드 범위 | 의미 | 대응 |
|---|---|---|---|
| 영구 반송(Hard Bounce) | 5xx | 주소 없음, 도메인 없음 | 즉시 수신자 목록에서 제거 |
| 임시 반송(Soft Bounce) | 4xx | 수신함 꽉 참, 서버 일시 불능 | 자동 재시도 후 일정 기간 경과 시 제거 |
Postfix relay 설정 — 앱 서버에서 외부 SMTP로 내보내기
앱 서버에 Postfix를 직접 설치해 외부 SMTP 릴레이로 보내는 구성은 기업 내부 시스템에서 가장 흔합니다. /etc/postfix/main.cf의 핵심 설정만 이해하면 됩니다.

# /etc/postfix/main.cf 핵심 설정 예시
# 릴레이할 SMTP 서버 (포트 587: STARTTLS)
relayhost = [smtp.example.com]:587
# SASL 인증 활성화 (릴레이 서버가 인증을 요구할 때)
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
# TLS 사용 (STARTTLS)
smtp_tls_security_level = encrypt
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
# 발신 도메인
myorigin = example.com
# /etc/postfix/sasl_passwd (인증 정보 파일)
[smtp.example.com]:587 mailuser@example.com:secretpassword
# 파일 등록 후 해시 생성 및 권한 설정
sudo postmap /etc/postfix/sasl_passwd
sudo chmod 600 /etc/postfix/sasl_passwd /etc/postfix/sasl_passwd.db
# Postfix 재로드
sudo systemctl reload postfix
메일 큐 확인 명령어:
# 큐에 쌓인 메일 목록
mailq
# 또는
postqueue -p
# 큐 강제 발송 시도
postqueue -f
# 특정 메일 큐 항목 상세 확인 (Queue ID로)
postcat -q <QUEUE_ID>
# 큐 전체 삭제 (주의: 미발송 메일 전부 삭제)
postsuper -d ALL
SPF/DKIM/DMARC
스팸 방지 3종 세트 — DNS로 메일 신뢰도를 증명한다
메일이 스팸함으로 분류되는 가장 흔한 이유는 SPF/DKIM/DMARC 중 하나라도 빠진 경우입니다. 셋은 각각 다른 문제를 해결합니다.
SPF (Sender Policy Framework) — 발신 서버 IP 인증:
# DNS TXT 레코드 예시 (example.com 도메인)
example.com. IN TXT "v=spf1 ip4:203.0.113.10 include:_spf.sendgrid.net ~all"
# 읽는 법:
# v=spf1 → SPF 버전 1
# ip4:203.0.113.10 → 이 IP에서 보내는 메일은 허용
# include:... → SendGrid 서버도 허용
# ~all → 목록 외 서버는 SoftFail (수신은 하되 의심 마킹)
# -all → 목록 외 서버는 HardFail (거부)
DKIM (DomainKeys Identified Mail) — 메일 내용 서명:
# DNS TXT 레코드 (selector._domainkey.example.com)
default._domainkey.example.com. IN TXT
"v=DKIM1; k=rsa; p=MIGfMA0GCSq..."
# 동작 원리:
# 발신 서버 → 메일 헤더+바디를 개인키로 서명 → 서명값을 헤더에 첨부
# 수신 서버 → DNS에서 공개키 조회 → 서명 검증 → 위변조 여부 확인
DMARC (Domain-based Message Authentication) — 정책 선언:
# DNS TXT 레코드 (_dmarc.example.com)
_dmarc.example.com. IN TXT
"v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com; pct=100"
# p=none → 모니터링만 (처음 도입 시 권장)
# p=quarantine → 실패 메일을 스팸함으로
# p=reject → 실패 메일을 거부
# rua= → 일일 집계 보고서 수신 주소
빠른 확인 명령어:
# SPF 레코드 조회
dig TXT example.com | grep spf
# DKIM 레코드 조회 (selector 이름은 메일 서버 설정 확인)
dig TXT default._domainkey.example.com
# DMARC 레코드 조회
dig TXT _dmarc.example.com
SMTP 연결 테스트
가장 기초적인 SMTP 연결 테스트입니다. 포트가 열려있는지, 서버가 응답하는지 바로 확인합니다.
# telnet으로 25번 연결
telnet smtp-server 25
# 정상 응답 예시:
# Trying 203.0.113.10...
# Connected to smtp-server.
# Escape character is '^]'.
# 220 smtp-server ESMTP Postfix (Ubuntu)
# 수동 SMTP 대화 (연결 확인 후)
EHLO testclient
# 250-smtp-server
# 250-PIPELINING
# 250-SIZE 10240000
# 250-STARTTLS
# 250 8BITMIME
QUIT
연결이 안 되면 방화벽 또는 보안 그룹에서 25번 포트가 차단된 것입니다.
telnet smtp-server 25telnet smtp-server 25접속 후220 ESMTP응답이 왔는가EHLO testclient명령에250-STARTTLS등 서버 기능 목록이 나왔는가- 연결이 거부됐다면 방화벽/보안 그룹 25번 포트 차단 여부를 확인했는가
운영 환경에서 실제 사용하는 587번 포트 + STARTTLS 조합을 테스트합니다. TLS 협상이 정상인지 인증서가 유효한지 한 번에 확인합니다.
openssl s_client -connect smtp-server:587 -starttls smtp
# 정상 응답에서 확인할 것:
# - "verify return:1" → 인증서 검증 성공
# - "CONNECTED(00000003)" → TCP 연결 성공
# - 서버 인증서 정보 (Issuer, Subject)
# 연결 후 수동 인증 테스트 (Base64 인코딩)
AUTH LOGIN
# username (Base64): dXNlckBleGFtcGxlLmNvbQ==
# password (Base64): c2VjcmV0cGFzcw==
openssl s_client -connect smtp-server:587 -starttls smtpopenssl s_client출력에서CONNECTED(00000003)가 나왔는가verify return:1이 나왔는가 (TLS 인증서 검증 성공)- 서버 인증서의
Subject와Issuer정보가 출력됐는가
swaks(Swiss Army Knife for SMTP)는 명령줄에서 실제 메일을 발송해볼 수 있는 SMTP 테스트 도구입니다. telnet보다 훨씬 편하게 전체 발송 과정을 검증합니다.
# 설치
sudo apt-get install -y swaks # Ubuntu
sudo dnf install -y swaks # RHEL/CentOS
# 기본 발송 테스트
swaks \
--to recipient@example.com \
--from noreply@myapp.com \
--server smtp-server \
--port 587 \
--auth LOGIN \
--auth-user mailuser@myapp.com \
--auth-password mysecret \
--tls \
--header "Subject: SMTP Test" \
--body "This is a test mail from swaks"
# 성공 시 출력 예시:
# -> MAIL FROM:<noreply@myapp.com>
# <- 250 2.1.0 Ok
# -> RCPT TO:<recipient@example.com>
# <- 250 2.1.5 Ok
# -> DATA
# <- 354 End data with <CR><LF>.<CR><LF>
# <- 250 2.0.0 Ok: queued as 3A1B2C4D
swaks --to user@example.com --from noreply@myapp.com --server smtp-server --port 587 --auth LOGIN --auth-user mailuser --auth-password secret --tls- swaks 출력에서
<- 250 2.1.0 Ok(MAIL FROM 수락)와<- 250 2.1.5 Ok(RCPT TO 수락)가 나왔는가 - 마지막 응답에
250 2.0.0 Ok: queued as ...가 나왔는가 (발송 큐 등록 성공) - 수신자 메일함(받은 편지함 또는 스팸함)에 테스트 메일이 도착했는가
메일을 발송하면서 로그를 실시간으로 확인합니다. 성공/실패 여부, 릴레이 경로, 수신 서버 응답이 모두 기록됩니다.
# RHEL/CentOS
sudo tail -f /var/log/maillog
# Ubuntu/Debian
sudo tail -f /var/log/mail.log
# 정상 발송 로그 예시:
# May 30 09:15:32 server postfix/smtp[1234]: 3A1B2C4D:
# to=<user@example.com>, relay=smtp-server[203.0.113.10]:587,
# delay=0.8, status=sent (250 2.0.0 OK)
# 실패 로그 예시:
# May 30 09:16:44 server postfix/smtp[1235]: 5E6F7G8H:
# to=<user@example.com>, relay=smtp-server[203.0.113.10]:587,
# status=bounced (550 5.1.1 The email account does not exist)
sudo tail -f /var/log/maillog- telnet smtp-server 25 연결 시 '220 ESMTP' 응답이 왔는가 (포트 열림 확인)
- openssl s_client에서 'verify return:1'이 나왔는가 (TLS 인증서 정상)
- swaks 발송 후 로그에 'status=sent'가 찍혔는가
- 수신자 메일함(받은 편지함 또는 스팸함)에 테스트 메일이 도착했는가
- mailq 명령어 실행 시 큐가 비어 있는가 (메일이 정상 발송됨)
SMS 게이트웨이 연동
SMS 게이트웨이 HTTP API — 구조와 운영 포인트
SMS 게이트웨이는 HTTP API를 제공합니다. 앱 서버가 REST API를 호출하면 게이트웨이가 통신사를 통해 단문 메시지를 전달합니다. 국내 주요 사업자로는 NHN Cloud, 카카오 알림톡, 솔라피, 채널톡 등이 있습니다.
기본 HTTP API 호출 구조:
# curl로 SMS 발송 테스트 (가상의 게이트웨이 예시)
curl -X POST https://api.sms-gateway.com/v1/send \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"from": "01012345678",
"to": "01098765432",
"text": "[인증번호] 귀하의 인증번호는 123456입니다.",
"type": "SMS"
}'
# 응답 예시 (성공):
# {
# "resultCode": "1",
# "message": "성공",
# "messageId": "MSG20260530001234"
# }
# 응답 예시 (실패):
# {
# "resultCode": "-1001",
# "message": "미등록 발신번호"
# }
운영 체크리스트:
| 항목 | 내용 |
|---|---|
| 발신번호 사전등록 | 통신사에 서비스 번호 등록 필수 (전기통신사업법) |
| API 키 보안 | 환경변수 또는 Secret Manager로 관리, 코드에 하드코딩 금지 |
| 재시도 로직 | 4xx는 재시도 금지, 5xx는 exponential backoff 재시도 |
| 발송 이력 DB 저장 | 메시지 ID, 수신번호, 발송시각, 결과코드 반드시 저장 |
| 발송 실패 알림 | 일정 횟수 이상 실패 시 Slack/메일로 알림 |
발송 결과 코드 분류:
# 게이트웨이 로그에서 실패 분류
# -1001: 미등록 발신번호 → 번호 등록 확인
# -1002: 잔액 부족 → 충전
# -9999: 통신사 오류 → 재시도
# 수신 거부: 080 수신거부 서비스 가입자 → 목록에서 제거
트러블슈팅
원인: SPF, DKIM, DMARC 레코드 중 하나 이상이 미설정이거나 실패하고 있습니다. 수신 서버는 이 세 가지 인증이 모두 통과되지 않으면 스팸으로 분류합니다.
# 1. 발송 도메인의 SPF 레코드 확인
dig TXT yourdomain.com | grep spf
# 결과 없으면 → SPF 레코드 미설정
# 2. DKIM 레코드 확인 (selector 확인 필요)
dig TXT default._domainkey.yourdomain.com
# 결과 없으면 → DKIM 설정 미완료
# 3. DMARC 레코드 확인
dig TXT _dmarc.yourdomain.com
# 결과 없으면 → DMARC 미설정
# 4. 실제 수신된 메일 헤더에서 인증 결과 확인
# Gmail: 메일 열기 → 점 세 개 메뉴 → "원본 메시지 보기"
# Authentication-Results: mx.google.com;
# spf=pass (sender IP is authorized)
# dkim=pass header.d=yourdomain.com
# dmarc=pass
# 5. 외부 진단 도구 활용
# https://mxtoolbox.com/SuperTool.aspx
# mail-tester.com 에서 실제 발송 후 점수 확인
해결: DNS 관리 콘솔에서 SPF, DKIM, DMARC TXT 레코드를 순서대로 추가합니다. DNS TTL 전파에 30분~1시간이 걸립니다.
원인: 릴레이 SMTP 서버의 587번 포트가 차단됐거나 서버가 응답하지 않습니다. 방화벽 규칙, 보안 그룹, Postfix main.cf의 relayhost 설정 오류 세 가지를 순서대로 확인합니다.
# 1. 포트 연결 가능 여부 확인
nc -zv smtp-server 587
# Connection refused → 방화벽 또는 서버 문제
# 2. 현재 서버에서 외부 SMTP 포트 차단 확인
telnet smtp-server 587
# 연결 안 되면 → 아웃바운드 587 차단
# 3. Postfix 설정 확인
grep relayhost /etc/postfix/main.cf
# relayhost = [smtp-server]:587
# 대괄호 [] 빠지면 MX 조회 시도 → 오동작
# 4. 인증 정보 파일 확인
sudo cat /etc/postfix/sasl_passwd
# [smtp-server]:587 user@domain:password 형식 확인
# 5. Postfix 재로드 후 큐 처리
sudo systemctl reload postfix
postqueue -f
# 6. 실시간 로그 확인
sudo tail -f /var/log/maillog
실제 업무에서 이 지식이 쓰이는 상황:
인프라 엔지니어가 메일/SMS 관련으로 가장 자주 받는 요청은 세 가지입니다.
1. "메일이 스팸함으로 가요" 처리:
# 빠른 진단 루틴
dig TXT yourdomain.com | grep -E "spf|v=spf1"
dig TXT default._domainkey.yourdomain.com
dig TXT _dmarc.yourdomain.com
# 셋 다 있으면 → 메일 헤더의 Authentication-Results 확인
# 하나라도 없으면 → DNS 설정 추가
2. SMTP relay 설정 요청 (신규 서버):
# Postfix 설치 및 릴레이 설정
sudo apt-get install -y postfix
sudo postconf -e "relayhost = [smtp.yourrelay.com]:587"
sudo postconf -e "smtp_sasl_auth_enable = yes"
sudo postconf -e "smtp_tls_security_level = encrypt"
# sasl_passwd 파일 설정 → postmap → systemctl reload
3. SMS 발송 실패 원인 파악:
앱 로그에서 게이트웨이 응답 코드를 확인합니다. -1001(미등록 발신번호)이 가장 흔하며, 실제 서비스 번호를 게이트웨이 콘솔에서 등록하면 해결됩니다. 발송 이력을 DB에 저장하지 않으면 "왜 A 고객한테 인증 메시지가 안 갔나"를 추적할 수 없습니다.
다음 모듈에서는 SFTP와 배치 파일 송수신을 통한 외부 기관 연계 실무를 다룹니다.