-
[Supabase][Auth] custom mailer invite 링크가 메일 스캐너에 먼저 열릴 때 1회용 만료와 재발송을 어떤 기준으로 나누나기타개발지식/풀스택개발 2026. 7. 14. 09:14
IT 리서치 노트
[Supabase][Auth] custom mailer invite 링크가 메일 스캐너에 먼저 열릴 때 1회용 만료와 재발송을 어떤 기준으로 나누나
Supabase custom mailer invite 흐름을 붙인 뒤 사용자가 메일을 눌렀는데 곧바로 invalid link나 otp_expired가 뜨면, 많은 팀이 단순 만료로만 본다. 하지만 2026년 7월 14일 기준 Supabase 공식 문서를 다시 보면 generateLink는 custom email provider용 링크와 OTP를 직접 만들게 하고, troubleshooting 문서는 email prefetching을 가장 흔한 원인으로 든다. 이 글은 메일 스캐너가 invite 링크를 먼저 열었을 때 1회용 링크 만료와 재발송을 어떤 기준으로 나눠 봐야 하는지 정리한 것이다.
1. 개요
결론부터 말하면 메일 발송 직후 곧바로 403 또는
otp_expired가 찍히면 사용자 지연보다 메일 스캐너 선조회 가능성을 먼저 본다. 반대로 발송 뒤 충분한 시간이 지난 뒤 첫 실패가 나면 실제 만료나 잘못된 재사용 가능성이 크다. custom mailer invite 운영에서는 링크 자체보다 발송 시각, 첫 verify 실패 시각, 재발송 횟수를 같은 trace로 남겨 두는 편이 빠르다.이미 generateLink와 inviteUserByEmail 분기 글이 메일 발송 주체를 갈랐다면, 이번 글은 generateLink를 택한 뒤 실제 메일 스캐너와 사용자 만료를 어떻게 분리할지의 문제다. 또 custom mailer 필드 저장 글에서 남겨 둔 필드를 어디에 쓰는지도 여기서 정리된다.
2. 어디서 실제로 막히는가
현장에서 흔한 막힘은 네 가지다. 첫째, 사용자가 메일을 열기 전인데도 verify endpoint에 403이 먼저 찍힌다. 둘째, action_link만 로그에 남겨 어떤 redirect와 어떤 type으로 보냈는지 복기하지 못한다. 셋째, 재발송 뒤에도 이전 링크를 다시 눌러 같은 문제를 반복한다. 넷째, 만료와 scanner 선소비를 같은 장애로 묶어 support 답변이 길어진다.
Supabase troubleshooting 문서는 OTP가 사용자가 누르기도 전에 expired처럼 보이는 가장 흔한 원인으로 email prefetching을 든다. 동시에 auth-email-templates 문서는 ConfirmationURL, TokenHash, RedirectTo를 अलग 단계로 설명하고, passwordless 가이드는 60초 재요청 제한과 1시간 만료 기본값을 적어 둔다. 이 네 문서를 같이 보면 단순히 '링크가 만료됐다'가 아니라 '누가 먼저 링크를 열었는가'와 '재발송 정책을 어떻게 가져갈 것인가'로 문제를 나눠야 한다는 점이 분명해진다.
- 증상: 발송 직후 바로 invalid link 또는
otp_expired가 뜬다. - 실패: action_link만 저장하고 type, redirect, resend 횟수를 잃어버린다.
- 막힘: scanner 선조회와 사용자 지연을 같은 만료로 본다.
- 누락: 발송 성공 이벤트와 verify 실패 이벤트를 다른 로그로 남기지 않는다.
상황 먼저 볼 것 판단 기준 발송 수 초 뒤 첫 실패 로그 시차와 IP 사용자보다 스캐너가 먼저 열었는지 본다 재발송 뒤에도 같은 실패 구링크 재사용 여부 사용자가 새 링크 대신 이전 탭을 눌렀는지 본다 도착 화면이 예상과 다르다 RedirectTo 도착 라우트와 재발송 정책을 분리한다 3. 실무에서 적용하는 순서
가장 실용적인 운영 순서는 다섯 단계다. 먼저 발송 성공 시각과 첫 verify 실패 시각을 함께 남긴다. 두 번째로 confirmation link와 redirect 맥락을 기록한다. 세 번째로 첫 실패가 발송 직후면 scanner 가능성으로 분류하고, 같은 사용자에게는 새 링크 재발송보다 code 입력형 fallback을 먼저 열어 준다. 네 번째로 첫 실패가 늦게 왔다면 실제 만료와 구링크 재사용을 분리한다. 마지막으로 재발송 시에는 이전 링크가 더 이상 유효하지 않다는 안내를 UI와 메일 본문에 분명히 적는다.
- 발송 시각과 첫 verify 실패 시각을 함께 남긴다.
- ConfirmationURL과 RedirectTo를 같은 trace에 기록한다.
- 발송 직후 실패면 scanner 선조회로 먼저 분류한다.
- 지연된 실패면 실제 만료와 구링크 재사용을 본다.
- 재발송 시 새 링크만 유효하다는 안내를 강하게 준다.
핵심은 재발송을 기본값으로 두지 않는 것이다. 발송 후 몇 초 만에 실패했는지 먼저 보면 scanner 선소비인지 실제 사용자 만료인지 빠르게 갈린다. 전자라면 링크를 또 보내도 같은 문제가 반복될 수 있으므로, 수동 code 입력 경로나 두 단계 확인을 먼저 준비하는 편이 낫다.
4. 공식 문서와 예시 화면으로 확인하기
첫 자료는 generateLink 문서의 정의다. Supabase는 이 API를 custom email provider로 보낼 링크와 OTP를 만드는 경로라고 직접 설명한다. 즉 invite 메일을 직접 조립하는 순간부터 링크 소비 시점과 재발송 기준도 애플리케이션이 함께 책임져야 한다.
이 출발점을 놓치면 메일 스캐너가 링크를 먼저 열었을 때도 'Supabase가 만료시켰다' 정도로만 오해하기 쉽다. 실제로는 어떤 링크를 보냈고, 그 링크가 어떤 타입과 어떤 redirect를 품었는지를 운영 쪽에서 따로 남겨야 원인이 갈린다.
두 번째 자료는 이메일 템플릿 변수 표다. ConfirmationURL, TokenHash, RedirectTo는 서로 다른 단계의 정보를 가진다. custom mailer invite 링크를 만들 때 이 셋을 같은 것으로 취급하면 재발송과 수락 실패가 한 덩어리로 섞인다.
특히 RedirectTo는 도착 화면을, TokenHash는 수락 검증 재료를, ConfirmationURL은 실제 클릭 경로를 뜻한다. 메일 스캐너가 먼저 연 링크가 무엇이었는지 복기하려면 이 셋을 따로 봐야 한다.
세 번째 자료는 Supabase troubleshooting 문서의 핵심이다. 이 문서는 토큰이 사용자가 누르기도 전에 expired처럼 보이는 가장 흔한 원인으로 email prefetching을 든다. 조직용 메일 보안 스캐너나 링크 분석기가 먼저 링크를 읽어 버릴 수 있다는 뜻이다.
이 지점이 중요하다. 메일은 정상 발송됐는데도 사용자가 받은 순간 이미 링크가 무효처럼 보일 수 있기 때문이다. 따라서 메일 발송 성공 로그와 verify 실패 로그를 반드시 분리해 둬야 한다.
네 번째 자료는 기본 만료 시간과 재요청 간격이다. Supabase는 기본적으로 magic link와 OTP를 60초 간격으로만 다시 요청할 수 있고, 1시간 뒤 만료된다고 안내한다. 이 수치는 재발송 UX를 설계할 때 기준점이 된다.
메일 스캐너가 개입한 상황에서도 이 기본 창을 기준으로 재발송 정책을 설명해야 한다. 사용자가 늦게 눌러 만료된 것인지, 발송 직후 스캐너가 먼저 소모한 것인지를 구분하는 축이기 때문이다.
실무에서는 이벤트 상관관계 표가 가장 먼저 필요하다. 메일 발송 시각, 첫 verify 실패, 사용자 실제 클릭 시각, 재발송 여부를 같은 trace로 묶어 두면 스캐너 선소비와 단순 사용자 지연을 더 빨리 분리할 수 있다.
이미 custom mailer 필드 저장 글을 봤다면, 이번 표는 그 저장 필드를 실제 장애 분류에 쓰는 단계다. custom mailer에서 action_link만 저장하면 이 표의 절반이 비게 된다.
마지막 자료는 안전한 재발송 메타데이터 예시다. 핵심은 링크 자체를 무분별하게 남기는 것이 아니라, 어떤 invite 타입이었고 몇 번째 재발송인지, 첫 실패가 발송 후 몇 초 만에 왔는지를 기록하는 것이다.
이 구조를 잡아 두면 만료 뒤 재발송 글이나 token_hash 수락 글과도 바로 연결된다. 재발송은 메일 한 통을 더 보내는 일이 아니라 새 수락 세션을 여는 일이다.
5. 주의사항과 리스크
첫 번째 리스크는 메일 스캐너가 링크를 먼저 여는 상황을 사용자 실수로 오해하는 것이다. 두 번째 리스크는 재발송을 남발해 여러 링크가 동시에 열려 구링크와 신링크가 섞이는 것이다. 세 번째 리스크는 RedirectTo와 verification type을 안 남겨 support가 실제 수락 경로를 재현하지 못하는 것이다.
운영 전에 확인할 것은 세 가지다. 발송 직후 verify 실패가 있는지, 사용자 실제 클릭 전에 이미 access 로그가 찍히는지, 재발송 이후 이전 링크를 막을 문구와 UI가 있는지다. 이 셋이 없으면 같은 문제를 만료로만 설명하게 된다.
- 메일 스캐너 선조회는 만료와 다른 문제로 분리한다.
- 재발송 시 새 링크만 유효하다는 안내를 넣는다.
- 발송 로그와 verify 로그를 서로 다른 이벤트로 남긴다.
6. 결론
Supabase custom mailer invite 링크가 메일 스캐너에 먼저 열리면, 많은 경우 실제 만료가 아니라 1회용 링크 선소비 문제다. 발송 시각과 첫 verify 실패 시각을 같은 trace로 남기고, scanner 가능성과 사용자 지연을 먼저 나누면 재발송 정책도 훨씬 짧아진다. custom mailer 운영은 메일 본문보다 링크 수명과 로그 설계가 더 중요하다.
- 발송 직후 실패면 scanner 선소비를 먼저 의심한다.
- 재발송보다 먼저 fallback 수락 경로를 준비한다.
- ConfirmationURL, RedirectTo, resend_count를 같이 남긴다.
scanner 선조회가 반복돼 재발송보다 token 입력형 복구 화면이 먼저 필요한 운영 조건을 따로 정리하려면 [Supabase][Auth] custom mailer에서 scanner 선열림이 의심될 때 ConfirmationURL 대신 Token 입력 복구 화면을 언제 먼저 두나를 이어서 보면 좋다.
7. 참고 링크
- https://supabase.com/docs/reference/javascript/auth-admin-generatelink
- https://supabase.com/docs/guides/auth/auth-email-templates
- https://supabase.com/docs/guides/troubleshooting/otp-verification-failures-token-has-expired-or-otp_expired-errors-5ee4d0
- https://supabase.com/docs/guides/auth/auth-email-passwordless
'기타개발지식 > 풀스택개발' 카테고리의 다른 글
- 증상: 발송 직후 바로 invalid link 또는