멱등성 키가 없을 때? Gmail을 안전하게 재시도하는 방법
요약
Gmail의 `messages.send` API는 멱등성 키(idempotency-key)를 지원하지 않아, AI 워크플로우에서 이메일 전송 실패 시 재시도 로직 구현에 어려움이 있습니다. 이를 해결하기 위해 결정론적 Message-ID를 생성하고, Gmail의 `messages:list` 검색 기능을 활용하여 메일함 자체를 수락 로그(acceptance log)로 사용하는 방법을 제시합니다.
핵심 포인트
- Gmail API는 멱등성 키가 없어 재시도 시 중복 전송 위험이 있습니다.
- 결정론적 Message-ID를 생성하여 작업별 고유성을 확보해야 합니다.
- 메일함의 `messages:list` 검색을 통해 이미 전송된 메일을 확인하는 것이 핵심입니다.
- 검색 인덱싱 지연 및 '발견됨' 상태만으로는 고유성 증명이 불충분합니다.
이 글은 저의 '공개 개발 과정(build-in-public)' 시리즈 중 일부입니다. 저는 n8n 워크플로우 템플릿으로 구성된 작은 스토어(AI Automation Lab)와 GitHub에서 사용할 수 있는 무료 오픈소스 연구실을 배포하고 있습니다. 이 게시물은 n8n 포럼의 실제 스레드에서 시작되었습니다. 한 독자가 모든 AI 제공업체가 전송 도중에 실패할 경우 어떻게 되는지 질문했고, 제 답변이 계속 길어졌습니다. 그래서 전체 버전을 여기에 올립니다.
이것은 이메일을 보내는 AI 워크플로우를 구축하는 사람이라면 누구나 겁먹을 만한 문장입니다:
Gmail의 messages.send에는 멱등성 키(idempotency-key) 매개변수가 없습니다.
이는 n8n에서 가장 흔하게 사용되는 '고객 알림' 경로, 즉 LLM으로 초안을 작성한 후 Gmail을 통해 전송하는 과정에, 가장 중요한 실패 상황에 대한 내장 보호 장치가 없다는 것을 의미합니다. 바로 당신이 전송했지만 응답이 돌아오지 않아 고객에게 이메일이 갔는지 여부를 알 수 없는 경우입니다.
처음에 제가 이것을 접했을 때, 제 직감은 모두 틀렸습니다. 로컬 중복 제거 테이블:
2. Message-ID 트릭: 메일함과 비교하기
Gmail이 멱등성 키(idempotency key)를 제공하지 않기 때문에, 저는 차선책인 결정론적 Message-ID를 직접 만듭니다.
메일을 전송할 때 원본 MIME을 구성하면서, 제가 헤더를 설정합니다:
Message-ID: <job-9f3a2c1b@yourdomain>
여기서 job-9f3a2c1b는 작업의 중복 제거 키(dedup key)에서 파생된 값입니다. 즉, 소스 이벤트에서 가져온 안정적인 키이며, 절대 $execution.id나 새로운 uuid를 사용하지 않습니다. 같은 작업이라면, 같은 Message-ID가 모든 재시도마다 영원히 유지됩니다.
이제 스위퍼(sweeper)는 무작정 재전송하지 않습니다. 메일함에 문의합니다:
GET gmail/v1/users/me/messages/list?q=rfc822msgid:<job-9f3a2c1b@yourdomain>
Gmail은 사용자가 제공한 Message-ID를 저장하므로, 메일함 자체가 수락 로그(acceptance log)가 됩니다.
3. 제가 생략하고 거짓말할 만한 세 가지 주의사항
주의사항 1 — 검색 인덱스는 잠시 동안 부정확합니다. Gmail의 messages.list 검색에는 색인 지연(indexing delay)이 있습니다. 따라서 스위퍼의 규칙은 다음과 같습니다: 찾을 수 없음 → 1~2분 대기 → 다시 스윕(sweep)하기.
주의사항 2 — '메일함에 발견됨'이 고유성 증명이 아닙니다.
이것이 전체 패턴입니다. 만약 외부 세계와 상호작용하는 n8n 워크플로우를 구축하고 있다면, GitHub의 무료 랩에 이 시리즈가 기반을 두고 있는 워크플로우 템플릿이 있으며, 유료 템플릿은 스토어에서 확인할 수 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기