SSD Nodes Learn 🎉 VPS $5.50/월부터
가이드 Matt Connor작성자 Matt Connor · 업데이트됨 2026-08-21

자체 호스팅 앱에서 메일 발송하는 방법 및 SMTP 릴레이 설정

직접 메일 서버를 운영하지 않고도 자체 호스팅 앱에서 메일을 안정적으로 발송하는 방법을 다룹니다. VPS의 25번 포트 차단 문제를 해결하고 SMTP 릴레이를 통해 SPF, DKIM, DMARC 설정을 완료하여 메일 전달률을 높이는 실무 가이드를 제공합니다.

자체 호스팅 애플리케이션에서 메일 발송이 필요한 이유

자체 호스팅 애플리케이션에서 메일을 보내기 위해 반드시 메일 서버가 필요한 것은 아닙니다. 필요한 것은 릴레이입니다. 호스트에 한 번만 설정해 두면 서버 내 모든 애플리케이션이 발신 메일을 전달할 수 있는 인증된 SMTP 계정 하나면 충분합니다. 메일함을 직접 운영하는 것은 어려운 문제이며, 이는 메일 발송과는 별개의 사안입니다.

메일을 수신한다는 것은 인터넷 전체로부터 port 25를 통해 연결을 허용하고, 스팸을 필터링하며, 메일함을 저장 및 백업하고, 서버가 존재하는 동안 IP 평판을 유지하는 것을 의미합니다. 이 작업은 실제로 훨씬 더 어려워졌습니다. 반면 메일을 발송한다는 것은 비밀번호 재설정, 가입 확인, "백업 실패" 알림, 포럼 답글 알림 등을 보내는 것입니다. 이러한 메일은 짧고, 양이 적으며, 한 번에 하나씩 발송됩니다. 릴레이는 이러한 메일을 처리하며, 설정하는 데는 오후 한나절이면 충분합니다.

본인이 해결하려는 문제가 둘 중 무엇인지 결정하십시오. 직접 메일함을 운영할 가치가 있는지는 실질적인 질문이며 그에 대한 답도 명확합니다. 대부분의 사람에게 그 답은 '아니오'입니다. 만약 답이 '예'라면, VPS에서 Mailcow 메일 서버를 전체 운영하는 것이 정석적인 방법입니다. 나머지 절반인 '발송'은 거의 모든 사람이 필요로 하지만, 정작 계획하는 사람은 거의 없는 영역입니다.

먼저 두 가지 용어를 정의합니다. SMTP(simple mail transfer protocol)는 이 과정의 모든 단계에서 사용하는 프로토콜입니다. 릴레이는 smarthost라고도 불리며, 인증된 메일을 받아 자신의 주소와 평판을 사용하여 목적지로 전달하는 서버를 의미합니다.

VPS에서 25번 포트로 메일을 보낼 수 없는 이유

거의 모든 VPS 제공업체는 기본적으로 아웃바운드 TCP 25번 포트를 차단합니다. 25번 포트는 메일 서버끼리 통신할 때 사용하는 포트이므로, 아웃바운드 25번 포트가 열려 있는 보안이 취약한 VPS는 모든 수신 메일 서버로 스팸을 직접 발송할 수 있습니다. 제공업체는 패킷을 거부하는 대신 폐기(drop)하므로, 오류 메시지가 발생하는 대신 연결이 지연되다가 타임아웃되는 현상이 나타납니다.

서버에서 다음 명령어로 테스트하십시오.

sudo apt update && sudo apt install -y netcat-openbsd
nc -vz -w 5 gmail-smtp-in.l.google.com 25
nc -vz -w 5 smtp.relay.example 587

첫 번째 명령어가 5초 동안 응답 없이 대기하고 두 번째 명령어가 즉시 응답한다면 차단된 것이 확실합니다. 일부 제공업체는 계정 검토 후 차단을 해제해주기도 하지만, 대부분은 그렇지 않습니다.

차단 여부가 릴레이를 사용해야 하는 주된 이유는 아닙니다. 25번 포트가 열려 있더라도 신규 VPS 주소에서 직접 보낸 메일은 스팸으로 분류되거나 즉시 거부됩니다. 해당 주소는 발송 이력이 없고 수신자가 호스팅 공간으로 간주하는 대역에 속하기 때문입니다. Google의 발신자 가이드는 발신 IP에 대해 유효한 정방향 및 역방향 DNS를 요구하는데, 많은 VPS 주소는 변경할 수 없는 일반 PTR(포인터) 레코드를 가지고 있습니다. 릴레이를 사용하면 이미 발송 이력이 있는 주소를 사용할 수 있습니다.

제출용 포트(submission port)가 해결책입니다. 587번 포트는 STARTTLS를 지원하며, 평문으로 세션을 시작한 뒤 암호화 방식으로 업그레이드합니다. 465번 포트는 암시적 TLS(transport layer security)를 사용하며, 첫 바이트부터 세션이 암호화됩니다. 두 포트 모두 인증된 클라이언트를 위한 것이며 VPS 네트워크에서 열려 있고, 귀하의 릴레이 서비스도 최소 하나 이상을 지원합니다.

릴레이 선택 및 발송용 서브도메인 선정

트랜잭션 메일 제공업체는 매우 많으며 모두 동일한 역할을 수행합니다. 다음 네 가지 기준을 바탕으로 업체를 평가하십시오.

  • SMTP AUTH를 지원하는 587 또는 465 제출 포트
  • 제공업체의 도메인뿐만 아니라 사용자의 도메인과 셀렉터를 사용하는 DKIM 서명
  • 대시보드나 웹훅을 통해 확인할 수 있는 반송 및 불만 데이터
  • 사용량에 적합한 요금제. 2026년 8월 기준으로 여러 제공업체가 여전히 매달 수천 건의 메시지를 무료로 제공하지만, 해당 조건은 자주 변경되므로 블로그 게시물보다는 최신 가격 페이지를 확인하십시오.

애플리케이션 메일은 서브도메인에서 발송하십시오. example.com 대신 notify.example.com와 같은 형식을 사용하십시오. 수신자는 도메인별로 평판을 평가하므로, 애플리케이션에서 잘못된 메일을 발송하더라도 청구서나 팀원들이 사용하는 메일 도메인에는 영향을 주지 않습니다. 한계점에 대해서는 솔직해질 필요가 있습니다. 일부 수신자는 서브도메인의 신호를 조직 도메인으로 통합하여 평가하므로, 서브도메인은 피해를 완전히 차단하기보다는 완화하는 역할을 합니다.

모든 셀프 호스팅 앱에 대해 릴레이를 한 번만 설정하기

각 앱의 설정 페이지를 열고 SMTP 호스트, 사용자 이름, 비밀번호를 일일이 붙여넣는 방식은 유혹적입니다. Nextcloud, 포럼, Grafana, Vaultwarden, uptime 모니터 모두 그런 양식을 갖추고 있습니다. 그렇게 하면 자격 증명이 6곳에 6가지 형식으로 흩어지며, 그중 일부는 설정이 아닌 데이터로 백업되는 데이터베이스 내부에 저장됩니다. 비밀번호를 교체할 때마다 5곳을 업데이트해야 합니다. 마지막 한 곳은 전송을 멈추게 되는데, 대부분의 앱은 SMTP 오류를 서버 측에만 기록하고 사용자에게는 성공 페이지를 보여주기 때문에 조용히 실패하게 됩니다.

대신 호스트에서 한 번만 설정하고 앱이 로컬로 제출하게 하십시오. 이를 잘 수행하는 두 가지 도구가 있으며, 선택의 기준은 큐(queue) 처리 방식입니다.

msmtp은 데몬이 없는 sendmail 호환 클라이언트입니다. 연결하고, 전송하고, 종료합니다. 큐 기능이 없으므로 릴레이에 도달할 수 없으면 메시지는 유실되고 호출한 앱은 0이 아닌 종료 상태를 반환합니다.

위성(satellite) 모드로 설정된 Postfix는 실제 큐를 갖춘 완전한 메일 전송 에이전트입니다. 메시지를 즉시 수락하고, 실패 시 며칠 동안 재시도하며, 릴레이 자격 증명을 root만 읽을 수 있는 파일에 보관합니다. 릴레이 중단 시 경고 메시지 유실이 중요하거나, 여러 앱이 서로 다른 시스템 사용자로 실행되는 경우 이 방식을 사용하십시오.

msmtp, 가벼운 선택지

sudo apt update
sudo apt install -y msmtp msmtp-mta ca-certificates

msmtp-mta/usr/sbin/sendmail 심볼릭 링크를 설치하므로, sendmail를 호출하는 모든 프로그램은 msmtp의 존재를 모른 채 이를 사용하게 됩니다.

/etc/msmtprc를 작성하십시오:

defaults
auth            on
tls             on
tls_trust_file  /etc/ssl/certs/ca-certificates.crt
syslog          on

account         relay
host            smtp.relay.example
port            587
from            apps@notify.example.com
set_from_header on
user            <relay username>
password        <relay password>

account default : relay

set_from_header on은 항상 From 헤더를 설정하고 기존 헤더를 덮어쓰므로, 앱이 생성한 내용을 from의 주소로 대체합니다. 이 설정이 없으면 cron 작업은 root@your-hostname로 메일을 보내게 되며, 릴레이는 이를 검증되지 않은 주소로 간주하여 거부합니다. syslog on는 로그를 syslog로 전송하므로 journalctl -t msmtp으로 읽을 수 있습니다. 공유 logfile 경로는 대안이 될 수 있으나, 메일을 보내는 모든 사용자가 쓰기 권한을 가져야 하므로 다중 사용자 환경에서는 위험 요소가 됩니다.

권한을 직접 설정하십시오. msmtp는 사용자별 설정 파일(~/.msmtprc)에 대해 권한을 엄격히 강제하며, 권한이 부적절하면 contains secrets and therefore must have no more than user read/write permissions 오류를 내며 실행을 거부합니다. /etc/msmtprc에 대해서는 파일을 읽을 수만 있다면 그대로 불러오므로 별도의 강제 조치를 하지 않습니다.

sudo chown root:root /etc/msmtprc
sudo chmod 600 /etc/msmtprc
printf 'Subject: relay test\n\nsent from the host\n' | sudo sendmail -v you@example.com

-v는 전체 SMTP 대화 내용을 출력하므로 릴레이의 응답을 확인할 수 있습니다. 정상적인 전송은 메시지를 수락한다는 250 응답으로 끝납니다. authentication failed 줄이 나타나면 사용자 이름이나 비밀번호가 틀렸거나, 릴레이가 계정 비밀번호 대신 API 키를 요구하는 상황입니다.

이제 주의할 점이 있는데, 많은 사람이 결국 Postfix를 선택하게 되는 이유입니다. 모드 600에 소유자가 root이면 root만 메일을 보낼 수 있습니다. www-data로 실행되는 앱은 파일을 읽을 수 없어 msmtp가 이를 건너뛰고, 앱은 기본 계정을 찾을 수 없다는 오류를 내며 실패합니다. 해결책은 그룹을 사용하는 것입니다:

sudo chgrp mail /etc/msmtprc
sudo chmod 640 /etc/msmtprc
sudo usermod -aG mail www-data

이 의미를 명확히 하십시오: mail 그룹의 모든 구성원은 릴레이 비밀번호를 읽고 해당 서버에서 귀하의 도메인 이름으로 메일을 보낼 수 있습니다. 혼자 관리하는 VPS라면 허용 가능합니다. 직접 작성하지 않은 여러 앱이 서로 다른 사용자로 실행되는 환경에서는 적절하지 않으며, 앱이 자격 증명을 직접 보지 않는 Postfix가 더 나은 해답입니다.

위성 모드의 Postfix

sudo debconf-set-selections <<'EOF'
postfix postfix/main_mailer_type select Satellite system
postfix postfix/mailname string notify.example.com
postfix postfix/relayhost string [smtp.relay.example]:587
EOF
sudo DEBIAN_FRONTEND=noninteractive apt install -y postfix libsasl2-modules

libsasl2-modules은 선택 사항이 아닙니다. 이 설정이 없으면 PLAIN 및 LOGIN 메커니즘 라이브러리가 /usr/lib/sasl2 아래에 설치되어 있지 않아 Postfix는 warning: SASL authentication failure: No worthy mechs found 오류를 기록합니다.

postconf -e을 사용하여 나머지를 설정하십시오. 이 명령은 /etc/postfix/main.cf를 제자리에서 편집합니다:

sudo postconf -e 'relayhost = [smtp.relay.example]:587'
sudo postconf -e 'smtp_sasl_auth_enable = yes'
sudo postconf -e 'smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd'
sudo postconf -e 'smtp_sasl_security_options = noanonymous'
sudo postconf -e 'smtp_tls_security_level = encrypt'
sudo postconf -e 'smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt'
sudo postconf -e 'inet_interfaces = loopback-only'

릴레이 호스트 이름 주위의 대괄호는 Postfix가 해당 이름에 대한 MX 레코드를 조회하지 않고 직접 연결하도록 합니다. 일부 릴레이 호스트 이름은 다른 곳을 가리키는 MX 레코드를 게시하므로, 대괄호가 없으면 메일이 잘못된 서버로 전달될 수 있습니다.

smtp_tls_security_level = encrypt는 TLS를 강제하므로 메시지가 평문으로 전송되지 않습니다. 이 설정은 인증서를 검증하지 않습니다. Postfix 자체 문서에도 명시되어 있듯이, 이 수준에서는 서버 인증서를 신뢰할 수 없거나 이름이 일치하지 않아도 전달이 계속됩니다. 인증서 확인을 원한다면 verify 또는 secure을 사용하고 smtp_tls_CAfile 설정을 유지하십시오.

자격 증명은 root만 읽을 수 있는 하나의 파일에 저장합니다:

echo '[smtp.relay.example]:587 relay-username:relay-password' | sudo tee /etc/postfix/sasl_passwd
sudo chmod 600 /etc/postfix/sasl_passwd
sudo postmap hash:/etc/postfix/sasl_passwd
sudo systemctl restart postfix

postmap는 Postfix가 실제로 읽는 인덱싱된 복사본을 생성합니다. 텍스트 파일을 수정한 후 postmap을 잊어버리면, Postfix는 로그에 아무런 알림 없이 이전 데이터베이스를 계속 사용합니다. Postfix 3.9 이상에서는 기본 맵 유형이 lmdb이므로, 선호한다면 매개변수와 postmap 인수 모두에 lmdb:를 작성하십시오. 양쪽 줄에 유형을 명시해야 설정이 동기화됩니다.

앱은 여전히 root@hostname로 메일을 보냅니다. 발신자를 재작성하십시오:

echo '/.+/    apps@notify.example.com' | sudo tee /etc/postfix/sender_canonical
sudo postconf -e 'sender_canonical_classes = envelope_sender, header_sender'
sudo postconf -e 'sender_canonical_maps = regexp:/etc/postfix/sender_canonical'
sudo systemctl reload postfix

regexp: 테이블은 직접 읽히므로 postmap이 필요 없습니다. 이제 모든 메시지는 동일한 엔벨로프 발신자와 From 헤더로 발송되며, 이는 릴레이가 요구하는 방식입니다. 단점은 모든 회신이 한곳으로 모인다는 것이므로, 회신이 사람에게 전달되어야 하는 경우 각 앱 내부에서 Reply-To 헤더를 설정하십시오.

테스트 메일을 보내고 로그를 읽으십시오:

printf 'Subject: postfix relay test\n\nsent through the queue\n' | sendmail you@example.com
sudo tail -n 20 /var/log/mail.log

전달된 메시지는 status=sent과 괄호 안의 릴레이 응답을 기록합니다. 그 외의 내용은 실패 원인을 나타냅니다. Connection timed out과 함께 status=deferred가 나타나면 무언가가 여전히 포트 25를 점유하고 있다는 뜻입니다. Host or domain name not found. Name service error for name=smtp.relay.example type=A은 릴레이 호스트 이름이 잘못되었거나 서버의 DNS가 작동하지 않음을 의미합니다. mailq는 대기 중인 메일 목록을 보여주며, sudo postqueue -f은 즉시 재시도를 수행합니다.

Docker 컨테이너에서 호스트 릴레이에 접근하기

컨테이너는 호스트의 sendmail를 호출할 수 없습니다. 해당 바이너리가 이미지 내부에 존재하지 않고 큐도 공유되지 않기 때문입니다. 대신 컨테이너에 네트워크 대상을 지정하십시오. Postfix는 Docker 브리지 주소에서 수신 대기할 수 있습니다.

ip -4 addr show docker0
sudo postconf -e 'inet_interfaces = 127.0.0.1, 172.17.0.1'
sudo postconf -e 'mynetworks = 127.0.0.0/8 172.17.0.0/16'
sudo systemctl restart postfix

Compose 프로젝트는 서로 다른 서브넷에 자체 네트워크를 생성하므로, 이 명령어를 그대로 복사하지 말고 첫 번째 명령어를 통해 자신의 브리지 주소를 확인하십시오. docker network inspect <name>가 해당 주소를 출력합니다. 여기서는 reload 대신 restart을 사용하십시오. Postfix 문서에 따르면 inet_interfaces을 변경한 후에는 반드시 서비스를 중지했다가 다시 시작해야 하며, reload 명령으로는 변경 사항이 적용되지 않습니다. 각 애플리케이션은 SMTP 호스트 172.17.0.1, 포트 25를 사용하며, 해당 트래픽이 호스트를 벗어나지 않으므로 인증이나 TLS는 필요하지 않습니다. 서비스가 Compose 네트워크에 위치한 경우, VPS에서 Docker Compose 실행하기에서 해당 서브넷의 출처를 확인할 수 있습니다.

이 단계는 위험을 초래할 수 있습니다. 공용 주소에서 넓은 범위의 mynetworks으로 수신 대기하는 Postfix는 오픈 릴레이가 됩니다. 외부인이 귀하의 릴레이 계정을 통해 메일을 발송하면 제공업체가 계정을 정지시키며, 귀하 도메인의 평판이 수개월 동안 손상될 수 있습니다. 변경 사항을 적용할 때마다 양쪽 모두를 확인하십시오.

ss -tlnp | grep ':25'

출력에는 루프백 주소와 브리지 주소만 표시되어야 합니다. 다른 머신에서 실행하는 nc -vz your.server.ip 25은 반드시 실패해야 합니다.

발신 도메인을 위한 SPF, DKIM 및 DMARC

첫 번째 실제 발송을 수행하기 전에 세 가지 레코드를 모두 게시하십시오. 이 레코드들은 무료이며 DNS 기반으로 동작하며, 수신 측에서 가장 먼저 확인하는 항목입니다.

SPF(sender policy framework)는 귀하의 도메인을 엔벨로프 발신자로 사용할 수 있는 주체를 나열합니다. 발신 서브도메인에 이를 게시하십시오:

notify.example.com.  IN  TXT  "v=spf1 include:_spf.relay.example -all"

릴레이 설정 페이지에서 include: 값을 복사하십시오. 해석되지 않는 include는 통과 대신 영구적인 오류를 발생시키기 때문입니다. SPF 평가는 10개의 DNS 쿼리 메커니즘 이후 중단되며 permerror을 반환하는데, 수신 측은 이를 실패로 간주하므로 include 개수를 최소화하십시오. 이름당 정확히 하나의 v=spf1 레코드만 게시하십시오. 두 개를 게시하는 것 또한 permerror에 해당합니다.

DKIM(domainkeys identified mail)은 릴레이가 보유한 개인 키로 각 메시지에 서명하며, 수신 측은 DNS에서 일치하는 공개 키를 가져옵니다. 릴레이는 게시할 셀렉터와 TXT 레코드 또는 CNAME을 제공합니다:

sel1._domainkey.notify.example.com.  IN  CNAME  sel1.dkim.relay.example.

DKIM은 포워딩 시에도 유지되므로 SPF보다 중요합니다. 메일링 리스트나 .forward 규칙이 메시지를 전달하면, 메시지는 포워더의 IP 주소에서 도착하게 되어 SPF는 실패하지만 서명은 여전히 검증됩니다.

DMARC(domain-based message authentication, reporting and conformance)는 두 검사 결과가 일치하지 않을 때 수신 측이 취할 행동을 지시하고 보고를 요청합니다. 조직 도메인에 이를 게시하십시오:

_dmarc.example.com.  IN  TXT  "v=DMARC1; p=none; rua=mailto:dmarc@example.com; adkim=r; aspf=r"

p=none에서 시작하여 2주 동안 보고서를 읽어보십시오. p=none은 전달에 아무런 영향을 주지 않으며 보고 기능만 활성화합니다. 이를 통해 귀하의 도메인을 발신자로 사용하는 시스템을 파악할 수 있습니다. 그 후 p=quarantine로 이동하고, 최종적으로 p=reject으로 변경하십시오. 첫날부터 p=reject을 게시하면, 송장을 받지 못한 고객을 통해 귀하의 청구 시스템이 해당 도메인을 사용하고 있었다는 사실을 뒤늦게 발견하게 될 수 있습니다.

DNS 패널에 표시되는 내용이 아니라 외부에서 어떻게 보이는지 확인하십시오:

dig +short TXT notify.example.com
dig +short TXT sel1._domainkey.notify.example.com
dig +short TXT _dmarc.example.com

출력 결과가 비어 있다면 레코드가 전파되지 않았거나 이름이 잘못된 것입니다. 5분 전에 수정한 레코드라도 이전 TTL(time to live) 값만큼 캐시에 남아 잘못된 상태로 보일 수 있으므로, 결론을 내리기 전에 TTL을 먼저 확인하십시오.

From과 Return-Path를 일치시키기

모든 메시지에는 두 개의 발신자 주소가 포함되며, 각각 다른 방식으로 검증됩니다. 봉투(envelope) 발신자는 SMTP MAIL FROM 명령에서 지정되며, 전달된 메시지에서는 Return-Path으로 나타납니다. 헤더의 From은 수신자가 확인하는 주소입니다.

SPF는 봉투 발신자의 도메인과 연결된 IP 주소를 대조합니다. DKIM은 d=를 통해 서명한 도메인을 보고합니다. DMARC는 두 도메인 중 하나 이상이 헤더의 From 도메인과 일치할 때만 통과합니다. 완화된 정렬(adkim=r, aspf=r, 기본값)을 사용하면 서브도메인도 인정되므로, notify.example.com의 봉투 발신자는 example.com의 헤더 From과 일치하는 것으로 간주됩니다. 엄격한 정렬에서는 일치하지 않습니다.

실무적인 규칙은 간단합니다. 헤더의 From과 봉투 발신자를 동일한 도메인으로 설정하면 문제가 발생하지 않습니다. 이것이 바로 msmtp에서 set_from_header on가 수행하는 작업이며, Postfix에서 sender_canonical_maps이 수행하는 작업입니다.

전달된 메시지에서 판정 결과를 확인하십시오. Gmail의 경우 "원본 보기(Show original)"를 선택하면 수신자가 기록한 헤더가 출력됩니다.

Authentication-Results: mx.google.com;
       dkim=pass header.i=@notify.example.com;
       spf=pass (google.com: domain of apps@notify.example.com designates 198.51.100.25 as permitted sender) smtp.mailfrom=apps@notify.example.com;
       dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=example.com

위 예시에서는 세 가지 검사 모두 통과했습니다. 다른 결과가 나타난다면 실패한 검사 항목과 그 이유가 명시되는데, 이는 이 주제와 관련하여 가장 빠르게 디버깅할 수 있는 정보입니다.

메일 반송 및 스팸 신고, 대량 발송 전 준비 사항

반송(bounce)은 수신 측에서 메일 수신을 거부하는 현상입니다. 하드 바운스(hard bounce)는 영구적인 거부이며, Gmail은 이를 550 5.1.1 The email account that you tried to reach does not exist라고 표현합니다. 소프트 바운스(soft bounce)는 일시적인 현상으로, 수신함이 가득 찼거나 그레이리스팅(greylisting)이 적용된 경우 발생하는 4xx 코드가 해당하며, 릴레이 서버가 스스로 재시도를 수행합니다.

릴레이 서비스는 하드 바운스 비율을 측정하며, 존재하지 않는 주소로 계속 메일을 보내는 계정은 정지시킵니다. 이러한 패턴은 구매한 메일 리스트를 사용하는 경우와 유사하기 때문입니다. 스팸 신고(complaint)는 더 중요한 지표입니다. 스팸 신고는 사용자가 스팸 버튼을 누르는 행위를 의미하며, Google의 발송자 가이드(2026년 8월 확인 기준)는 Postmaster Tools에 보고되는 스팸 비율을 0.30% 미만으로 유지할 것을 요구하고, 0.10% 미만으로 유지할 것을 권장합니다.

대량 발송을 시작하기 전에 다음 네 가지를 반드시 갖추어야 합니다.

  • 웹훅(webhook)을 설정하거나 릴레이의 수신 거부 목록(suppression list)을 매주 확인하여 반송 발생 여부를 파악할 것
  • 실제 사람이 읽는 메일함의 From 주소를 사용하고, 회신을 받을 주소를 Reply-To에 설정할 것
  • 주소를 목록에 추가하기 전에 반드시 확인 절차를 거쳐, 소유자가 직접 입력하지 않은 주소로는 메일을 보내지 않을 것
  • 메일을 발송하게 만드는 모든 폼(form)에 전송 속도 제한(rate limit)을 설정할 것

마지막 두 가지는 자체 호스팅 애플리케이션에서 가장 먼저 실패하는 지점입니다. 보호되지 않은 가입 폼은 누구나 타인의 주소를 입력할 수 있게 하며, 서버가 확인 메일을 발송하면 해당 타인이 이를 스팸으로 신고하게 됩니다. 가입 폼에서 구독 폭탄 방지하기는 악용 방지 업무인 동시에 메일 도달률을 높이는 업무이기도 합니다.

대량 메일은 이 경로에서 분리하십시오. 뉴스레터는 트랜잭션 메일에는 없는 리스트 관리 기능과 수신 거부 헤더가 필요하므로, 자체 호스팅 Listmonk 인스턴스를 별도의 서브도메인에서 운영하여 고유한 평판을 관리해야 합니다. 자체 호스팅 포럼에서 발송되는 알림 메일은 트랜잭션 메일의 형태와 대량 메일의 볼륨을 동시에 가지며, 보통 시스템 설정이 적절한지 가장 먼저 확인할 수 있는 지표가 됩니다.

참고로, Gmail의 대량 발송자 규칙은 Gmail 주소로 하루 5,000건 이상의 메일을 발송할 때 적용되며, SPF, DKIM, DMARC 설정 및 마케팅 메일에 대한 원클릭 수신 거부 기능을 요구합니다. 대부분의 자체 호스팅 앱은 이 기준에 도달하지 않지만, 인증 설정은 이제 발송 규모와 관계없이 모든 발송자에게 요구되는 기본 사항입니다.

신뢰하기 전에 테스트하십시오

swaks는 이를 위한 도구입니다. 이 도구는 SMTP 프로토콜을 사용하여 전체 대화 과정을 출력하므로 어느 단계에서 실패했는지 확인할 수 있습니다.

sudo apt install -y swaks
swaks --to you@example.com --from apps@notify.example.com --server smtp.relay.example --port 587 --tls --auth-user '<relay username>' --auth-password '<relay password>'

위 명령은 릴레이에 대해 직접 자격 증명을 테스트합니다. 애플리케이션이 실제로 사용하는 경로를 테스트하려면 호스트 릴레이를 대상으로 지정하십시오.

swaks --to you@example.com --from apps@notify.example.com --server 127.0.0.1

그런 다음 서버에서 실제 메일을 보내 종단 간 결과를 확인하십시오. 설정 파일만으로는 아무것도 증명할 수 없으므로 직접 실행해 보아야 합니다.

  • mail-tester.com과 같은 점수 측정 서비스로 메일을 보내십시오. 이 서비스는 SPF, DKIM, DMARC 및 메시지 내용을 읽고 점수의 근거를 알려줍니다.
  • 사용자가 실제로 사용하는 두 곳의 메일 제공업체 각각에 메일함을 만들어 메일을 보내고, 원본 메시지에서 Authentication-Results를 확인하십시오.
  • 정렬 결과가 명확하지 않을 때는 learndmarc.com을 통해 메시지 처리 과정을 확인하십시오.
  • 애플리케이션이 From 헤더를 설정하므로, 명령줄뿐만 아니라 애플리케이션 자체에서 전송을 트리거하십시오.

마지막으로 솔직한 경고를 하나 드립니다. 세 가지 레코드를 모두 올바르게 설정한 신규 도메인이라도 스팸으로 분류될 수 있습니다. 도메인에 이력이 없고 수신 측에서는 지난주에 생성된 도메인을 경계하기 때문입니다. 처음에는 적은 양의 메일을 보내고, 사람들이 기대하는 메일을 발송하십시오. 평판은 그 과정에서 쌓이는 것이며, 어떤 설정으로도 이 단계를 건너뛸 수는 없습니다.

FAQ

VPS에서 아웃바운드 25번 포트가 차단되는 이유는 무엇입니까?

거의 모든 제공업체는 기본적으로 아웃바운드 TCP 25번 포트를 차단합니다. 해당 포트가 열린 상태에서 서버가 해킹당하면 스팸 메일을 수신 메일 서버로 직접 발송할 수 있기 때문입니다. 패킷은 거부되는 것이 아니라 드롭되므로, 오류 메시지가 발생하는 대신 연결이 지연되다가 타임아웃되는 증상이 나타납니다. nc -vz -w 5 gmail-smtp-in.l.google.com 25nc -vz -w 5 smtp.relay.example 587과 비교하여 확인하십시오. 전자는 응답이 없지만 후자는 즉시 응답합니다. 차단을 해제해 달라고 요청하는 것은 해결책이 아닙니다. 인증된 클라이언트를 위해 열려 있는 587번 또는 465번 서브미션 포트의 릴레이를 통해 메일을 발송하십시오.

단순히 몇 개의 앱 알림을 보내는 데도 SPF, DKIM, DMARC가 필요합니까?

네, 발송량과는 관계없습니다. 수신 측은 5만 통의 캠페인 메일과 단 한 통의 비밀번호 재설정 메일에 동일한 검증 절차를 적용합니다. SPF와 DKIM이 없으면 메일은 인증되지 않은 것으로 간주되며, Google의 최신 발신자 가이드라인은 모든 발신자에게 최소 하나 이상의 인증을 요구합니다. DMARC가 없으면 보고서를 받을 수 없으므로, 사용자가 재설정 링크를 받지 못했다고 말할 때 비로소 문제를 인지하게 됩니다. 이 세 가지는 모두 DNS 레코드이며 비용이 들지 않고, 게시하는 데 약 10분 정도 소요됩니다.

릴레이 클라이언트로 msmtp와 Postfix 중 무엇을 사용해야 합니까?

한 명의 관리자가 서버를 운영하고 릴레이 장애 시 메일이 유실되어도 괜찮다면 msmtp를 사용하십시오. msmtp는 데몬 없이 단일 설정 파일로 작동하며, 큐(queue) 기능이 없으므로 릴레이에 연결할 수 없으면 메일은 사라집니다. 며칠 동안 재시도를 수행하는 큐가 필요하거나, 여러 앱이 서로 다른 시스템 사용자로 실행되는 경우에는 Postfix를 위성(satellite) 모드로 사용하십시오. Postfix는 릴레이 비밀번호를 root만 읽을 수 있는 파일에 보관하여 앱이 직접 읽지 못하게 하지만, msmtp는 메일을 보내는 모든 사용자가 설정 파일을 읽을 수 있어야 합니다.

앱에서 보낸 메일이 root 주소로 인해 거부되는 이유는 무엇입니까?

Cron 작업과 많은 앱은 로컬 사용자와 호스트 이름을 조합하여 root@srv1.localdomain와 같은 발신자 주소를 생성합니다. 이는 릴레이에서 인증받은 주소가 아니므로, 릴레이는 해당 발신자 주소를 명시하며 553 또는 554 응답으로 메일을 거부합니다. 각 앱을 수정하기보다 호스트 수준에서 해결하십시오. /etc/msmtprcfrom 주소를 설정하여 set_from_header on를 사용하거나, Postfix에서 sender_canonical_classes = envelope_sender, header_sender을 사용하여 sender_canonical_maps를 설정하십시오. 회신을 받아야 하는 경우 각 앱 내부에서 Reply-To을 설정하십시오.

앱 메일을 위해 별도의 서브도메인을 사용하는 것이 메인 도메인을 보호합니까?

부분적으로는 효과가 있으며 여전히 권장되는 방법입니다. 수신 측은 도메인별로 평판을 추적하므로, notify.example.com에 대한 불만 사항은 주로 notify.example.com에 국한되어 메인 도메인의 메일 발송에는 영향을 주지 않습니다. 다만 한계는 분명합니다. 일부 수신 측은 서브도메인의 신호를 조직 도메인으로 통합하여 평가하며, 조직 수준에서 게시된 DMARC 정책은 별도로 sp=을 설정하지 않는 한 서브도메인에도 적용됩니다. 서브도메인을 완벽한 보증이 아닌 피해 최소화 수단으로 간주하십시오.