원문 캡처 · github.com
원문 캡처 · github.com

mailrelay는 이메일을 규칙에 따라 분류해 웹훅으로 보내는 자체 호스팅 SMTP 서버다. Docker로 실행할 수 있으며, 웹 UI에서 받은 이메일과 웹훅 응답을 확인하고 규칙을 만들 수 있다.

은행·카드 알림을 가계부 앱인 Firefly III나 Actual로 보내거나, 영수증을 Paperless-ngx에 전달할 수 있다. 모니터링 알림을 ntfy, Slack, Discord, PagerDuty로 보내고, 고객 지원 주소를 Zammad나 Linear, 사용자 지정 API와 연결하는 방식도 지원한다. 배송 알림을 Home Assistant로 보내거나 n8n 등 자동화 도구와 연결할 수도 있다.

빠른 시작과 배포

Docker가 설치돼 있다면 다음 명령으로 mailrelay와 테스트용 에코 서버를 실행한다.

bash
curl -fsSLO https://raw.githubusercontent.com/gowthamgts/mailrelay/main/docker-compose.quickstart.yaml
docker compose -f docker-compose.quickstart.yaml up -d

curl로 SMTP 이메일을 보낸다.

bash
curl smtp://localhost:2525 --mail-from me@example.com --mail-rcpt hello@example.com --upload-file - <<'EOF'
From: me@example.com
To: hello@example.com
Subject: Hello from mailrelay

It works!
EOF

그다음 http://localhost:2623을 열면 이메일이 표시된다. 아직 일치하는 규칙이 없어 상태는 Dropped다. Rules → New rule에서 조건을 비워 두면 모든 이메일에 일치하는 규칙이 만들어진다. 웹훅 URL을 http://echo:8080/으로 설정한 뒤 이메일을 열고 Replay를 누르면 상태가 Delivered로 바뀌며, 응답 본문에서 웹훅이 받은 내용을 확인할 수 있다. 실행한 서비스를 정리하려면 다음 명령을 쓴다.

bash
docker compose -f docker-compose.quickstart.yaml down -v

실제 서버 배포에는 예제 Docker Compose 파일을 참고하거나 다음 명령을 사용할 수 있다.

bash
docker run -d --name mailrelay \
 -p 25:25 -p 127.0.0.1:2623:2623 \
 -v mailrelay_data:/data \
 -e MAILRELAY_SMTP__DOMAIN=mx.example.com \
 ghcr.io/gowthamgts/mailrelay:latest

웹훅이 네트워크 내부 서비스에 연결돼 있다면 -e MAILRELAY_WEBHOOK__ALLOW_PRIVATE_NETWORKS=true를 추가해야 한다. 컨테이너 이미지는 SQLite 데이터베이스, 원본 .eml 파일, 선택 사항인 config.yaml을 /data에 저장하며 웹 UI를 활성화한다. 바이너리는 릴리스 페이지에서 내려받거나 go build ./cmd/mailrelay로 빌드한 뒤 ./mailrelay -config config.yaml로 실행할 수 있다.

웹 UI는 http.auth(OIDC)를 설정하지 않으면 로그인 기능이 없다. 따라서 2623 포트를 localhost에만 열어 두거나 인증을 처리하는 리버스 프록시 뒤에 둬야 한다. 허용된 주소만 이메일을 받도록 smtp.allowed_recipients도 설정해야 한다.

이메일 처리와 규칙

메일은 SMTP로 받은 뒤 SPF·DKIM·DMARC 검사와 규칙 매칭을 거쳐 웹훅으로 전달된다. 인증 검사는 off, log, enforce 모드로 설정할 수 있다. 규칙은 웹 UI에서 만들며 즉시 적용된다. 한 이메일과 일치하는 규칙이 여러 개면 첫 규칙만 실행하는 것이 아니라 모두 실행한다.

mailrelay는 이메일을 수락하자마자 SMTP 응답 250 OK를 보낸다. 웹훅은 백그라운드에서 지수형 백오프로 재시도하며, 응답이 느리거나 실패해도 이메일 수신을 거부하지 않는다. 모든 시도는 기록되고, 웹 UI에서 이메일을 다시 재생할 수 있다.

규칙 조건에는 From, To, CC, Subject, Body, 임의의 헤더, SMTP 봉투의 MAIL FROM·RCPT TO, 각 주소의 도메인을 사용할 수 있다. 패턴은 기본적으로 glob이며 alerts@*, *.example.com 같은 형식을 지원한다. 정규식은 패턴을 슬래시로 감싸며, 예를 들어 /^\\[URGENT\\]/ 또는 대소문자를 구분하지 않는 /(?i)invoice/를 쓸 수 있다. 조건을 모두 만족시키거나 하나만 만족시키도록 설정할 수 있고, 조건이 없는 규칙은 모든 이메일에 일치한다.

웹훅마다 시간 제한과 재시도 설정을 따로 지정할 수 있으며, Test 버튼으로 URL에 연결할 수 있는지 확인할 수 있다. 기본 설정에서는 localhost, LAN, Docker 네트워크, Tailscale, 클라우드 메타데이터 주소 등 사설·내부 주소로 웹훅을 보낼 수 없다. 자체 호스팅 서비스에 웹훅을 연결하려면 webhook.allow_private_networks: true로 허용해야 한다.

웹훅 데이터와 설정

템플릿을 지정하지 않으면 웹훅에 파싱된 이메일 전체가 전달된다. 첨부 파일의 내용은 Base64로 포함되고 SPF·DKIM·DMARC·ARC 인증 결과와 SMTP 봉투 주소도 제공된다.

json
{
 "from": "sender@example.com",
 "to": ["you@example.com"],
 "subject": "Hello",
 "text_body": "…",
 "html_body": "…",
 "attachments": [{ "filename": "a.pdf", "content_type": "application/pdf", "content": "base64…" }],
 "auth_result": { "spf": "pass", "dkim": "pass", "dmarc": "pass", "arc": "none" },
 "envelope_from": "bounce@example.com",
 "envelope_to": ["you@example.com"]
}

Go 템플릿으로 전달 형식을 직접 작성할 수도 있다. 각 변수는 이미 JSON으로 인코딩되므로 변수 주위에 따옴표를 추가하지 않는다.

text
{"text": {{ .Subject }}, "from": {{ .From }}, "body": {{ .TextBody | default .HTMLBody }}}

사용 가능한 변수는 .From, .To, .CC, .Subject, .TextBody, .HTMLBody, .Headers, .Attachments, .AuthResult, .EnvelopeFrom, .EnvelopeTo다. 사용할 수 있는 함수는 first, last, join ", ", default <fallback>이며, {{ .CC | default .To | first }}처럼 연결할 수 있다. 값이 없는 필드는 null로 렌더링되므로 결과는 항상 유효한 JSON이다. UI 편집기는 변수 자동완성을 제공하고 입력 중 오류를 표시한다.

설정은 기본값에 선택한 설정 파일을 적용한 뒤 MAILRELAY_ 환경 변수로 덮어쓴다. 설정 파일은 YAML, JSON, TOML 형식을 지원하며 환경 변수에서 중첩 항목은 __로 표시한다. 예를 들어 MAILRELAY_SMTP__DOMAIN을 쓸 수 있다. smtp.allowed_recipients와 인증 공급자 같은 목록은 파일에 지정해야 한다. 전체 옵션은 config.example.yaml에 설명돼 있다.

설정기본값
smtp.addr0.0.0.0:25
smtp.allowed_recipients비어 있음(모든 수신자 허용)
http.addr127.0.0.1:2623(Docker에서는 0.0.0.0:2623)
webui.enabledfalse(Docker에서는 true)
webui.retention_days7
auth.spf / dkim / dmarclog(off, log, enforce 지원; enforce는 SMTP 550으로 거부)
retry.max_retries / retry.timeout3 / 30s
webhook.allow_private_networksfalse(localhost, LAN, Docker, Tailscale, 메타데이터 주소 차단)

재시도, 인증 모드, 로그 수준, 보관 기간은 Settings 페이지에서 실행 중에도 변경할 수 있다.

웹 UI 로그인을 사용하려면 http.auth.enabled: true로 설정하고 http.auth.providers에 OpenID Connect 공급자를 추가한다. Google, Okta, Auth0, Keycloak, Authentik 등을 사용할 수 있으며, 공급자의 issuer_url, client_id, client_secret을 지정하면 엔드포인트를 자동으로 찾는다. 리디렉션 URI로 https://<your-host>/auth/callback을 등록해야 한다. 프록시 뒤에서 쓸 때는 http.auth.redirect_url로 고정할 수 있다. ID 토큰의 서명, 발급자, 대상, 만료 시간, nonce를 검증한다. allowed_email_domains를 설정하면 해당 도메인의 인증된 이메일만 허용한다. 이 항목을 비워 두면 공급자 계정이 있는 누구나 로그인할 수 있다. 재시작 후에도 세션을 유지하려면 session_secret을 설정한다. GitHub는 OIDC를 지원하지 않으므로 GitHub 로그인은 지원하지 않는다.

Gmail이나 Fastmail 같은 공급자가 전달한 이메일은 헤더의 주소와 SMTP 봉투 주소가 서로 다를 수 있다. UI는 원래 헤더의 From·To를 표시하고 봉투 주소를 별도로 보여주며, 규칙은 어느 쪽 주소에도 적용할 수 있다. ARC를 검증해 전달된 이메일의 원래 SPF·DKIM 결과를 유지한다.

운영과 개발

/healthz는 상태 확인용 엔드포인트로 ok를 반환한다. /metrics는 mailrelay_* Prometheus 지표를 제공한다. 자세한 내용은 Prometheus 문서와 Grafana 대시보드를 참고하면 된다. mailrelay -version은 빌드 버전을 출력한다.

text
just dev # run with config.dev.yaml (SMTP on :2525, UI on :2623)
just dev-test # send a batch of sample emails with swaks
just test

mailrelay는 MIT 라이선스로 배포된다.