엔지니어링 · 2026년 9월 21일
최소 한 번 전달(at-least-once)을 전제로 한 이메일 웹훅 설계
재시도, 멱등성 키, 서명 검증으로 이메일 이벤트용 웹훅 소비자를 견고하게 만들어 전송 이벤트를 하나도 놓치지 않는 방법을 알아보세요.
안정적인 이벤트 전달의 과제
이메일 웹훅에서 최소 한 번 전달(at-least-once)을 달성하려면, 발신 측은 실패한 요청을 지수 백오프로 재시도하고 수신 측은 멱등성을 보장하는 시스템을 구현해야 합니다. 네트워크는 불안정하고 서버는 중단될 수 있으므로 HTTP 200 OK 하나가 이벤트 처리를 보장한다고 가정할 수 없습니다. 안정성은 발신 측의 영구 재시도 큐와 수신 측의 중복 제거 계층을 결합해 만들어집니다.
SendHQ 같은 이메일 API를 연동하면 애플리케이션은 이메일이 전달되었는지, 반송되었는지, 스팸으로 신고되었는지 알아야 합니다. 이런 이벤트는 비동기로 발생합니다. 트래픽이 급증하는 동안 웹훅 엔드포인트가 5분간 중단되면 중요한 전송 신호 수천 건을 잃을 수 있습니다. 이는 분석 데이터에 공백을 만들고, 시스템이 반송에 대응하지 못하게 합니다(발신자 평판을 유지하려면 이 대응이 필수입니다).
안정적인 웹훅의 구조
견고한 웹훅 아키텍처는 세 가지 핵심 축으로 구성됩니다. 서명 검증, 멱등 처리, 재시도 전략입니다.
1. 서명 검증
IP 주소나 본문에 포함된 API 키만 믿고 웹훅 엔드포인트로 들어온 POST 요청을 신뢰해서는 안 됩니다. 공격자는 이런 값을 위조할 수 있습니다. 대신 HMAC(Hash-based Message Authentication Code) 서명을 사용하세요.
발신 측은 공유 시크릿으로 페이로드에 서명하고 서명을 헤더(예: X-SendHQ-Signature)에 담아 보냅니다. 수신 측은 같은 시크릿으로 해시를 다시 계산해 헤더 값과 비교합니다.
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
// Use timingSafeEqual to prevent timing attacks
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature));
}
2. 멱등성과 중복 제거
최소 한 번 전달(at-least-once)은 발신 측이 성공 응답을 받을 때까지 이벤트를 계속 보낸다는 뜻입니다. 서버가 이벤트를 처리했지만 200 OK를 보내기 전에 중단되면 발신 측은 이벤트를 다시 보냅니다. 멱등성이 없으면 하나의 전달을 데이터베이스에서 두 번의 전달로 집계할 수 있습니다.
모든 이벤트에는 고유한 event_id가 있어야 합니다. 처리된 이벤트를 추적하려면 멱등성 키 패턴을 사용하세요.
워크플로:
- 웹훅 페이로드를 받습니다.
processed_events테이블에event_id가 있는지 확인합니다.- 이미 있으면 즉시 200 OK를 반환하고 본문은 무시합니다.
- 없으면 이벤트를 처리하고 하나의 트랜잭션 안에서
event_id를 기록합니다.
3. 재시도 전략
발신 측 관점에서 재시도 정책은 필수입니다. 표준 패턴은 지터(jitter)를 적용한 지수 백오프입니다. 예를 들어 1분, 5분, 30분, 2시간, 12시간 후에 재시도합니다.
수신 측이 4xx 오류(429 제외)를 반환하면 보통 잘못된 서명 같은 클라이언트 오류를 뜻하며, 재시도해도 도움이 되지 않습니다. 5xx 오류나 타임아웃은 일시적 실패를 뜻하므로 재시도가 필수입니다.
구체적인 페이로드 예시
SendHQ에서 받을 수 있는 전송 이벤트 페이로드의 전형적인 예시는 다음과 같습니다.
{
"event_id": "evt_12345abcde",
"event_type": "delivered",
"timestamp": "2026-09-15T10:00:00Z",
"message_id": "msg_98765xyz",
"recipient": "user@example.com",
"metadata": {
"order_id": "ord_5544"
}
}
실패와 엣지 케이스 처리
"느린 소비자" 문제
웹훅 핸들러가 무거운 데이터베이스 쓰기를 하거나 다른 외부 API를 동기적으로 호출하면 엔드포인트가 타임아웃됩니다. 이 때문에 발신 측의 재시도 로직이 작동하고, 서버를 다운시킬 수 있는 "재시도 폭풍"으로 이어집니다.
해결책: 수락과 처리를 분리하세요.
- 웹훅을 받습니다.
- 서명을 검증합니다.
- 원본 페이로드를 메시지 큐(RabbitMQ, SQS, Redis 등)에 넣습니다.
- 즉시 200 OK를 반환합니다.
- 별도의 워커 프로세스가 큐를 소비하고 데이터베이스를 업데이트합니다.
에이전트 대응 준비 문제
웹훅으로 AI 에이전트를 실행하면 무한 루프의 위험이 커집니다. 에이전트가 "delivered" 이벤트를 받고 다른 이메일을 보내 응답하고, 그것이 다시 "delivered" 이벤트를 일으키면 루프가 생깁니다.
이메일 발송은 외부 부수 효과로 다루세요. 에이전트는 사람이 개입하는 승인 절차나 해당 동작이 필요한지 확인하는 엄격한 상태 머신 검사 없이 웹훅만으로 자동으로 이메일을 보내서는 안 됩니다.
생태계 비교
제공업체를 고를 때 안정성은 이런 이벤트를 어떻게 처리하는지, 그리고 이런 이벤트를 만들어 내는 메일 발송량에 어떤 요금을 부과하는지와 연결되는 경우가 많습니다.
대용량 트랜잭션 메일에서는 비용 차이가 뚜렷합니다. Amazon SES 요금에 따르면 종량제 발송은 이메일 1,000통당 0.10 USD입니다. 반면 Postmark 요금은 월 15 USD에 10,000통부터 시작하며 초과분은 1,000통당 1.20~1.80 USD입니다. 50,000통 기준으로 SES 종량제는 대략 5 USD이고 Postmark 요금제는 약 66 USD입니다.
다른 선택지로 Resend는 월 3,000통(일 100통 제한)의 무료 요금제와 월 20 USD에 50,000통인 Pro 요금제를 제공합니다. Mailgun은 월 15 USD에 10,000통부터 시작합니다. SendGrid는 무료 요금제를 60일 체험판으로 바꿨고 Essentials는 월 19.95 USD부터 시작합니다.
어떤 제공업체를 쓰든 데이터 무결성은 이런 이벤트를 소비하는 쪽의 안정성이 좌우합니다.
엔지니어를 위한 구현 체크리스트
- 서명 검증: 공유 시크릿과 상수 시간 비교 함수를 사용해 페이로드를 검증하나요?
- 비동기 처리: 엔드포인트가 무거운 비즈니스 로직을 수행하기 전에 200 OK를 반환하나요?
- 멱등성: 중복 처리를 막기 위해
event_id에 고유 제약 조건이 있나요? - 타임아웃 관리: 중복 재시도를 피하도록 타임아웃을 제공업체의 타임아웃보다 짧게 설정했나요?
- 모니터링: 웹훅 엔드포인트에서 5xx 응답이 급증할 때 알림이 있나요?
- DNS 상태: 수신 서버가 올바르게 구성되었나요? SendHQ 이메일 DNS 검사기 같은 도구로 인프라에 연결할 수 있고 올바르게 구성되었는지 확인하세요.
- 인증 표준: DKIM, SPF, DMARC를 구현하여 발신 메일이 수락되고 처리해야 할 "반송" 웹훅 수가 줄어들도록 했나요?
트레이드오프 요약
접근 방식 | 장점 | 단점
동기 처리 | 구현이 간단하고 즉각적인 일관성 | 타임아웃 위험이 높고 재시도 폭풍이 발생하기 쉬움
큐 기반 처리 | 확장성이 뛰어나고 급증에 강함 | 인프라 복잡도 증가, 최종 일관성
단순 로깅 | 오버헤드가 낮음 | 수동 로그 없이는 놓친 이벤트를 복구할 방법이 없음
멱등성 테이블 | 데이터 무결성 보장 | 이벤트당 데이터베이스 쓰기 추가
마무리
이메일 웹훅의 안정성은 실패를 막는 것이 아니라 실패를 전제로 설계하는 데 있습니다. 네트워크가 실패하고 이벤트가 두 번 이상 전달될 수 있다고 가정하면 진정으로 견고한 시스템을 만들 수 있습니다. 작은 프로젝트의 SPF 레코드를 관리하든 대규모 트랜잭션 시스템을 확장하든, 서명 검증과 멱등성 패턴은 여전히 표준으로 자리 잡고 있습니다.
SendHQ로 이메일 인프라를 구축하세요.