본문으로 건너뛰기

직원 초대 흐름 — 운영 runbook

직원 초대의 전체 흐름 · 토큰 정책 · 오류 분기 · 트러블슈팅 SSoT. 2026-06-11 사고 체인 (PR #2747~#2751) 이후의 현행 동작을 기술한다. 배경은 회고 참조.

1. 전체 흐름

  • 발송: apps/web/app/(workspace)/settings 구성원 관리 (대표 전용). 비즈니스 로직은 packages/business-logic/src/members/invite.ts
  • 수락 페이지: apps/web/app/staff-invite/page.tsx (공개 라우트 — 그룹 layout 가드 밖)
  • 수락 액션: apps/web/app/(setup)/actions/invite.tsActionResult 반환 (throw 는 production 에서 메시지가 마스킹되므로 금지)

2. 토큰 정책 (2026-06-11 이후)

동작토큰/코드만료비고
신규 초대신규 발급 (randomBytes(32) hex 64자 + INV-XXXXXXX)7일
재발송기존 재사용연장 (now+7일)수신함의 모든 초대 메일 링크가 계속 유효. #2563 의 회전 동작은 #2747 에서 폐기
링크 무효화"구성원 제거 → 재초대" 만의도적 무효화의 유일한 명시 경로
left 재활성신규 발급7일이전 멤버십의 옛 링크 부활 차단
수락 완료토큰 필드는 doc 에 보존"이미 수락" 분기 식별에 사용 (이메일 등 상세는 미노출)

3. 오류 분기 (errorCode)

errorCode화면원인사용자/운영자 행동
invalid초대 링크 오류토큰 미존재 — 2026-06-10 이전 회전된 링크 · 제거된 초대 · 오타가장 최근 메일 확인 → 없으면 대표에게 재초대 요청
expired만료 안내expiresAt 경과대표가 재발송 (기존 링크 유지 + 만료 연장)
accepted이미 수락된 초대 (성공 톤)수락 후 재클릭로그인 버튼으로 진입
(없음)일시 오류조회 실패 (인프라)재시도

4. 수락 경로별 주의점

  1. 비밀번호: 신규 계정 생성 또는 기존 비밀번호 로그인. invalid-credential 시 구글 경로 안내 카피 노출
  2. Google 팝업: login_hint 로 초대 이메일 유도. 팝업 후 클라이언트 이메일 일치 선검사 + 서버의 invite.email !== session.email 가드가 최종 차단 — 제3자가 링크를 입수해도 다른 계정으로 수락 불가. 구글 전용 계정 (password provider 없음) 의 유일한 정상 경로
  3. 세션 원클릭: 초대받은 이메일로 이미 로그인된 브라우저면 비밀번호 생략. 이메일 대소문자 무시 비교

보안 불변식:

  • 초대 대상 이메일은 검증 응답의 pending 분기에서만 반환 (수락 완료/무효 분기는 미노출 — #163 정합)
  • 수락은 transaction 으로 race 차단 (두 탭 동시 클릭 시 1회만 성공)

5. 운영 가시성 — ops /health 구성원 초대 헬스 패널

ops 콘솔 /health구성원 초대 헬스 (#2751) 가 전 tenant pending 초대를 스윕한다:

  • stuck (3일+): 수신자가 메일을 못 받았거나 (스팸함·발송 실패) 막힌 신호 → 해당 사무소 대표에게 재발송 안내
  • 만료: 재발송 1회로 복구 (만료 연장)
  • 나이는 최초 초대 기준 — 재발송해도 리셋되지 않으므로 "오래 머문 초대" 가 숨지 않음

이메일 발송 자체의 실패는 발송 시점에 UI 가 분기 (emailSent=false → 초대 코드 수동 공유 안내) 하고, member_invited 활동 로그의 metadata.emailSent 로 사후 추적 가능 (#2748 이후 caseless 로그가 실제 기록됨).

6. 트러블슈팅

증상원인 후보확인조치
"유효하지 않은 초대 링크"2026-06-11 fix 이전 재발송으로 회전된 옛 링크ops 초대 헬스에서 해당 이메일 pending 확인재발송 1회 (이후 모든 메일 유효)
"비밀번호가 올바르지 않습니다" 반복구글 전용 계정"Google 계정으로 계속하기" 안내
버튼 클릭 시 영어 "Server Action … was not found"배포 skew (옛 빌드 탭)ops /health 배포 현황에서 rollout 시각 확인새로고침 후 재시도 (배포 & 릴리스 배포 후 체크리스트)
초대 메일 미수신발송 실패 또는 스팸함발송 시 toast / activityLogs metadata.emailSent초대 코드 수동 공유 (온보딩 "초대 코드 참여" 탭) 또는 재발송
수락 후 대시보드 진입 실패Claims 미반영 토큰페이지가 force-refresh + 쿠키 동기화를 수행 — 재현 시 로그아웃 후 재로그인

7. 데이터 모델 (참조)

tenants/{tid}/members/{memberId} (pending 초대 시):

필드의미
statuspending → 수락 시 active
inviteToken링크 토큰 (hex 64) — 재발송 시 보존
inviteCodeINV-XXXXXXX — 온보딩 수동 입력 경로 (cross-tenant 유일)
expiresAt만료 (기본 7일, 재발송 시 연장)
invitedAt / resentAt마지막 (재)발송 시각 / 재발송 흔적
createdAt최초 초대 — 초대 나이의 기준