Skip to content

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로 노출되지 않으므로, 실시간 로그로 발송 오류를 확인합니다.

Terminal window
cd Prego/pregoi-mail-sender
npx wrangler tail --format pretty

위를 실행한 상태에서 다른 터미널에서 Gateway로 테스트 메일을 한 통 보냅니다.

Terminal window
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-sender FROM_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 프로젝트에서 인정되지 않음.)

조치:

  1. 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(또는 테스트 시 사용하는 키)에 그대로 반영. 기존에 복사한 키가 다른 프로젝트 것이거나 삭제된 키일 수 있음.
  1. 클라이언트에서 보내는 키와 형식

    • 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> 를 붙이는지 확인.
  2. 키가 맞는데도 401이면

    • 해당 키가 이 환경(예: Production) 에서 사용 가능한지, 경로(/email) 에 연결돼 있는지 Zuplo 설정에서 확인.
    • 브라우저/Postman으로 POST https://prego-main-a8d4dfe.zuplo.app/emailAuthorization: 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 요청만 표시).

가능한 원인 (우선순위):

  1. Worker에 RESEND_API_KEY 미설정 또는 잘못됨

    • Secret이 없으면 Authorization: Bearer undefined 로 요청 → Resend 401 → Worker에서 throwmsg.retry().
    • 확인: cd Prego/pregoi-mail-sendernpx wrangler secret listRESEND_API_KEY 존재 여부 확인.
    • 조치: Resend 대시보드에서 API Key 복사 후 npx wrangler secret put RESEND_API_KEY 로 설정.
  2. Resend API Key 만료/권한/도메인

  3. 페이로드 형식 문제

    • Worker는 to, subject, content(string) 필드가 없으면 isQueueEmailPayload 실패 → msg.retry() (Resend 호출 자체를 안 함). Gateway/Frappe에서 보내는 JSON 형식이 맞는지 확인.

해결 절차:

  1. Worker 실시간 로그로 원인 확인

    Terminal window
    cd Prego/pregoi-mail-sender
    npx wrangler tail --format pretty

    다른 터미널에서 테스트 메일 한 통 보낸 뒤, wrangler tail“Mail send failed: Resend 401”, “Resend 403” 등이 찍히는지 확인. 401이면 위 1번, 403이면 2번(도메인/키) 점검.

  2. RESEND_API_KEY 설정 후 재테스트
    wrangler secret put RESEND_API_KEY 로 올바른 키 설정 → 테스트 메일 재발송 → Resend 대시보드에 로그/이메일 나타나는지 확인.

  3. 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/403API 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

어디에 등록하나

등록하는 쪽등록 위치
FrappeSite Config: bench --site <사이트> set-config mail_sender_api_url "..."
Zuplo환경 변수 EMAIL_QUEUE_GATEWAY_URL = Gateway Worker URL; API Key는 Portal에서 발급
Gatewaywrangler 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 직접 (테스트)
URLZuplo 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에서 이메일 테스트

파이프라인만 확인:

Terminal window
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 스크립트 (환경 변수 설정 후):

Terminal window
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.py

Frappe 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 사용 시 참고.

Help