상담문의(리드) 연동
사이트의 문의 폼 제출을 RootTale로 보내면 어드민 CRM(받은문의) 에서 관리됩니다. 블로그 조회와 같은 API 키 하나로 동작하며, 키가 테넌트를 식별하므로 별도 식별자가 필요 없습니다. 개인정보(이름·연락처 등)는 서버에서 암호화 저장됩니다.
권장 — Next.js Server Action
// 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 — 아래 유입 어트리뷰션 참고 |
extras에 개인정보가 담길 수 있으므로 폼의 동의 고지에 수집 항목을 반영하세요.
유입 어트리뷰션 (attribution)
문의가 어느 글·검색·단축링크/QR에서 왔는지를 CRM에 표시하려면 두 줄만
추가하면 됩니다. RootTale 비콘이 방문자의 first-touch(처음 도착한
경로·rt_src 토큰·utm·외부 referrer 호스트명)를 30일간 기억하며,
readAttribution()(브라우저 전용, @roottale/cms-client/attribution)으로
읽습니다. 식별자가 아니므로 개인정보가 아닙니다.
- 폼 안에 hidden input 추가 (클라이언트 컴포넌트):
"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>
- Server Action에서 파싱해 전달:
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 하지 않고 구조화된 결과를 반환합니다:
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를 참고하세요.