Home · 언어: English · 한국어

authmail-relay 사용 가이드

자체 SMTP를 통해 magic link, OTP, password reset 같은 인증 메일을 보내는 실용 가이드. authmail-relay를 HTTP 게이트웨이로 또는 Python 라이브러리로 사용한다.

authmail-relay가 하는 일

authmail-relay는 사용자 본인의 SMTP 계정을 통해 트랜잭셔널 인증 메일을 발송하는 작은 self-hosted 서비스다. 인증 메일을 보내야 하는 모든 앱에서 SMTP 자격증명과 메일 템플릿 로직을 분리한다. 다른 앱들은 Bearer API key로 내부 HTTP 엔드포인트 하나만 호출하거나 Python 라이브러리로 import해서 사용한다.

magic link, OTP 코드, password reset에 대한 기본 플로우와, 임의의 HTML/텍스트를 보낼 수 있는 /send 엔드포인트를 함께 제공한다.

언제 쓰는가

관리형 도달률, 반송 처리, suppression list, 도메인 평판이 필요하다면 관리형 provider(Resend, Postmark, SendGrid, Mailgun, Amazon SES)를 사용한다. 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 importauthmail_relay

HTTP 서비스 실행

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_HOSTAPI_KEY이며, 빠지면 서비스는 시작 시점에 즉시 실패한다.

30초 SMTP smoke test

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로 종료한다.

curl로 엔드포인트 호출

일반 트랜잭셔널 메일 — /send

curl -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>"
  }'

OTP — /send/otp

curl -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"
  }'

Magic link — /send/magic-link

curl -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")

Docker와 Mailpit으로 로컬 개발

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

프로덕션 보안 체크리스트

이 서비스를 노출하기 전에 반드시 읽을 것. self-hosted 인증 메일 서비스는 잘못 노출되면 악용될 수 있다. 다음을 필수 요구사항으로 다룬다.

Webhook과 관측성

/send* 요청 본문에 webhook_url을 전달하면 발송 결과를 비동기로 받는다. 서비스는 legacy V1 헤더와 timestamp-bound V2 헤더 양쪽으로 payload에 서명한다. 새 수신자는 V2를 검증해야 한다. webhooks.md 참고.

관측성 기능은 모두 기본 off, opt-in:

이 서비스가 아닌 것