서비스에 회원가입 인증 메일과 주문 완료 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 호출 구조와 발송 실패 대응 흐름을 설명할 수 있다
메일 전달 구조 이해
알림 한 건이 발송되는 전체 흐름 — 요청부터 전달·바운스까지 6단계 (메일·SMS 공통)
회원가입 인증 메일 한 통, 주문 완료 SMS 한 건. 앱에서는 send(...) 한 줄이지만 실제로는 발송 요청 준비 → 게이트웨이 연결·인증 → 메시지 전송 → 수신 서버/통신사 큐 → 전달 또는 바운스 → 상태 콜백을 거칩니다. 메일(SMTP)이든 SMS(HTTP API)든 뼈대는 같아서, "왜 안 갔지"를 채널에 상관없이 같은 단계 지도로 좁힐 수 있습니다. 특히 발송이 실패했는지 아닌지는 이 흐름의 어느 지점을 봤느냐에 따라 답이 완전히 달라집니다.
[앱] send(수신자, 본문)
│
① 발송 요청 준비 (수신자·본문 + 발신 신원: 발신번호/From, 인증 토큰·SASL 계정)
│
② 게이트웨이 연결·인증 (SMTP 587 STARTTLS / HTTPS API + Bearer 키)
│
③ 메시지 전송(접수) (릴레이·게이트웨이가 자기 큐에 접수 → 250 queued / resultCode:1)
│ → 여기서의 "성공"은 접수됐다는 뜻이지 도착했다는 뜻이 아님
│
④ 수신 서버·통신사 큐 (수신 MX / 이동통신사로 비동기 전달, 임시 실패는 재시도)
│
⑤ 전달 또는 바운스 (수신함 저장 / 스팸 격리 / 반송·발송 실패)
│
⑥ 상태 콜백·로그 (delivery report·webhook / maillog·resultCode)
▼
[운영] 전달률은 ⑥으로 측정 — ③의 접수 응답이 아니라
각 단계에서 무슨 일이 일어나고, 막히면 어떤 증상인가:
| 단계 | 하는 일 | 여기서 막히면 |
|---|---|---|
| ① 요청 준비 | 수신자·본문에 발신 신원(발신번호·From)과 인증정보를 붙임 | 발신번호 미등록 → SMS -1001 / From 도메인 SPF·DKIM 미정렬 → 이후 스팸 격리의 씨앗 |
| ② 연결·인증 | 게이트웨이에 TLS로 붙어 계정·API 키로 인증 | 포트 차단 → Connection refused(587)·타임아웃 / 키·계정 오류 → 535 auth failed·API 401 |
| ③ 전송(접수) | 릴레이·게이트웨이가 메시지를 자기 큐에 접수 | 발송 제한(rate) 초과 → 421·API 429 / 접수 응답 없음 → 큐 등록 실패로 미발송 |
| ④ 수신 큐 | 수신 MX·통신사로 비동기 전달, 임시 실패는 재시도 | 4xx 임시 거부 → deferred 재시도(delay 증가) / 통신사 지연 → 도착 지체 |
| ⑤ 전달·바운스 | 수신함 저장 / 스팸 격리 / 영구 실패 반송 | 5xx → hard bounce(주소 없음) / 인증 불충분 → 조용히 스팸함 / 080 수신거부 → 차단 |
| ⑥ 콜백·로그 | 전달 결과를 리포트·webhook·로그로 회신 | 콜백 미처리 → 바운스 누적을 모름 → 죽은 주소로 계속 발송해 발송 IP 평판 하락 |
즉 발송 실패를 볼 때 ③의 250 queued·resultCode:1을 "전달 완료"로 오해하지 않는 것이 핵심입니다 — 그건 ③(접수)까지의 성공일 뿐, ④⑤는 비동기라 나중에 조용히 실패할 수 있습니다. ②에서 막히면 ⑤을 담당하는 MTA·MDA·MUA 세 주체를 봅니다.Connection refused·401(연결·인증), ③은 429·421(발송 제한), ⑤는 status=bounced·스팸 격리(수신측 거부), ⑥ 미처리는 평판 문제로 갈라 봅니다. 메일은 /var/log/maillog의 queue ID로, SMS는 게이트웨이 resultCode·발송 이력 DB로 각 단계를 추적합니다. 아래에서는 먼저 메일 쪽 ①
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 25- telnet smtp-server 25 실행 후 첫 줄부터 확인 — "220 ESMTP"가 나오면 포트 열림. "Connection refused"면 서비스 미기동, "Connection timed out"이면 방화벽 차단(두 경우 대처가 다름)
- EHLO testclient 명령에 250- 응답 줄이 5개 미만이면 기능 제한된 SMTP 서버 — 250-STARTTLS가 없으면 평문 전송만 가능. 운영 환경에서는 587 포트 STARTTLS 또는 465 포트 TLS만 허용해야 함
- "220 ESMTP"가 오고 EHLO 응답도 정상인데 이후 AUTH가 거부되면 — 25번 포트는 릴레이 제한이 있어 외부 서버에서 인증 불가인 경우가 많음. 발송 테스트는 587 포트로 전환
운영 환경에서 실제 사용하는 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 smtp- openssl s_client 출력에서 "CONNECTED(00000003)"을 먼저 확인 — 이 줄이 없으면 TCP 연결 자체 실패. 있으면 TLS 핸드셰이크로 진행된 것
- "verify return:1"이 나오면 인증서 검증 성공, "verify return:0" 또는 "verify error"이면 인증서 문제 — 자체 서명 인증서(self-signed)는 verify error가 정상이므로 Subject와 Issuer가 동일한지로 확인
- Subject와 Issuer가 모두 출력됐고 verify return:1인 조합이면 TLS 연결 정상 — 이후 AUTH LOGIN 단계에서 535 Authentication failed가 나오면 SMTP 계정 인증 문제로 TLS와 별개 원인
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이 아닌 550이나 553이 오면 발신 주소 거부(SPF 오류 또는 릴레이 차단). 각 단계 응답 코드가 2xx이어야 다음 단계로 진행
- 마지막 "queued as" 응답이 없으면 메일 서버 큐 등록 실패 — 이 경우 수신자 메일함에 메일이 도착하지 않음. mailq 명령으로 큐 상태 확인 필요
- "queued as"가 나왔는데 수신자 메일함에 메일이 없으면 — 스팸함 먼저 확인. 없으면 /var/log/maillog에서 해당 queue ID의 delivery status 추적. "status=bounced"이면 수신 서버가 거부한 것
메일을 발송하면서 로그를 실시간으로 확인합니다. 성공/실패 여부, 릴레이 경로, 수신 서버 응답이 모두 기록됩니다.
# 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- /var/log/maillog에서 queue ID를 기준으로 발송 흐름 전체를 추적 — "to=", "relay=", "delay=", "status=" 순서로 확인. status=sent이면 수신 서버까지 전달 성공
- status=deferred이면 재시도 중인 것 — delay가 60초 이상이면 수신 서버가 느리거나 거부. status=bounced이면 영구 거부로 수신 서버 에러 코드(5xx)를 확인해야 함
- mailq가 비어있고 status=sent인데 수신자가 메일을 못 받으면 — 스팸함 확인 후 없으면 SPF/DKIM/DMARC 설정 문제로 수신 서버가 조용히 폐기한 것. DMARC report 확인 필요
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
심화 — 250 성공 응답의 함정
심화: 릴레이 큐와 반송의 내부 동작 — '큐에 넣었다'는 '전달됐다'가 아니다
발송 스크립트가 250 queued as ...를 받으면 안심하기 쉽지만, 이 응답은 "수신자에게 닿았다"가 아니라 "릴레이가 내 메일을 자기 큐에 받았다"는 뜻일 뿐입니다. 실제 전달은 릴레이가 수신 MX로 다시 보내는 비동기 과정이라, 성공 응답을 받은 뒤에 조용히 실패할 수 있습니다.
- 재시도는 4xx에서만 일어납니다: 수신 서버가
4xx(그레이리스팅·일시 과부하 등 임시 거부)를 주면 릴레이는 메일을 버리지 않고 deferred 큐에 넣습니다. Postfix라면queue_run_delay마다 큐를 훑고minimal_backoff_time~maximal_backoff_time사이에서 재시도 간격을 점점 늘립니다. - 포기 시점이 정해져 있습니다:
maximal_queue_lifetime(기본 5일)이 지나도 못 보내면 릴레이는 재시도를 멈추고 원 발신자에게 반송(bounce) 합니다.5xx(주소 없음 등 영구 거부)는 재시도 없이 즉시 반송됩니다. - 반송은 Return-Path로 돌아옵니다: 반송 메일은 본문의
From이 아니라 봉투 발신자(Return-Path, envelope sender)로 갑니다. 이 주소를 실제로 수신·처리하지 않으면 hard bounce가 쌓여도 아무도 모르고, 죽은 주소로 계속 발송해 발송 IP 평판만 깎입니다. - 한계: 존재하지 않는 대량 주소로 발송하면 deferred 큐가 폭주해 정상 메일까지 밀립니다. 또 위조된 발신자 주소로 온 스팸을 거부하며 만든 반송이 엉뚱한 사람에게 쏟아지는 backscatter도 릴레이 운영의 함정입니다.
그래서 발송 성공률은 앱이 받은 250이 아니라, 릴레이의 반송·deferred 지표와 Return-Path 사서함으로 측정해야 합니다.
상황: swaks로 보내면 각 단계가 250으로 성공하고 큐에도 등록됩니다. 그런데 특정 도메인(회사 도메인, 대형 포털) 수신자에게만 메일이 안 닿습니다. 원본 헤더의 Authentication-Results를 보면 spf=pass, dkim=pass인데 dmarc=fail로 찍혀 조용히 격리됩니다.
원인: DMARC는 SPF/DKIM이 단순히 pass인지가 아니라 정렬(alignment) 됐는지를 봅니다. 외부 릴레이(SES·SendGrid 등)를 경유하면 SPF는 봉투 발신자(Return-Path)인 릴레이의 바운스 도메인에 대해 pass하고, DKIM도 릴레이의 기본 도메인(d= 값이 릴레이 도메인)으로 서명돼 pass합니다. 둘 다 pass지만 둘 다 내 From 도메인과 다른 도메인이라, "내 도메인이 인증됐다"는 DMARC의 조건을 만족하지 못합니다.
진단: 수신 메일 원본에서 세 도메인을 나란히 비교합니다 — From: 헤더의 도메인, SPF가 검사한 Return-Path(mailed-by) 도메인, DKIM 서명의 d= 도메인. 릴레이를 거치면 앞의 것과 뒤 두 개가 다른 경우가 많습니다. DMARC rua 집계 보고서에서도 spf/dkim 정렬 결과를 aligned/fail로 확인할 수 있습니다.
해결: 내 도메인으로 DKIM 서명이 붙도록 릴레이에 도메인 인증을 마칩니다(도메인 소유 확인 후 릴레이가 안내하는 DKIM CNAME/TXT를 DNS에 등록 → d= 값이 내 도메인으로 서명). SPF 정렬이 필요하면 커스텀 MAIL FROM(내 서브도메인)을 설정해 Return-Path를 내 도메인으로 맞춥니다. 도입 초기에는 DMARC를 p=none으로 두고 보고서로 정렬 상태를 확인한 뒤 quarantine→reject로 올립니다.
실제 업무에서 이 지식이 쓰이는 상황:
인프라 엔지니어가 메일/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 고객한테 인증 메시지가 안 갔나"를 추적할 수 없습니다.
명령어·단축키 빠른 참조
이 모듈에서 다룬 메일(SMTP·Postfix)·DNS 인증·SMS 발송 점검 명령을 실전 옵션과 함께 모았습니다.
| 명령어/단축키 | 용도 | 자주 쓰는 예 |
|---|---|---|
telnet <서버> 25 | SMTP 포트 열림·응답 확인 | 220 ESMTP 나오면 정상, EHLO로 기능 확인 |
openssl s_client ... -starttls smtp | 587 STARTTLS·인증서 검증 | openssl s_client -connect smtp:587 -starttls smtp |
swaks | 실제 메일 발송 테스트 | swaks --to a@x.com --server smtp --port 587 --auth LOGIN --tls |
dig TXT | SPF/DKIM/DMARC 레코드 조회 | dig TXT _dmarc.example.com +short |
mailq / postqueue -p | Postfix 발송 큐 확인 | postqueue -f로 큐 강제 재시도 |
postcat -q <큐ID> | 큐에 걸린 메일 내용 열람 | postcat -q 3A1B2C4D |
postsuper -d ALL | 미발송 큐 전체 삭제(주의) | 특정만: postsuper -d <큐ID> |
postconf -e | main.cf 설정 항목 변경 | postconf -e "relayhost = [smtp]:587" |
postmap | sasl_passwd 해시 DB 생성 | postmap /etc/postfix/sasl_passwd |
tail -f /var/log/maillog | 발송 결과 실시간 추적 | status=sent·bounced·deferred 확인 |
nc -zv <서버> 587 | 릴레이 포트 도달 확인 | nc -zv smtp-server 587 |
curl -X POST | SMS 게이트웨이 API 호출 | -H "Authorization: Bearer <API_KEY>" -d '{...}' |
systemctl reload postfix | 설정 변경 반영(무중단) | reload는 큐 유지, 재시작 불필요 |
관련 모듈로 더 깊이:
- 써드파티 API와 공공 인프라 연계 실무 — SMS 게이트웨이처럼 외부 API를 안정적으로 연동·운영하는 법
- SFTP/배치 파일 송수신과 외부 기관 연계 실무 — 메일/SMS와 함께 쓰이는 SFTP 기반 외부 기관 파일 연계
- RESTful API 구조와 curl/Postman 테스트 실무 — 발송 게이트웨이 API의 요청/응답 코드를 읽기 위한 REST 기초
다음 모듈에서는 SFTP와 배치 파일 송수신을 통한 외부 기관 연계 실무를 다룹니다.