자체 SMTP를 통해 magic link, OTP, password reset 같은 인증 메일을 보내는 실용 가이드. authmail-relay를 HTTP 게이트웨이로 또는 Python 라이브러리로 사용한다.
authmail-relay는 사용자 본인의 SMTP 계정을 통해 트랜잭셔널 인증 메일을 발송하는 작은 self-hosted 서비스다. 인증 메일을 보내야 하는 모든 앱에서 SMTP 자격증명과 메일 템플릿 로직을 분리한다. 다른 앱들은 Bearer API key로 내부 HTTP 엔드포인트 하나만 호출하거나 Python 라이브러리로 import해서 사용한다.
magic link, OTP 코드, password reset에 대한 기본 플로우와, 임의의 HTML/텍스트를 보낼 수 있는 /send 엔드포인트를 함께 제공한다.
authmail-relay는 그 대체품이 아니다.| 모드 | 사용 상황 |
|---|---|
| HTTP 서비스 모드 | 여러 앱이 내부 메일 서비스 하나를 공유할 때. SMTP 자격증명은 이 서비스에만 존재한다. |
| Python 라이브러리 모드 | 하나의 Python/FastAPI 앱에서 직접 메일을 보낼 때. 별도 게이트웨이가 필요 없다. |
| SMTP smoke test CLI | 커맨드라인에서 SMTP 자격증명을 빠르게 검증할 때. |
# Library mode (추가 의존성 없음)
pip install authmail-relay
# HTTP service mode (FastAPI + uvicorn)
pip install "authmail-relay[http]"
요구 사항: Python 3.10+.
아직 릴리즈되지 않은 최신 commit을 git에서 바로 설치:
pip install "authmail-relay[http] @ git+https://github.com/hwan96-ai/authmail-relay.git"
저장소 이름, PyPI 배포 이름, Python import 이름이 서로 다르다. 각각 올바른 위치에 사용해야 한다.
| 맥락 | 이름 |
|---|---|
| 저장소 / 서비스 | authmail-relay |
PyPI 배포 (pip install에 사용) | authmail-relay |
| Python import | authmail_relay |
pip install "authmail-relay[http]"
export SMTP_HOST=smtp.gmail.com
export SMTP_USER=sender@gmail.com
export SMTP_PASSWORD=app-password
export API_KEY=$(openssl rand -hex 32)
python -m authmail_relay
# → Uvicorn running on http://127.0.0.1:8000
OpenAPI 문서는 http://127.0.0.1:8000/docs에서 제공된다. 필수 환경변수는 SMTP_HOST와 API_KEY이며, 빠지면 서비스는 시작 시점에 즉시 실패한다.
export SMTP_HOST=smtp.gmail.com
export SMTP_USER=sender@gmail.com
export SMTP_PASSWORD=app-password
python -m authmail_relay test --to me@example.com
성공 시 0, 실패 시 error_code와 함께 1로 종료한다.
/sendcurl -X POST http://127.0.0.1:8000/send \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "user@example.com",
"subject": "Hi",
"html_body": "<p>Hello</p>"
}'
/send/otpcurl -X POST http://127.0.0.1:8000/send/otp \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "user@example.com",
"user_name": "User Name",
"code": "482901"
}'
/send/magic-linkcurl -X POST http://127.0.0.1:8000/send/magic-link \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "user@example.com",
"user_name": "User Name",
"token": "$TOKEN",
"base_url": "https://myapp.com"
}'
토큰의 생성, 저장, 만료, 일회성 보장은 호출자의 책임이다. authmail-relay는 메일을 보낼 뿐이다. $TOKEN은 호출자 앱에서 생성한다 — 예: python -c "import secrets; print(secrets.token_urlsafe(32))" — 또는 인증 제공자가 발급한 토큰을 그대로 전달한다.
$API_KEY는 export한 셸 안에만 존재한다. 다른 터미널에서 curl을 실행한다면 그 터미널에서 API_KEY를 다시 export하거나 .env에서 불러와야 한다.from authmail_relay import SmtpSender, MagicLinkNotifier, OTPNotifier
from authmail_relay.sender import SmtpConfig
sender = SmtpSender(SmtpConfig(
host="smtp.gmail.com",
user="sender@gmail.com",
password="app-password",
))
# 일회성 HTML 메일
sender.send("user@example.com", "Hi", "<p>Hello</p>")
# Magic link
# 토큰은 호출자 책임이다. 자체 인증이면 아래처럼 고엔트로피 토큰을 발급하고,
# Supabase 같은 외부 인증을 쓴다면 그 제공자가 만든 토큰을 전달한다.
import secrets
token = secrets.token_urlsafe(32)
MagicLinkNotifier(sender, base_url="https://myapp.com").send(
"user@example.com", "User Name", token,
)
# OTP
OTPNotifier(sender).send("user@example.com", "User Name", "482901")
cp .env.example .env
# .env 편집: SMTP_HOST / SMTP_USER / SMTP_PASSWORD / API_KEY
docker compose up -d --build
curl http://127.0.0.1:8000/health
docker-compose.yml은 편의를 위해 호스트의 8000:8000을 노출한다. 이 포트를 공용 인터넷에 노출하지 말 것.
실제 SMTP provider 없이 로컬에서 개발하려면 Mailpit을 사용한다:
docker compose -f docker-compose.dev.yml up -d --build
# Mailpit UI: http://127.0.0.1:8025
/docs와 /metrics를 보호 — 엣지에서 비활성화하거나 인증을 요구한다. METRICS_REQUIRE_AUTH=true를 설정한다.API_KEY, WEBHOOK_SECRET, SMTP 자격증명은 환경변수 또는 시크릿 매니저에 둔다. API_KEY는 openssl rand -hex 32로 생성한다.authmail-relay는 인증 메일을 발송할 뿐, 로그인 토큰을 생성·저장·검증·만료시키지 않는다. 토큰 엔트로피(secrets.token_urlsafe(32) 이상), 만료, 일회성 보장, replay 방지, 계정 상태 검사는 호출자의 책임이다.token_hash 값, 전체 confirmation_url 값, action_link 값을 로그에 남기지 말 것. 이들은 bearer secret이다. 비프로덕션 디버깅에는 dry-run 또는 .eml capture 모드를 사용한다./send* 요청 본문에 webhook_url을 전달하면 발송 결과를 비동기로 받는다. 서비스는 legacy V1 헤더와 timestamp-bound V2 헤더 양쪽으로 payload에 서명한다. 새 수신자는 V2를 검증해야 한다. webhooks.md 참고.
관측성 기능은 모두 기본 off, opt-in:
/metrics (METRICS_ENABLED=true, METRICS_REQUIRE_AUTH=true 권장).EMAIL_SERVICE_LOG_FORMAT=json). 수신자 주소는 해시 처리(SHA-256 앞 8자) — 평문으로 기록하지 않는다.X-Request-ID 전파 — 엔드-투-엔드.max_retries=N).