0. 구현 상태 (요약)
다음은 2026-04 기준 코드베이스와의 정합 요약이다. 세부 계약은 prego-zuplo OpenAPI와 prego-control-plane 핸들러를 우선한다.
| 영역 | 내용 |
|---|---|
| prego-control-plane | D1 trial_runtime_free_domains(마이그레이션 0026), 정적 분류 후 런타임 사전, 선택적 Abstract phase-2, reasonCode / isCompanyEmail / outcome / pipeline, 내부 CRUD·trial-signup-attempts 로그·external preview, 관리자 cp-trial-email-filter. |
| prego-zuplo | 공개 POST /api/trial/email/check 응답 스키마와 문서 정합. |
Prego apps/www | trial-gateway, TrialEmailGate에서 isCompanyEmail·reasonCode 기반 카피. |
| 운영 | D1 마이그레이션 적용, 선택 시 ABSTRACT_EMAIL_VALIDATION_API_KEY, CP·Zuplo 배포. |
운영 적용: prego-control-plane README Setup 단계 4–5와 동일 — D1에 0026_trial_runtime_free_domains 포함해 npm run db:migrate (원격) / npm run db:migrate:local (로컬), 선택 시 ABSTRACT_EMAIL_VALIDATION_API_KEY, 배포는 npm run deploy 또는 CI. Zuplo는 해당 리포지토리 배포 파이프라인을 따른다.
기획서 본문의 남은 항목: /trial 카드 레이아웃·m.pregoi.com preset·정책 문구 등은 제품 일정에 따라 별도 적용.
1. 배경·문제 정의
1.1 관측 증상
- 공용 이메일 예:
kinybaik@gmail.com입력 시, 기대에 맞는 차단/안내 흐름이 보인다. - 회사 도메인 이메일 예:
marco@gokiri.com입력 시에도 공용 메일과 동일한 메시지·톤으로 보인다. - Zuplo 로그상
POST /api/trial/email/check는 HTTP 200이며, 라우트는 정상 매칭된다.
1.2 문제를 두 층으로 나눔
| 층 | 설명 |
|---|---|
| A. 제품/카피 | API가 분류 결과를 구분해 주더라도, 프론트가 같은 문구만 쓰면 사용자에게는 “똑같다”로 느껴진다. |
| B. 분류 정확도 | gokiri.com이 실제로는 회사 도메인인데, 서버 분류가 free_public / disposable / unknown_needs_review 등으로 떨어져 의도와 다른 정책이 적용되는 경우. |
기획·구현 시 A만으로는 B를 가릴 수 없고, B만 고치면 여전히 A 때문에 UX가 동일할 수 있다. 양쪽을 분리해 검증한다.
1.3 목표 플로우 (제품 요구)
- 회사 메일로 검증이 완료된 경우(API 상으로 자동 트라이얼 허용·또는 기획에서 정의하는 “회사로 확정” 상태), 사용자는 다음 단계로 아래 URL로 이동하여 이메일 인증(verify) 절차를 시작해야 한다.
- 랜딩 URL(예시):
https://m.pregoi.com/?plan=free®ion=sg— 쿼리에plan=free와 리전(예:region=sg) 을 포함한다. 리전 값은 제품/배포 정책에 맞게 정한다.
- 랜딩 URL(예시):
- www의
/trial(또는 동일 출처 이메일 입력 UX)은 게이트 역할로 남고, 실제 인증·프리 플랜 온보딩은 m.pregoi.com(Tenant Onboarding 등) 쪽 플로우와 연결된다. - m.pregoi.com 해당 진입 화면에서의 UX (필수):
- 이메일 주소는 preset(고정 표시) 되어, 사용자가 input 필드를 통해 이메일을 변경할 수 없다(읽기 전용·비활성·마스킹 등 구현 형태는 앱에서 결정).
- 사용자는 Send verification code(또는 동일 의미의 CTA)만으로 다음 단계(인증 코드 수신·입력)를 진행한다.
- 목적: www에서 이미 확정한 회사 메일과 동일 주소로만 온보딩·OTP를 이어가게 하고, 다른 주소로 바꿔 우회하는 것을 방지한다.
- 구현 시 확인할 점: 이동 URL 쿼리(
plan,region등), preset 이메일 전달 방식(쿼리email·해시·세션·짧은 토큰 등 — 길이·노출·PII는 보안 검토), trace 연계, CORS/리다이렉트가 tenant·게이트웨이 경계와 맞는지.
2. 현행 동작 (코드베이스 기준 요약)
- 경로: 브라우저 → (동일 출처)
POST /api/trial/email/check→ Zuplo → prego-control-planePOST /v1/trial/email/check. - 응답 조립: Control Plane의
buildCheckResponse는 대략 다음과 같다.classification === 'business'이면서allowedForAutoTrial === true인 경우에만 수락 메시지 분기.- 그 외 모든 분류(
free_public,disposable,invalid,unknown_needs_review등)는 동일한 차단 카피(TRIAL_BLOCKED_COPY)와showManualApproval: true를 쓴다.
따라서 gmail과 gokiri.com이 둘 다 “자동 트라이얼 불가”이면, API 본문의 message는 구조상 동일할 수 있다. 이는 버그라기보다 현재 제품 규칙에 가깝다.
3. 원인 가설 (우선순위 순)
3.1 가설 1 — 의도된 동일 카피 (층 A)
- 두 이메일 모두
allowedForAutoTrial === false이면 항상 같은message. - 검증: 네트워크 탭 또는
POST /internal/trial-email-check-preview(운영자·Bearer)로 원시 JSON을 비교한다.classification,reasonCode,domain,matchedSources/preview필드가 서로 다른지 확인.
3.2 가설 2 — gokiri.com 분류가 회사가 아님 (층 B)
- 정책: 알 수 없는 도메인은
unknown_needs_review, 자동 트라이얼 불가. - 오분류: 생성 블록리스트·커레이션 리스트에
gokiri.com이 무료/일회성으로 잘못 포함된 경우. - 검증: Preview API 또는 로그로
reasonCode,matchedSources확인. 필요 시 도메인 오버라이드(D1domain_overrides) 로 임시 허용해 A/B 비교.
3.3 가설 3 — 프론트가 구분 필드를 무시
- API가
classification등을 내려주어도 UI가message만 표시하면 체감은 동일. - 검증:
apps/wwwtrial 관련 컴포넌트에서 어떤 필드로 문구를 고르는지 코드 리뷰.
4. 조사 체크리스트 (구현 전 필수)
- 동일 시각·동일 환경에서 두 이메일의 API JSON 전문 저장 (
classification,domain,allowedForAutoTrial,reasonCode, 있으면matchedSources). - 운영자용 Preview 엔드포인트로 동일 도메인 재현 시
mxCheck등 부가 필드 확인 (이미 구현된 경우). gokiri.com이 블록리스트/생성 리스트에 있는지 저장소에서 검색 (prego-control-planeemail-domain-classifier,data/email-domains/).- 필요 시 D1
domain_overrides에서 해당 도메인 수동 허용 후 재테스트(운영 절차 준수).
5. 해결 방향 (택·조합)
5.1 제품 카피·UX (층 A)
- 분류별 메시지 분기: 예)
free_public/disposable→ “개인·무료 메일은 사용할 수 없습니다”;unknown_needs_review→ “도메인 확인이 필요합니다. 수동 승인을 요청할 수 있습니다” 등. - 동일 출처 API 응답에
userFacingReason같은 안정적인 enum을 두어 i18n과 매핑 (OpenAPI·Zuplo·CP 순 변경 순서 준수).
5.1b 회사 메일 검증 성공 → 다음 단계 (필수)
- 조건:
POST /api/trial/email/check(및 CPv1) 응답이 회사 메일로 검증됨을 나타낼 때(현행 스키마에서는 보통allowedForAutoTrial === true및 수락 분기와 정합). - 행동: 사용자를
https://m.pregoi.com/?plan=free®ion=sg형태(plan=free,region=— 실제 값은 환경에 맞게)로 안내하거나 클라이언트 네비게이션하여, 이메일 인증 플로우를 시작한다. - m.pregoi.com 온보딩 페이지 동작 (필수):
- 이메일은 preset — www에서 통과한 주소가 화면에 반영되며, 사용자는 해당 필드로 이메일을 변경할 수 없다.
- 진행은 Send verification code 버튼(또는 동일 역할 CTA)만으로 이어진다(코드 입력·검증 단계는 기존 온보딩 규칙 따름).
- 크로스 앱: 변경 범위는 www의 CTA·리다이렉트·쿼리 조립, admin-web(
apps/admin-web등) / m.pregoi.com 온보딩 화면의 이메일 필드 편집 가능 여부·preset 소스. prego-zuplo는 공개 계약이 바뀌면 OpenAPI 동기화. - 수동 승인 경로: 회사로 보이지 않아 수동 승인만 가능한 경우, 기획상 동일 진입 URL·동일 preset 규칙인지·별도 딥링크인지 구분해 두면 구현 충돌을 줄인다.
5.2 분류 정확도 (층 B)
- 오탐 도메인: 리스트/생성 파이프라인에서 제외 또는 denylist 정정.
- 알 수 없는 도메인: MX/DNS 기반 보조(비용·지연·오탐 리스크 기획에 명시). 기존
mxCheck가skipped인 정책과 충돌하지 않게 정의. - 운영:
domain_overridesallowlist로 긴급 완화 + 장기적으로 리스트/알고리즘 수정.
5.3 계약·게이트웨이
- 공개 응답 스키마 변경 시: prego-zuplo OpenAPI → 라우트 → CP → www 순 (How to add an API 및 플랫폼 허브).
5.4 www /trial — 레이아웃·카드 UI (m.pregoi.com 스타일 정렬)
대상 URL: https://www.pregoi.com/trial — Start your free trial 페이지.
목표: 상단 글로벌/메인 메뉴 아래의 본문(콘텐츠) 영역을, m.pregoi.com 온보딩에 쓰이는 카드형 UI(참고: Account Setup 류 — 흰 배경 카드, 라운드 코너, 내부 패딩, 라벨+이메일 필드, 초록색 Send verification code pill CTA, Or continue with 구분선, Singpass / Google 등 보조 버튼)과 동일한 시각 언어·박스 스케일로 맞춘다.
| 항목 | 요구 |
|---|---|
| 카드 컨테이너 | 본문 콘텐츠를 단일 카드 박스 안에 배치(테두리·라운드·배경·내부 여백은 m.pregoi.com 참조 화면과 동일 계열). |
| width / height | 카드(및 그 안의 폼 열)의 가로·세로 느낌이 첨부 기준 이미지(m.pregoi.com Account Setup 카드) 와 같은 크기·비율이 되도록 한다. 구현 시에는 참조 페이지에서 computed width / min-height / max-width(예: 카드 최대 너비 약 380–450px 대역 등)를 측정해 토큰·픽셀 값을 고정하고, www에 동일하게 적용한다. |
| 수직 정렬 | 뷰포트 높이를 기준으로, 헤더(메인 메뉴) 아래 남은 영역에서 카드 블록이 PC·모바일 모두 세로 방향 중앙(vertically middle) 에 오도록 한다. 일반적으로 min-height를 100dvh(또는 100vh)에서 헤더·여백을 뺀 값으로 잡고, flex/grid + items-center / justify-center 등으로 카드 열을 중앙 정렬(스크롤이 필요한 짧은 뷰포트에서는 상단 여백·하단 스크롤 규칙을 QA로 확정). |
| 범위 | 메뉴는 기존 www 헤더를 유지하고, 변경 범위는 메뉴 하단 본문에 한정한다. |
| 구현 위치(참고) | Prego 모노레포 apps/www 의 trial 라우트·레이아웃(구체 파일은 구현 시 확정). |
비고: 시각 스펙의 단일 기준 소스로 m.pregoi.com 해당 화면(또는 디자인 시스템에 등록된 동일 카드 컴포넌트)을 두고, www는 복제가 아니라 동일 토큰/스펙 공유를 목표로 한다.
6. 수용 기준 (예시)
…@gmail.com과…@gokiri.com(회사로 간주되는 케이스)에 대해, 사용자에게 보이는 문구 또는 다음 액션이 요구사항에 맞게 구분된다.- 회사 메일 검증에 성공한 사용자는
https://m.pregoi.com/?plan=free®ion=sg형태로 진입할 수 있으며(리전 값은 배포 정책에 맞게), 그 화면에서 이메일은 preset·변경 불가이고 Send verification code 로만 이메일 인증(verify) 단계가 진행된다(수동 승인 전용 경로가 다르면 기획서·QA 시나리오에 명시). - 직접 m.pregoi.com에 들어온 사용자와 www에서 넘어온 사용자의 이메일 편집 정책이 다를 수 있으므로, “preset·잠금”은 해당 진입 경로(쿼리 플래그·토큰 등) 에 바인딩하는지 QA에 포함한다.
- API는 최소한
classification(또는 후속userFacingReason) 으로 클라이언트가 분기 가능하다. /trial: 메뉴 하단 본문이 m.pregoi.com 카드와 정렬된 박스 크기·스타일이며, PC·모바일에서 뷰포트 기준 세로 중앙에 배치된다(짧은 화면·키보드 오픈 등 예외는 QA 시나리오에 명시).- 회귀: 기존 수동 승인 플로우·Zuplo 경로가 깨지지 않는다.
7. 범위 밖·리스크
- 게이트웨이에서 도메인 비즈니스 규칙 추가는 하지 않는다 (플랫폼 원칙).
- 분류 강화만으로 스팸·무료 메일 우회가 늘 수 있어, 수동 승인·로그와 함께 설계한다.
8. 관리자 도구 확장 — 2단계 필터·dictionary·로그
아래는 코드 생성 없이 구현 범위를 고정하기 위한 기획 보강이다. 참고 UI는 운영자용 Trial email filter (test) 화면(예: POST /internal/trial-email-check-preview 와 동일 규칙)을 전제로 한다.
8.1 파이프라인 개요 (1차 → 2차 → 최종 판정)
| 단계 | 역할 | 비고 |
|---|---|---|
| 1차 — 내부 dictionary | 알려진 무료·공용·일회성 도메인/패턴을 내부 리스트로 걸러 낸다. | 여기서 매칭되면 “무료/공용 가능성 높음”으로 처리하고, 제품 정책에 따라 자동 트라이얼 불가 등으로 이어질 수 있다. |
| 2차 — 외부 API library | 1차를 통과한(내부 리스트에 없는) 주소 중, 여전히 무료·소비자 메일일 가능성이 있는 경우 외부 검증 API(제품에서 채택한 라이브러리·SaaS)로 추가 판정한다. | API별 비용·지연·쿼터·오탐/미탐은 기획·운영 문서에 명시. |
| 최종 | 내부 dictionary + 외부 API를 모두 거친 뒤 회사 이메일로 볼지, 수동 승인(manual) 으로 안내할지 등을 단일 결과로 확정한다. | 프론트·운영자 도구는 이 최종 판정과 중간 단계 메타데이터(어느 단계에서 걸렸는지)를 함께 볼 수 있게 한다. |
원칙: 게이트웨이(Zuplo)에는 도메인 비즈니스 규칙을 두지 않는다(§7). 분류·dictionary·외부 API 호출·로그 저장은 prego-control-plane(및 합의된 저장소)에서 수행한다.
8.2 1차 — 내부 “무료 이메일” dictionary 관리 UI
- 목적: 운영자가 등록된 무료·공용 도메인(및 필요 시 패턴) 목록을 직접 조회·검색·추가·삭제할 수 있게 하여, 오탐(회사 도메인이 무료 리스트에 들어간 경우 등)을 빠르게 수정한다.
- 화면 위치: 기존 Trial email filter (test) 관리자 화면에 섹션 또는 하위 페이지로 추가(동일 인증·내부 라우트 범위 내).
- 기능 요구:
- 목록 조회·페이지네이션(또는 가상 스크롤) 및 키워드 검색(도메인 부분 일치 등).
- 단건/다건 삭제, 추가(도메인 정규화 규칙: 소문자·FQDN 등 — 구현 시 단일 함수로 고정).
- 변경 시 감사 가능성: 누가·언제·무엇을 바꿨는지는 §8.5와 연계해 최소 메타데이터를 남길지 기획에서 결정(내부 Bearer만 접근한다고 해도 운영 분쟁 대비 권장).
8.3 2차 — 외부 API library 연동 및 관리자에서의 “API별 테스트”
- 목적: 1차를 통과한 주소에 대해, 외부 서비스 A, B, … 로 동일 입력을 보내 결과를 비교할 수 있게 하여, 라이브러리 교체·가중치·fallback 순서를 검증한다.
- 관리자 화면:
- 통합 미리보기: 한 번의 이메일 입력으로 최종 파이프라인 결과(회사 여부·manual 여부·
reasonCode등)를 본다(기존 preview JSON 확장). - 외부 API별 테스트: 선택한 단일 외부 연동만 호출하는 dry-run/미리보기 모드(비용·쿼터 보호를 위해 일일 한도·운영자 전용 여부를 명시).
- 통합 미리보기: 한 번의 이메일 입력으로 최종 파이프라인 결과(회사 여부·manual 여부·
- 응답에 포함할 정보(기획):
- 최종:
회사 이메일로 판정되었는지(예:isCompanyEmail또는 기존classification/allowedForAutoTrial과 정합되는 필드). - 단계별:
matchedSources를 확장하거나 별도 배열로 1차 dictionary 히트 여부, 2차 어떤 API가 어떤 라벨을 반환했는지(성공/스킵/실패/타임아웃)를 구분해 표시.
- 최종:
8.4 Preview·공개 API 응답에 “회사 메일 여부” 명시
- 사용자·운영자 모두 혼동을 줄이기 위해, 필터링 결과 JSON에 다음을 일관되게 포함한다(이름은 OpenAPI·CP 구현 시 확정).
- 최종 판정: 회사 이메일로 확인되었는지, 아닌지(및 수동 승인 안내가 필요한지).
- 파이프라인 투명성: 1차만으로 결정 났는지, 2차 외부 API까지 갔는지,
mxCheck등 기존 필드와 모순 없이 병기.
공개 계약을 바꿀 경우: prego-zuplo OpenAPI → 라우트 → CP → www 순(§5.3).
8.5 이메일 주소 감사 로그 · 리포팅
- 목적: 사용자가 입력한 이메일 주소(또는 정규화된 도메인)에 대해, 나중에 “manual 신청으로 안내된 주소” 와 “회사 이메일로 확정된 주소” 를 관리자 화면에서 목록·필터로 확인한다.
- 수집 시점: 실제 공개 trial check API 호출 시(필요 시 preview 전용은 제외하거나 플래그로 구분) — 중복·샘플링 정책(동일 세션 반복 입력 등)은 구현 전에 정한다.
- 관리자 UI:
- 테이블/내보내기(CSV 등): 기간·최종 판정(manual vs company)·도메인·
reasonCode필터. - 개인정보: 이메일은 PII이므로 보관 기간·접근 역할·마스킹(로컬파트) 여부를 §7 및 GDPR/내부 정책과 정합되게 별도 한 줄로 명시한다.
- 테이블/내보내기(CSV 등): 기간·최종 판정(manual vs company)·도메인·
8.6 구현 순서 제안 (기획)
- 1차 dictionary 저장소·API·관리자 CRUD(읽기 우선 후 쓰기).
- 파이프라인 응답 필드 정리(Preview → 공개 API 정합).
- 2차 외부 API 연동 및 관리자 “API별 테스트”.
- 감사 로그 저장 및 관리자 리포트.
9. 참고 링크
- Trial 이메일 필터 — HTTP 오류 진단 기획
- www /trial — Zuplo·Cloudflare 통합 기획 (경로가 다르면 허브 platform/index에서 최신 링크 확인)