Email Not Received Troubleshooting (Resend)
When the gateway returns 202 but mail does not arrive in the inbox, use the checks below. Sending uses the Resend API.
한국어 {#korean}
이메일 미수신 트러블슈팅 (Resend)
Gateway에서 202를 받았는데 kinybaik@gmail.com 등 수신함에 메일이 없을 때 확인할 항목입니다.
현재 발송은 Resend API를 사용합니다.
1. Resend 사전 조건
- Resend API Key: pregoi-mail-sender Worker에 Secret
RESEND_API_KEY로 설정되어 있어야 함. (wrangler secret put RESEND_API_KEY) - 발신 도메인: Worker는 no-reply@update.pregoi.com 으로 발송. Resend Domains에서 update.pregoi.com (또는 사용할 도메인) 인증 완료 필요. (루트 pregoi.com 대신 서브도메인만 인증한 경우 발신 주소를 해당 서브도메인으로 맞춤.)
- 수신 주소: Resend는 임의 수신자로 발송 가능. Cloudflare Destination Address 같은 사전 검증 불필요.
- Enable Receiving (MX / inbound): 도메인 설정에서 “Enable Receiving”·MX 레코드(inbound-smtp…)가 Pending이어도 발송(outbound)에는 영향 없음. 수신(inbound) 기능만 해당하며, 발송은 발신 도메인 인증(SPF/DKIM)으로 동작함.
2. Worker 로그로 실제 오류 확인
Queue Consumer Worker는 HTTP로 노출되지 않으므로, 실시간 로그로 발송 오류를 확인합니다.
cd Prego/pregoi-mail-sendernpx wrangler tail --format pretty위를 실행한 상태에서 다른 터미널에서 Gateway로 테스트 메일을 한 통 보냅니다.
curl -X POST "https://pregoi-email-queue-gateway.kinybaik.workers.dev" \ -H "Content-Type: application/json" \ -d '{"to":"kinybaik@gmail.com","subject":"[Prego] 테스트","content":"<p>본문</p>","tenant_id":"test"}'wrangler tail 출력에서 다음을 확인합니다.
- Mail send failed: Resend 401 → API Key 잘못됨 또는 만료. Worker Secret
RESEND_API_KEY재설정. - Mail send failed: Resend 403 → 도메인 미인증 또는 권한 문제. Resend 대시보드에서 도메인·API 키 확인.
- Mail send failed: Resend 4xx/5xx → 응답 본문으로 원인 파악.
- 로그에 오류 없이 메일만 안 옴 → 수신 측 스팸함·지연 확인.
3. Resend 로그에 “Domain not verified” / 메일이 안 올 때
증상: Resend Dashboard → Logs에서 POST /emails 요청은 보이는데, 수신함에는 메일이 없음. 로그에 “Domain not verified: Verify pregoi.com or update your from domain.” 메시지가 있음.
원인: Resend에 Verified된 도메인과 Worker 발신 주소가 다름. 예: Resend에는 update.pregoi.com 만 인증돼 있는데 Worker가 no-reply@pregoi.com(루트)으로 보내면 “Domain not verified” 발생. Resend는 인증된 도메인/서브도메인으로만 발송 가능.
조치 (둘 중 하나):
- A. 발신 주소를 인증된 서브도메인으로 맞추기
Resend에 update.pregoi.com 만 인증된 경우: Worker 발신 주소를 no-reply@update.pregoi.com 으로 변경. (pregoi-mail-senderFROM_EMAIL상수.) - B. 루트 도메인 인증하기
no-reply@pregoi.com 을 쓰려면 Resend Dashboard에서 pregoi.com 도메인 추가 후 SPF/DKIM 등 DNS 설정하고 Verify 완료.
그 외: 새로 도메인을 인증한 경우 Resend에서 Verify 실행 후 Verified가 될 때까지 대기한 뒤 테스트 메일 재발송.
참고: “Enable Receiving”(MX)은 수신용이라 발송과 무관. 발송을 위해 필요한 것은 발신 도메인 인증(Domains → pregoi.com → SPF/DKIM 설정)입니다.
4. DNS / SPF (선택)
Resend 도메인 인증 시 안내하는 SPF/DKIM 등 DNS 레코드를 pregoi.com에 추가.
5. 스팸함 / 지연
- 수신함뿐 아니라 스팸(정크) 함도 확인.
- 발송 후 수 분 지연될 수 있음.
6. Zuplo 로그에 POST /email 401만 있을 때
증상: Zuplo Activity Log에 POST /email 요청이 401 Unauthorized (ApiKeyInboundPolicy ‘email-api-key-inbound’ - 401 response from Key Service)로만 찍히고, 202 성공 로그가 없음.
의미: Zuplo Key Service가 요청에 실려 온 API Key를 인정하지 않아 401을 반환한 상태. 즉, Zuplo 경로로 보낸 요청이 인증에서 막혀 Gateway까지 도달하지 못함.
원인: 응답이 "detail": "Authorization Failed" 이면 요청에 실린 API Key가 Zuplo Key Service에 등록된 유효한 키가 아님. (헤더 형식은 맞지만, 키 값이 해당 Zuplo 프로젝트에서 인정되지 않음.)
조치:
- Zuplo Portal에서 API Key 확인
- URL이
prego-main-a8d4dfe.zuplo.app인 경우 → 이 URL을 서빙하는 같은 Zuplo 프로젝트로 들어가서 확인.- 해당 프로젝트 → API Keys (또는 Developers / API Keys).
- 사용 중인 키가 활성(Active) 인지, 만료/삭제 되지 않았는지 확인.
- 새 API Key를 발급한 뒤, 그 값을 Frappe
mail_sender_api_key(또는 테스트 시 사용하는 키)에 그대로 반영. 기존에 복사한 키가 다른 프로젝트 것이거나 삭제된 키일 수 있음.
-
클라이언트에서 보내는 키와 형식
- Frappe site_config:
mail_sender_api_key값이 Zuplo Portal의 키와 완전히 동일한지 확인 (앞뒤 공백, 복사 오류 없음). - 헤더: Zuplo는 보통
Authorization: Bearer <API_KEY>또는X-Api-Key: <API_KEY>를 사용. prego_saas는Authorization: Bearer로 보냄. Zuplo 프로젝트의 인바운드 정책에서 어떤 헤더를 쓰는지 확인. - 테스트 스크립트로 보낼 때:
MAIL_SENDER_API_KEY환경 변수에 Zuplo에서 복사한 키를 그대로 넣고, 스크립트가Authorization: Bearer <key>를 붙이는지 확인.
- Frappe site_config:
-
키가 맞는데도 401이면
- 해당 키가 이 환경(예: Production) 에서 사용 가능한지, 경로(/email) 에 연결돼 있는지 Zuplo 설정에서 확인.
- 브라우저/Postman으로
POST https://prego-main-a8d4dfe.zuplo.app/email에Authorization: Bearer <동일_키>, Content-Type: application/json, body{ "to": "...", "subject": "...", "content": "..." }로 직접 호출해 202가 나오는지 확인.
참고: Gateway를 직접 호출하면 Zuplo를 거치지 않으므로 401이 나오지 않음. Zuplo 로그에 202가 쌓이려면 Zuplo URL + 올바른 API Key로 요청해야 함.
7. Queue까지 도달했는데 Resend에 로그가 없을 때
증상: Zuplo → Gateway(202) → Queue(saas-email-queue)까지 메시지가 들어오고, Worker도 큐를 소비하는데 Resend 대시보드에는 이메일/API 로그가 전혀 없음.
Queue 메트릭 예시 (이럴 때 의심):
- Messages Ingested / Messages Acknowledged: 숫자가 쌓임 (메시지는 큐에 들어오고, Consumer가 처리 시도함).
- Messages Retried: 0이 아님 (예: 7 등) → Consumer가 처리 중 에러가 나서
msg.retry()가 호출된 횟수. - Average Consumer Lag Time이 크게 나옴 → 재시도·실패 반복으로 지연.
의미: Worker가 큐 메시지를 받아 Resend API를 호출하기 전/후에 예외가 발생하고, catch에서 msg.retry()만 하고 있음. 성공한 발송이 없으므로 Resend에는 기록이 안 남음 (Resend 대시보드는 보통 성공한 발송 또는 API 요청만 표시).
가능한 원인 (우선순위):
-
Worker에
RESEND_API_KEY미설정 또는 잘못됨- Secret이 없으면
Authorization: Bearer undefined로 요청 → Resend 401 → Worker에서throw→msg.retry(). - 확인:
cd Prego/pregoi-mail-sender후npx wrangler secret list로RESEND_API_KEY존재 여부 확인. - 조치: Resend 대시보드에서 API Key 복사 후
npx wrangler secret put RESEND_API_KEY로 설정.
- Secret이 없으면
-
Resend API Key 만료/권한/도메인
- 401: 키 잘못/만료. 403: 발신 도메인(예: no-reply@pregoi.com) 미인증 또는 권한 부족.
- 조치: Resend Dashboard → API Keys 확인, Domains에서 pregoi.com 인증 여부 확인.
-
페이로드 형식 문제
- Worker는
to,subject,content(string) 필드가 없으면isQueueEmailPayload실패 →msg.retry()(Resend 호출 자체를 안 함). Gateway/Frappe에서 보내는 JSON 형식이 맞는지 확인.
- Worker는
해결 절차:
-
Worker 실시간 로그로 원인 확인
Terminal window cd Prego/pregoi-mail-sendernpx wrangler tail --format pretty다른 터미널에서 테스트 메일 한 통 보낸 뒤,
wrangler tail에 “Mail send failed: Resend 401”, “Resend 403” 등이 찍히는지 확인. 401이면 위 1번, 403이면 2번(도메인/키) 점검. -
RESEND_API_KEY 설정 후 재테스트
wrangler secret put RESEND_API_KEY로 올바른 키 설정 → 테스트 메일 재발송 → Resend 대시보드에 로그/이메일 나타나는지 확인. -
Resend 도메인 인증
no-reply@pregoi.com 사용 시 pregoi.com 도메인이 Resend에서 Verified 상태인지 확인. 미인증이면 403으로 실패할 수 있음.
요약
| 현상 | 우선 확인 |
|---|---|
| Zuplo 로그에 POST /email 401만 있음 | Zuplo Portal API Key 활성·동일 키 사용, Authorization Bearer 형식, /email 경로 연결 |
| Resend 로그에 “Domain not verified” | Resend Domains에서 pregoi.com 추가 후 SPF/DKIM DNS 설정 및 Verify 완료 |
| Gateway 202인데 메일 없음 | 1) Worker에 RESEND_API_KEY 설정, 2) Resend 도메인 인증, 3) wrangler tail로 Worker/Resend 오류 확인 |
| Queue Retry 있음 + Resend 로그 없음 | Worker RESEND_API_KEY Secret 설정 여부, wrangler tail로 “Mail send failed: Resend 401/403” 확인, Resend 도메인 인증 |
| Worker 로그에 Resend 401/403 | API Key, 발신 도메인 인증 |
| 메일 없고 로그도 없음 | Queue 소비 정상 여부, Worker 배포·바인딩 재확인 |
8. MAIL_SENDER_API_URL / MAIL_SENDER_API_KEY
어디서 가져오나
| 사용처 | 읽는 위치 |
|---|---|
| Frappe (prego_saas) | Site Config: mail_sender_api_url, mail_sender_api_key (또는 zuplo_email_url, zuplo_email_api_key) |
| 테스트 스크립트 | 환경 변수 MAIL_SENDER_API_URL, MAIL_SENDER_API_KEY |
어디에 등록하나
| 등록하는 쪽 | 등록 위치 |
|---|---|
| Frappe | Site Config: bench --site <사이트> set-config mail_sender_api_url "..." |
| Zuplo | 환경 변수 EMAIL_QUEUE_GATEWAY_URL = Gateway Worker URL; API Key는 Portal에서 발급 |
| Gateway | wrangler secret put GATEWAY_API_KEY (선택) |
- 일반(권장): Frappe → Zuplo URL (
https://prego-main-a8d4dfe.zuplo.app/email) + Zuplo API Key. - Gateway 직접: Frappe → Gateway URL + (선택) Gateway 키. Zuplo를 거치지 않음.
9. Zuplo 경유 vs Gateway 직접 호출
| 항목 | Zuplo 경유 (운영) | Gateway 직접 (테스트) |
|---|---|---|
| URL | Zuplo URL (예: prego-main-a8d4dfe.zuplo.app/email) | Gateway URL (pregoi-email-queue-gateway.kinybaik.workers.dev) |
| API Key | 필수 (Zuplo API Key) | Gateway에 키 미설정 시 불필요 |
| Zuplo 로그 | 남음 | 안 남음 |
| 용도 | Frappe, 클라이언트 앱 | 파이프라인 검증, 원인 분리 |
원인 분리: Zuplo 401이어도 Gateway 직접 호출로 202·메일 도착이면 → Zuplo/API Key 쪽 문제. Queue·Worker·Resend는 정상.
10. Docker bench에서 이메일 테스트
파이프라인만 확인:
curl -X POST "https://pregoi-email-queue-gateway.kinybaik.workers.dev" \ -H "Content-Type: application/json" \ -d '{"to":"kinybaik@gmail.com","subject":"[Prego] 테스트","content":"<p>본문</p>","tenant_id":"docker-test"}'API 스크립트 (환경 변수 설정 후):
export MAIL_SENDER_API_URL="https://pregoi-email-queue-gateway.kinybaik.workers.dev"export MAIL_SENDER_API_KEY=""python3 scripts/send_test_email_via_api.pyFrappe send_test_email은 prego_saas가 설치된 환경에서만 가능. frappe-bench/apps/prego_saas/docs/EMAIL_ZUPLO_TEST.md 참고.
11. Cloudflare Destination Address (레거시)
Workers send_email 사용 시: Cloudflare Email Routing의 Destination Addresses에 수신 주소를 등록·검증해야 함. 0이면 Worker 발송이 배달되지 않을 수 있음.
현재 발송은 Resend API를 사용하므로 Destination Address와 무관. 위 항목은 Workers Email 사용 시 참고.