Skip to content

마케팅 문의를 연락처 기준으로 WARP CRM에 자동 등록 - #49

Merged
OziinG merged 4 commits into
mainfrom
feat/marketing-inquiry-crm-sync
Sep 17, 2026
Merged

OziinG merged 4 commits into
mainfrom
feat/marketing-inquiry-crm-sync

Conversation

@SUMZ711

@SUMZ711 SUMZ711 commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

마케팅 수집기의 문의를 전용 인증키로 받아 WARP CRM에 등록합니다. 신규 기본 연락처는 B2C 잠재고객으로 만들고, 동일 연락처의 기존 고객이 한 명이면 프로필을 보존하며 문의 이력만 추가합니다. 여러 고객의 연락처가 겹치면 409로 보류합니다.

원본 UUID의 대소문자를 정규화해 동일 문의의 재전송이 중복 이력을 만들지 않도록 했습니다. 독립 서버 프로세스가 공유 SQLite에 동시에 쓰는 경우에는 읽기 전에 writer를 확보하고 전체 트랜잭션을 비동기로 재시도합니다. 실패한 append는 원자적으로 rollback하며, 서버 로그에는 허용된 오류 코드와 재시도 여부만 남깁니다.

공용 HTTPS API는 IP 허용목록 없이 전용키로 인증합니다. 로컬 개발자는 동적 Wi-Fi 공인 IP로 접속할 수 있고 고정 IP/EIP는 필요하지 않습니다. 고정된 원본 범위와 16KB 요청 제한을 적용하며 기존 BUILDUP 키·DB 스키마·전역 DB 설정은 변경하지 않습니다. 삭제된 활동의 재전송까지 영구 차단하지 않는 보관 기간 기준의 중복 방지 계약을 명시했습니다.

검증 (07224a2209f3089e06a67f44905607f9fe0f1cf1):

  • Python 배포 검사 29개, Node 검사 51개 통과. 수신기 12개에는 UUID 대소문자, 독립 DB 연결 경합/후속 쓰기, append 실패 rollback/로그 비공개 검사가 포함됩니다.
  • 연동 계약 7개, 타입 검사, 수정 파일 ESLint, 기존 보안 정책의 감사 통과.
  • 프로덕션 빌드 및 독립 서버 프로세스 3개를 사용한 HTTP 검사 9개 통과. 합성 DB에서 동시 재전송, 서로 다른 동일 연락처 문의, 프로필 보존, 인증·입력 오류·미설정 차단을 확인했습니다.
  • 독립 코드 검토 APPROVE 및 구조 검토 CLEAR.

배포는 OziinG 검토 후 정확한 main에서 기존 release 파이프라인으로 수행합니다. 배포 Revision·이미지 digest·SSM 버전·공개 인증 검증·관찰 결과는 댓글로 기록합니다. 담당자 연결 안내와 CRM 쓰기 없는 인증 확인 명령은 docs/integrations/WARP_MARKETING_INQUIRIES.md에 있습니다.

실제 로컬 마케팅 발신기의 문의 전송·재전송·CRM readback 및 미전달 일괄 처리는 발신 담당자의 연동 확인 단계로 남깁니다. 이 PR의 WARP 배포와 실제 수집 완료를 구분하기 위해 Issue #48은 계속 열어 둡니다.

Refs #48

@SUMZ711
SUMZ711 marked this pull request as ready for review September 16, 2026 07:26
@SUMZ711
SUMZ711 requested a review from OziinG September 16, 2026 07:26
@SUMZ711

SUMZ711 commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

@OziinG 마케팅 문의의 WARP CRM 자동 등록 기능에 대해 PR #49 및 Issue #48 검토와 운영 반영 승인을 요청드립니다.

신규 연락처는 성함·연락처를 가진 고객으로 등록하고, 기존 고객 한 명과 연락처가 일치하면 프로필을 유지하며 문의 이력만 추가합니다. 여러 고객과 일치하면 자동 처리를 보류하고, 같은 문의의 재전송은 중복 등록하지 않습니다.

변경본 49c1974a34686cdc97196cdeea7f2af47a40123b의 GitHub 연동 검사와 비밀정보 검사가 통과했습니다. 운영 배포·전용 연결키 설정·실제 고객 등록은 아직 진행하지 않았습니다.

연동 계약과 변경 내용을 검토하신 뒤 운영 반영 승인 여부를 남겨주세요. 승인 시 전용 WARP_MARKETING_API_KEY 설정과 기존 release 절차를 통한 배포 진행 방식도 확인 부탁드립니다. 담당자 승인 전까지 운영 반영은 보류하겠습니다.

@OziinG OziinG left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

검토 결과, 머지 전에 아래 중복 방지 결함을 수정합니다.

  • [P2] 동일 UUID의 대소문자 표기를 같은 문의로 처리해야 합니다. lib/marketing-inquiries.ts:89는 UUID 검증 시 대소문자를 허용하지만 활동 ID를 만들 때 원문을 해시합니다. ABCDEFAB-CDEF-4ABC-8DEF-ABCDEFABCDEF를 먼저 전송한 후 같은 UUID의 소문자 표기를 재전송하면 두 번째 응답이 created: true, duplicate: false이고 활동이 추가됩니다. 합성 SQLite 회귀 테스트에서 재현했습니다. 해시 입력의 원본 UUID를 소문자로 정규화하고 재전송 회귀 테스트를 추가하겠습니다. 작은 수신기 결함이므로 이 PR에서 직접 수정합니다.

현재 확인한 검증: 기존 Python 배포 검사 29개, Node 검사 48개, 연동 계약 7개, 타입 검사, 수정 파일 ESLint, 기존 정책의 보안 감사 통과. 별도 코드/구조 검토와 프로덕션 HTTP 검증은 진행 중입니다.

운영은 현재 main 2874eec1199d5e0e04d2b058bf5b8d631245ddcf이며 /api/readyz가 정확한 Revision과 이미지 digest로 정상 응답합니다. SSM /evn-warp/app-env 버전 202에는 전용 마케팅 연결키가 아직 없습니다. 수신기 배포와 발신측 활성화/실제 고객 등록 확인은 구분해 기록하겠습니다. 비밀값과 고객 자료는 기록하지 않았습니다.

@OziinG

OziinG commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

추가 검토에서 [P1] 독립 서버/DB 연결 간 동시 수신 시 SQLite 잠금이 남는 문제를 재현했습니다.

기존 동시 요청 테스트는 Prisma 클라이언트 한 개만 사용합니다. 독립 클라이언트 두 개 및 프로덕션 빌드의 별도 서버 프로세스 두 개가 같은 SQLite DB에 동일 문의를 보내면 둘 다 503이 반환되었습니다. 독립 클라이언트 테스트에서는 후속 조회·쓰기까지 P1008로 실패했습니다. appendInquiry의 deferred transaction이 조회 후 쓰기로 전환되는 과정이며, Blue/Green 컨테이너가 공유 DB를 쓰는 운영 구조에서 무시할 수 없습니다.

직접 수정 범위는 수신기 트랜잭션에 한정합니다. 조회 전에 행을 바꾸지 않는 UPDATE로 SQLite 쓰기 잠금을 확보하고, 트랜잭션 연결의 busy timeout을 0으로 설정해 기존 비동기 재시도가 Node 이벤트 루프를 막지 않도록 했습니다. 전역 DB 설정·스키마·배포 파이프라인은 변경하지 않습니다. 독립 클라이언트의 동시 요청 및 이후 DB 쓰기 회귀 테스트를 추가했으며 현재 11개 수신기 테스트가 통과했습니다. 별도 서버 프로세스 HTTP 재검증과 독립 코드 재검토를 마친 뒤 머지 여부를 확정하겠습니다.

또한 중복 방지 보장은 수신 활동이 보존되는 동안에 적용된다는 점을 계약에 명시하겠습니다. 사용자의 명시적 고객/활동 삭제 후 재전송까지 영구 차단하는 새 저장소는 이번 범위에 추가하지 않습니다. 실제 발신측 활성화는 발신측 계약/키 확인 후 진행할 별도 운영 단계입니다.

Reserve the writer before phone lookup so concurrent receiver processes cannot deadlock during a deferred transaction upgrade. Canonicalize source UUID spelling and preserve safe failure diagnostics.

Constraint: Blue/Green containers share one SQLite database and callers may use dynamic public IPs.
Rejected: New receipt schema or global adapter replacement | Receiver-local locking and retained-record semantics satisfy the current contract.
Confidence: high
Scope-risk: narrow
Directive: Keep the writer reservation before reads and preserve whole-transaction asynchronous retries.
Tested: 29 deployment Python tests; 51 Node tests; 7 integration endpoints; typecheck; ESLint; security audit; production build; three-process HTTP smoke.
Not-tested: Local marketing sender end-to-end delivery and real CRM readback.

@OziinG OziinG left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

최종 검토 승인합니다. 검토 대상은 07224a2209f3089e06a67f44905607f9fe0f1cf1입니다.

UUID 대소문자에 따른 중복 등록과 독립 SQLite 연결의 잠금 교착을 수정했고, 실패 rollback·잠금 해제·안전한 오류 로그까지 검증했습니다. 독립 코드 검토는 APPROVE, 구조 검토는 CLEAR이며 미해결 머지 차단 사항은 없습니다.

Python 29개·Node 51개, 연동 계약 7개, 타입 검사·수정 파일 ESLint·보안 감사·프로덕션 빌드와 3개 독립 프로세스 HTTP 검사 9개가 통과했습니다. 중복 방지의 삭제/보관 경계와 동적 Wi-Fi IP 개발자의 공용 HTTPS+전용키 접속 방법을 문서화했습니다.

정규 release 이후 exact main Revision·digest·SSM 버전·공개 인증 응답과 연속 가용성을 확인합니다. 로컬 마케팅 발신기의 실제 문의 전송/CRM readback은 별도 증거가 필요하며, 이번 검토에서 고객 운영 데이터는 수정하지 않았습니다.

@OziinG
OziinG merged commit 96e20be into main Sep 17, 2026
2 checks passed
@OziinG

OziinG commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

WARP 수신기 검토·직접 수정·머지·정규 배포·공개 인증 검증과 배포 후 30분 관찰을 완료했습니다.

증거 결과
최종 main 96e20be7d25261342a0dc1e15b553d0460bbe385
PR 검사 35167455969, 성공
ENV validate 35167651336, 성공·트래픽 변경 없음
Release 35167724978, 성공
관찰 후 슬롯 상태 35170028698, 성공
SSM APP ENV 버전 202 → 203, 전용 마케팅 키만 추가·기존 설정 보존
Image digest sha256:668533947fe0c05b4752573699c1a0736362d3d47c36f509ff3d0f4cb2c466dc
ECR OS scan Critical 0 / High 0
최종 슬롯 active=green, previous=blue, candidate=none, 양쪽 running
/api/readyz HTTP 200, exact main Revision·ECR digest 일치
운영 migration 적용 0개, DB 직접 수입·수정 없음
Rollback 실행 불필요·미실행. 이전 blue 슬롯을 실행 상태로 유지
SSM command b7a0a707-e338-4017-9bec-fb9fbafc0f3a
배포 후 관찰 새 Revision 최초 확인 후 1,802초(30분 2초)
공개 연속 검사 배포 전후 총 615회: login 205 / healthz 205 / readyz 205, 실패 0
최종 재확인 main·운영 Revision 일치, SSM 203 유지, 인증 검사 4개 재통과, 로컬 main clean

소스 검증은 Python 29개·Node 51개, 연동 계약 7개, 타입 검사·수정 파일 ESLint·보안 감사·프로덕션 빌드 통과입니다. 별도 서버 프로세스 3개를 사용한 합성 DB HTTP 검사 9개도 통과했습니다. UUID 대소문자 중복과 SQLite read-to-write 잠금 교착을 직접 수정했고, 독립 코드 검토 APPROVE 및 구조 검토 CLEAR를 받았습니다. 가용성 관찰은 위 공개 HTTP 경로의 검사 결과이며 전체 사용자 업무 화면이나 Docker 재시작/OOM 로그 전수 검증을 의미하지 않습니다.

공개 API: POST https://warp.cleversystem.ai/api/external/marketing-inquiries

  • HTTPS 443 보안그룹이 0.0.0.0/0을 허용하며 별도 IP 허용목록은 없습니다. 로컬 Wi-Fi 동적 공인 IP로 사용할 수 있고 EIP가 필요하지 않습니다.
  • 전용 x-api-key가 없거나 틀리면 401입니다. 정상 키로 {}를 보내면 400 bad_payload로 인증·접속을 확인할 수 있습니다. 이 검사를 운영에서 통과했고 CRM에는 쓰지 않았습니다. GET은 405입니다.
  • 키 값은 GitHub·로그에 공개하지 않습니다. 운영 담당자의 보안 전달 경로로 발신 환경에 주입해야 합니다.
  • 담당자 연결 안내에 데이터 쓰기 없는 연결 검사 명령과 실제 문의·재전송 검증 절차를 제공합니다.

Issue #48은 로컬 마케팅 발신 담당자가 검토된 계약과 키를 적용해 실제 문의 한 건 전달 → 같은 sourceId 재전송 → CRM 고객/이력 확인을 완료할 때까지 열어 둡니다. 그 후 미전달 문의를 처리합니다. 이번 실행에서 실제 고객 등록·일괄 전송은 하지 않았습니다.

@OziinG

OziinG commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

@SUMZ711 검토·수정·배포 결과와 로컬 개발 환경에서 진행할 연동 테스트를 아래에 정리합니다.

WARP 수신 API의 운영 배포는 완료됐습니다. 이제 로컬 evn-marketing에서 운영 API에 접속해 연동을 확인할 수 있습니다.

다만 실제 발신기의 문의 전달·재전송·CRM 반영 확인은 아직 수행하지 않았습니다. 이 확인은 Issue #48에 담당자로 배정했으며, 결과를 남길 때까지 Issue를 열어 둡니다.


기존 구현에서 발견한 두 문제는 WARP 측에서 직접 수정했습니다.

  • 같은 UUID의 대소문자 표기가 다르면 문의 이력이 중복 생성되는 문제

    활동 식별값을 계산할 때 원본 UUID를 소문자로 정규화했습니다. 같은 UUID의 대문자·소문자 재전송은 동일한 문의로 처리합니다.

  • 독립 서버 프로세스가 동시에 요청하면 SQLite 잠금이 남는 문제

    기존 동시성 테스트는 DB 연결 하나를 공유해 이 문제를 잡지 못했습니다. 서로 다른 DB 연결 및 별도 서버 프로세스에서 두 요청이 모두 503으로 실패하고 후속 DB 작업도 막히는 상황을 재현했습니다.

    조회 전에 쓰기 잠금을 확보하도록 수신기 트랜잭션을 수정했습니다. 잠금 경합은 전체 트랜잭션 단위로 비동기 재시도하며, 실패 시 고객 생성과 활동 추가가 함께 취소됩니다. 후속 DB 쓰기까지 정상인지 검사했습니다.

DB 오류를 조용히 숨기지 않도록 안전한 오류 코드와 재시도 가능 여부도 기록합니다. 고객 이름·전화번호·API 키·DB 오류 원문은 로그에 남기지 않습니다.

기존 BUILDUP 공유키, DB 스키마, 전역 DB 설정은 변경하지 않았습니다.


로컬에서 사용할 접속 정보는 다음과 같습니다.

항목 설정
주소 https://warp.cleversystem.ai/api/external/marketing-inquiries
메서드 POST
Content-Type application/json
인증 헤더 x-api-key: <전용 마케팅 API 키>
발신 환경변수 WARP_MARKETING_API_KEY
네트워크 HTTPS 443, 모든 IPv4 허용. 개별 IP 등록 없음
요청 크기 최대 16KB

Wi-Fi 연결 시 사용하는 동적 공인 IP로 접근할 수 있습니다. 고정 IP나 EIP, VPN, 운영 서버 SSH 접속은 필요하지 않습니다.

전용키는 WARP 운영 SSM에 설정했고, 실제 공개 API에서 키 인증이 동작하는 것까지 확인했습니다. 발신기에 넣을 키는 OziinG에게 별도 보안 경로로 전달받아 주세요. 기존 BUILDUP 키를 재사용하지 않습니다.

키는 로컬 서버/worker 환경에만 주입하고 브라우저 코드·Git 저장소·GitHub 댓글·실행 로그에 넣지 않습니다.


먼저 CRM에 데이터를 쓰지 않는 연결 검사부터 실행해 주세요.

WARP_MARKETING_API_KEY를 발신 프로세스 환경에 주입한 상태에서 아래 명령을 실행합니다.

node --input-type=module <<'JS'
const key = process.env.WARP_MARKETING_API_KEY
if (!key) throw new Error('WARP_MARKETING_API_KEY is required')

const response = await fetch(
  'https://warp.cleversystem.ai/api/external/marketing-inquiries',
  {
    method: 'POST',
    headers: {
      'content-type': 'application/json',
      'x-api-key': key,
    },
    body: '{}',
    signal: AbortSignal.timeout(10000),
  },
)

const body = await response.json()
if (response.status !== 400 || body.error !== 'bad_payload') {
  throw new Error(`Connection check failed: HTTP ${response.status}`)
}

console.log('인증 및 HTTPS 접근 정상: CRM 데이터는 등록하지 않았습니다.')
JS

이 연결 검사의 성공 기준은 HTTP 400 / bad_payload입니다.

빈 JSON을 의도적으로 보내므로, 정상 키로 인증을 통과한 뒤 입력 검증에서 거절되는 응답입니다. 연결 오류가 아니며 고객·문의 이력은 생성하지 않습니다.

응답 의미와 조치
400 bad_payload 위 연결 검사에서는 정상. 실제 문의 전송에서는 필드·형식·고정 범위를 확인
401 unauthorized 키 누락 또는 불일치. 전달받은 전용키와 헤더 확인
503 not_configured WARP 측 전용키 설정 문제. 운영 담당자 확인 필요
503 temporarily_unavailable DB 처리 일시 실패. 같은 sourceId를 유지하고 간격을 두어 재시도
409 ambiguous_phone 같은 기본 연락처의 고객이 여러 명. 자동 선택·즉시 반복 재시도하지 말고 담당자 확인
405 지원하지 않는 메서드. 브라우저 주소창의 GET 대신 POST 사용

접속 확인 후에는 최신 계약을 반영하고, 미전달 실제 문의 한 건부터 확인해 주세요.

발신측 계약 문서를 검토된 WARP 계약과 맞추고, 발신측 계약 검사와 전달 테스트를 실행합니다. 이번 검토에서는 로컬 evn-marketing 소스에 직접 접근하지 않았으므로 이 부분은 발신 담당자 확인이 필요합니다.

요청은 아래 9개 필드만 사용하며 모두 문자열입니다.

필드 값 또는 형식
source mleverage-admin 고정
companyScopeId 55f9a8bb-73f9-4316-bcd7-7dcdce0bdcc3 고정
homepageScopeId 5c7a6115-a0a9-4e8d-bf65-efce52195fa4 고정
sourceId 원본 문의 UUID. 재전송 시 새로 만들지 않고 같은 값 유지
inquiryDate 원본 문의일자, YYYY-MM-DD
inquiryTime 원본 문의시간, HH:mm. CRM 날짜는 한국 시간으로 해석
sourceStatus 원본 상담상태, 최대 100자
name 원본 성함, 최대 100자. 확인되지 않은 이름은 빈 문자열 허용
phone 원본 연락처, 최대 40자. 숫자로 정규화한 길이 9~15자리

확인 순서는 아래와 같습니다.

  1. 미전달 문의 한 건을 전송합니다.

    HTTP 200과 ok: true, created: true, duplicate: false를 확인합니다. 여기서 created는 문의 활동 추가 여부이며, 고객이 반드시 새로 생성됐다는 의미는 아닙니다.

  2. 같은 문의를 동일한 sourceId로 다시 전송합니다.

    HTTP 200과 created: false, duplicate: true를 확인합니다. 첫 응답과 동일한 customerId, activityId여야 하며 고객·활동이 추가 생성되면 안 됩니다.

  3. WARP CRM에서 실제 반영 결과를 확인합니다.

    신규 연락처는 B2C 잠재고객으로 등록돼야 합니다. 기존 고객 한 명과 기본 연락처가 일치하면 해당 고객에 문의 이력만 추가되고 이름·상태·담당자·메모 등 기존 프로필은 유지돼야 합니다. 문의 이력의 원본 일자·시간·상담상태도 확인합니다.

  4. 한 건의 전달·재전송·CRM 확인이 끝난 뒤 미전달 문의를 처리합니다.

    SQLite 쓰기는 직렬 처리되므로 대량 동시 요청을 시작하기보다 순차 전송 또는 낮은 동시성으로 진행하고, 503은 간격을 두고 재시도합니다.

참고로 중복 방지는 수신 활동이 보관돼 있는 동안에 적용됩니다. 고객이나 수신 활동을 명시적으로 삭제하면 중복 방지 기록도 없어지므로, 이후 같은 문의를 보내면 고객 또는 활동이 다시 생성될 수 있습니다.


테스트 후 Issue #48에 아래 결과를 남겨주시면 연동 완료 여부를 확인할 수 있습니다.

  • 발신측 적용 커밋과 계약 검사·전달 테스트 결과
  • 로컬 Wi-Fi 환경의 인증 검사 성공 여부
  • 최초 전달 / 동일 문의 재전송의 응답 상태와 중복 방지 확인 결과
  • CRM 고객·문의 이력 및 기존 프로필 보존 확인 결과
  • 미전달 문의의 완료·대기·오류 건수와 남은 오류 유형

결과에는 API 키와 고객 개인정보를 넣지 않습니다.

이번 WARP 배포의 최종 커밋은 96e20be7d25261342a0dc1e15b553d0460bbe385입니다. Python 29개·Node 51개, 프로덕션 빌드, 독립 서버 프로세스 HTTP 검사 9개를 통과했습니다. 배포 후 30분 관찰을 완료했고 배포 전후 공개 검사 615회에서 실패는 없었습니다.

상세 배포 증거 · 연결 안내 문서 · 후속 확인 Issue #48

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants