UNPKG

@roottale/cms-mcp

Version:

RootTale CMS integration MCP server — bundled integration docs, Next.js example code, and public API lookup tools. Run with: npx @roottale/cms-mcp

168 lines (135 loc) 6.55 kB
--- title: 상담문의(리드) 연동 description: submitInquiry 서버 액션으로 문의 폼을 어드민 CRM에 연결 --- # 상담문의(리드) 연동 사이트의 문의 폼 제출을 RootTale로 보내면 어드민 **CRM(받은문의)** 에서 관리됩니다. 블로그 조회와 **같은 API 키 하나**로 동작하며, 키가 테넌트를 식별하므로 별도 식별자가 필요 없습니다. 개인정보(이름·연락처 등)는 서버에서 암호화 저장됩니다. ## 권장 — Next.js Server Action ```ts // lib/actions/submit-contact.ts "use server"; import { submitInquiry } from "@roottale/cms-client/server"; export interface ContactState { status: "idle" | "success" | "error"; message?: string; errors?: Partial<Record<"name" | "phone" | "privacyConsent", string>>; } export async function submitContact( _prev: ContactState, formData: FormData, ): Promise<ContactState> { const name = (formData.get("name") as string | null)?.trim() ?? ""; const phone = (formData.get("phone") as string | null)?.trim() ?? ""; const message = (formData.get("message") as string | null)?.trim() ?? ""; const privacyConsent = formData.get("privacyConsent") !== null; const errors: ContactState["errors"] = {}; if (!name) errors.name = "이름을 입력해주세요."; if (!phone || phone.length < 7) errors.phone = "연락처를 입력해주세요."; if (!privacyConsent) errors.privacyConsent = "개인정보 수집·이용에 동의해주세요."; if (Object.keys(errors).length > 0) { return { status: "error", message: "필수 항목을 입력해주세요.", errors }; } const result = await submitInquiry({ apiKey: process.env.ROOTTALE_API_KEY!, baseUrl: process.env.ROOTTALE_API_BASE, fields: { vertical: "tax", // consulting | medical | tax | legal contactName: name, businessName: name, // 사업체명 미수집 폼이면 이름으로 대체 email: `noemail-${name}@example.invalid`, // 이메일 미수집 폼이면 placeholder phone, // 자동으로 010-1234-5678 형태 포맷됨 message: message || undefined, privacyConsent: true, // 사용자가 명시 동의한 경우에만 true }, }); if (result.ok) { return { status: "success", message: "상담 문의가 접수되었습니다." }; } return { status: "error", message: result.message }; } ``` 클라이언트 폼에서는 `useActionState(submitContact, { status: "idle" })`로 연결합니다. ## 필드 레퍼런스 (`SubmitInquiryFields`) | 필드 | 필수 | 설명 | |---|---|---| | `vertical` | | `consulting` \| `medical` \| `tax` \| `legal` | | `contactName` | | 이름 | | `businessName` | | 사업체명 (미수집 시 이름으로 대체) | | `email` | | 이메일 (`.+@.+\..+`) | | `phone` | | 전화번호 (자동 한국식 포맷) | | `privacyConsent` | | 개인정보 수집·이용 동의 — 반드시 사용자 명시 동의 | | `message` | | 문의 내용 | | `consultationField` | | 상담 분야 라벨 | | `currentSiteUrl` | | 현재 사이트 URL | | `overseasTransferConsent` | medical 시 | 국외이전 동의 | | `leadKind` | | `patient`(기본) \| `sales` | | `extras` | | 임의 추가 항목 (최대 50개, 암호화 보관, CRM 상세에 노출) | | `attribution` | | 유입 first-touch — 아래 [유입 어트리뷰션](#유입-어트리뷰션-attribution) 참고 | `extras`에 개인정보가 담길 수 있으므로 폼의 동의 고지에 수집 항목을 반영하세요. ## 유입 어트리뷰션 (`attribution`) 문의가 **어느 글·검색·단축링크/QR에서 왔는지**CRM에 표시하려면 두 줄만 추가하면 됩니다. RootTale 비콘이 방문자의 first-touch(처음 도착한 경로·`rt_src` 토큰·utm·외부 referrer 호스트명)를 30일간 기억하며, `readAttribution()`(브라우저 전용, `@roottale/cms-client/attribution`)으로 읽습니다. 식별자가 아니므로 개인정보가 아닙니다. 1. 폼 안에 hidden input 추가 (클라이언트 컴포넌트): ```tsx "use client"; import { useEffect, useState } from "react"; import { readAttribution } from "@roottale/cms-client/attribution"; export function AttributionField() { const [value, setValue] = useState(""); useEffect(() => { const attribution = readAttribution(); if (attribution) setValue(JSON.stringify(attribution)); }, []); return <input type="hidden" name="attribution" value={value} />; } // 사용: <form action={...}> ... <AttributionField /> ... </form> ``` 2. Server Action에서 파싱해 전달: ```ts import { parseAttributionJson, submitInquiry } from "@roottale/cms-client/server"; const attribution = parseAttributionJson(formData.get("attribution")); // 깨진 값은 null const result = await submitInquiry({ apiKey: process.env.ROOTTALE_API_KEY!, fields: { /* ...표준 필드 */ attribution }, }); ``` `InquiryAttribution` 형태 (모든 필드 선택): | 필드 | 설명 | |---|---| | `landing_path` | 첫 방문 landing pathname | | `rt_src` | 단축링크/QR 토큰 (`roottale.link` 경유 시) | | `utm_source` / `utm_medium` / `utm_campaign` | UTM 파라미터 | | `referrer` | 외부 referrer **호스트명** (raw URL 아님) | | `first_touch_at` | first-touch 시각 (ISO 8601) | 직접 HTTP 연동 시에는 같은 값을 `attr_landing_path`, `attr_rt_src`, `attr_utm_source`, `attr_utm_medium`, `attr_utm_campaign`, `attr_referrer`, `attr_first_touch_at` 폼 필드로 보내면 됩니다. ## 에러 처리 `submitInquiry`는 throw 하지 않고 구조화된 결과를 반환합니다: ```ts type SubmitInquiryResult = | { ok: true } | { ok: false; code: string | null; message: string }; // message = 한국어 사용자 메시지 ``` | `code` | 의미 | |---|---| | `consent_privacy` | 개인정보 동의 누락 | | `consent_overseas` | medical인데 국외이전 동의 누락 | | `missing_fields` | 필수 필드 누락 | | `invalid_email` | 이메일 형식 오류 | | `invalid_vertical` | 허용되지 않는 vertical | | `invalid_api_key` | 키 인증 실패 | | `internal` | 서버/네트워크 오류 | ## 대안 — RootTaleLeadForm 컴포넌트 자체 폼 없이 빠르게 붙일 때는 `@roottale/cms-renderer-next`의 `RootTaleLeadForm`(RSC, HTML form)을 사용할 수 있습니다. 디자인·검증을 통제하려면 위의 Server Action 방식을 권장합니다. raw HTTP로 직접 연동(비 JS 스택)하려면 `api-reference.md`의 `POST /v1/public/inquiries`를 참고하세요.