👋 소개: 왜 이런 짓을 시작했나요?
안녕하세요! 허세임입니다.
지난 3년간 운영해 온 구글 폼만 수십 개가 넘습니다. 강의 신청, 강의 후기 등등... 그런데 가장 큰 문제는 이 소중한 고객 데이터가 구글 드라이브 구석구석에 '파편화(Fragmentation)'되어 있다는 점이었습니다.
문제 1: 어떤 폼은 시트와 연결돼 있지만, 어 떤 폼은 시트 없이 응답만 쌓여있음 (일명 '고아 폼').
문제 2: A라는 고객이 3년 전 이벤트에도 참여하고, 어제 문의도 남겼는데, 이 두 사람이 동일 인물인지 알 방법이 없음.
문제 3: 고객들이 주관식으로 정성껏 남겨준 니즈(Needs)와 불만 사항들이 엑셀 텍스트로만 남아서 활용되지 못하고 썩고 있음.
그래서 결심했습니다. "이 흩어진 조각들을 하나로 모으자!"
단순한 엑셀 취합이 아니라, 살아있는 CRM 데이터베이스를 구축하는 '고객디비 조각모음' 프로젝트의 기록을 공유합니다.
🧠 설계 전략: 무작정 코딩하기 전에 (가장 중요!)
개발에 들어가기 앞서, "비용은 줄이고, 데이터 퀄리티는 높이기 위해" 4가지 핵심 원칙을 세웠습니다. 이 부분이 저의 노하우입니다.
0. "그냥 쌓는 건 창고, 써먹어야 무기고" (CRM DB 설계)
코드를 짜기 전에 가장 먼저 한 일은 "어떤 데이터를 남길 것인가?"를 정의하는 것이었습니다.
단순히 엑셀에 데이터를 쌓아두는 건 '창고'일 뿐입니다. 마케터가 필요할 때 바로 꺼내 쓸 수 있는 '무기고'를 만들기 위해, 고객DB의 각 열(Column) 하나하나에 CRM 마케팅 목적을 심어두었습니다.
이메일 (Unique Key):
목적: 중복 제거의 기준입니다. 구글 폼은 익명성이 강해서 닉네임이나 전화번호는 바뀔 수 있지만, 이메일은 잘 안 바뀝니다. 3년 동안 5번 응답한 고객 데이터를 5줄이 아닌 '1줄의 깊이 있는 히스토리'로 합치기 위함입니다.
응답수 (Loyalty Score):
목적: 우리 브랜드의 '찐팬' 식별기입니다. 한 번 스쳐 지나간 사람과, 매번 이벤트에 참여하는 사람은 다르게 대우해야죠. 이 숫자가 높은 분들은 따로 관리해서 신규 상품 런칭 시 가장 먼저 알립니다.
처음 & 마지막 활동일 (Customer Lifecycle):
목적: 고객의 수명주기를 파악합니다.
처음: "가입 1주년 축하" 같은 기념일 마케팅 용도.마지막: 마지막 응답일로부터 6개월이 지났다면? 이탈 위험군(Churn Risk)입니다. "잘 지내시나요?" 메일을 보내 잠든 고객을 깨우는 용도입니다.
관심사 (Hyper-Segmentation):
목적: 초개인화 마케팅의 핵심입니다. 모든 사람에게 똑같은 뉴스레터를 보내면 스팸 취급받습니다. Gemini가 분석해 준 "n8n" 태그가 있는 사람에게만 [n8n 세미나]를, "클로드코드" 태그가 있는 사람에게만 [바이브코딩 클래스] 정보를 보내 오픈율을 극대화합니다.
그리고 이 고객DB를 만들기 까지 자동화를 위한 시트 3장
1. "Human-in-the-loop" (수집 여부는 내가 정한다)
처음엔 모든 폼을 다 분석하게 할까 했습니다. 하지만 '오늘 점심 메뉴 조사' 같은 의미 없는 폼까지 Gemini API를 태우면 돈 낭비, 시간 낭비잖아요?
그래서 수집여부라는 열을 만들고 기본값은 비워뒀습니다.
스크립트가 리스트를 쫙 뽑아오면, 제가 눈으로 보고 "이건 분석 가치가 있다" 싶은 것만
1을 입력합니다.스크립트는 오직
1이 적힌 시트만 들어가서 데이터를 퍼옵니다. 자동화 속에 수동 검수를 딱 한 스푼 넣어서 효율을 잡았습니다.
2. "서술형(Paragraph)" 유무로 가치 판단
단답형이나 객관식만 있는 폼은 굳이 AI가 분석할 필요가 없습니다.
스크립트가 폼을 1차로 훑을 때, 장문형(주관식) 질문이 포함되어 있는지 미리 파악해서 주관식여부 열에 자동으로 체크하게 했습니다. "주관식이 있다? = 고객의 진짜 속마음이 들어있다"는 뜻이니, 우선순위 판단에 큰 도움이 됩니다.
3. 관심사 키워드는 '별도 시트'로 통제
Gemini에게 "이 사람 관심사 뽑아줘"라고만 하면, 같은 뜻인데도 '살빼기', '체중감량', '다이어트'처럼 제각각인 태그를 뱉어냅니다. 이러면 나중에 필터링이 안 됩니다.
그래서 '관심사' 시트를 따로 만들고 우리 비즈니스에 중요한 표준 키워드(Standard Keywords)를 미리 정의했습니다.
Gemini에게 "다른 말 쓰지 말고, 꼭 이 리스트 안에 있는 단어로만 분류해!"라고 시키니 데이터가 아주 깔끔해졌습니다.
4. '고아 폼' 구출 작전
가장 골치 아픈 게 "응답은 받았는데 시트 연결을 안 해둔 폼"입니다. 이건 드라이브에서 '스프레드시트'만 검색하면 절대 안 나옵니다.
그래서 스크립트가 '구글 폼' 파일 자체를 전수 조사해서, 연결된 시트가 없으면 강제로 새 시트를 만들고 연결한 뒤 가져오도록 설계했습니다. 덕분에 죽어있던 데이터까지 살려냈습니다.
🛠️ 진행 방법: 도구와 코드
사용 도구
Google Apps Script (GAS): 전체 오케 스트레이션
Gemini 2.5 Pro (API): 비정형 데이터 분석 & 헤더 매핑
Google Sheets와 Forms: DB 저장소
Gemini 3: 바이브코딩
Step 1. 흩어진 조각 모으기 (Listing)
먼저 3년 치 드라이브를 뒤져서 '고객조각'시트에 리스트를 만듭니다. 이때 폼과 시트를 크로스 체크해서 누락을 방지합니다.
모든 폼과 시트를 다 불러 올 때, 주관식, 서술형 문항이 있었는지도 파악하여 주관식 여부를 표시해주었습니다. 다음단계의 관심사 조사를 위해!
이제 저의 3년치 구글폼 중에서, 고객DB에 넣을 폼만 수집하도록 수집여부 열에 체크를 해 주었습니다.
네, 맞습니다! 전체 숲(폼의 주제)을 먼저 보고, 그 다음에 나무(개별 고객)를 봐야죠. 순서가 그게 훨씬 논리적입니다.
Step 2에 '폼 주제 분석' 내용을 넣고, 기존의 고객 개별 처리 내용을 Step 3로 미뤄서 전체 흐름을 완성해 드리겠습니다. 이대로 복사해서 쓰시면 됩니다.
Step 2. 폼의 정체성 파악하기 (Form-level Topic Tagging)
리스트업이 끝났다면, 이제 각 폼이 "도대체 무슨 주제인지" 파악해야 합니다.
폼 제목이 '10월 이벤트'라고만 되어 있으면, 이게 바이브코딩 이벤트인지 AI 주식 투자 이벤트인지 나중엔 기억도 안 나거든요.
그래서 폼의 제목, 설명, 질문을 싹 긁어서 Gemini에게 던져주고 물어봤습니다.
"이 폼 내용을 읽어보고, 미리 정해둔 [관심사 키워드 리스트] 중에서 가장 적합한 태그를 골라줘!"
이렇게 하면 폼에 응답한 모든 사람에게 적용될 '기본 관심사 태그'가 생성됩니다. (예: '다이어트 챌린지' 폼 -> 기본 태그: 다이어트)
💡 활용 프롬프트 (Strict Mode)
Gemini가 엉뚱한 단어를 창조하지 못하게 "Allowed Keyword List"를 주고 그 안에서만 고르라고 시켰습니다.
JavaScript
const prompt = `
You are an expert data analyst.
Analyze the provided Google Form content (Title, Description, Questions).
Constraints:
1. Select 1 to 3 keywords that best match the form's topic.
2. YOU MUST ONLY SELECT KEYWORDS FROM THE PROVIDED LIST BELOW. Do not invent new words.
---
Allowed Keyword List: ["다이어트", "주식", "부동산", "영어회화", "육아", "캠핑"...]
---
Target Google Form Content:
${formText}
`;
Step 3. 고객 한 명 한 명 DB로 이사시키기 (Individual Processing)
폼의 주제(공통 관심사)를 파악했으니, 이제 본격적으로 수집여부가 1이고 주관식 여부가 1인 시트를 열어서 고객 데이터를 '고객DB'로 옮깁니다.
이 과정에서 Gemini가 두 번 활약합니다.
1) 헤더 자동 매핑 (Header Mapping)
시트마다 "이름", "성명", "Name"... 컬럼명이 제각각이죠?
Gemini에게 헤더 한 줄과 샘플 데이터 한 줄을 주고 "어디가 이메일이고 어디가 이름이냐?"라고 물어봐서 인덱스를 찾게 했습니다. 정규식 짜는 것보다 훨씬 정확합니다.
2) 주관식 답변 심층 분석 (Deep Analysis)
앞서 Step 2에서 폼의 내용으로 관심사를 추출했지만, 혹시 있을 고객 개인의 디테일한 관심사를 파악하기위해 스크립트가 장문형(서술형) 답변을 발견하면, 그 내용을 Gemini에게 보내 개인별 맞춤 키워드를 추가로 뽑아냅니다.
Form 태그:
CRM(공통)AI 분석 태그:
n8n,바이브코딩(개인 답변에서 추출)최종 DB:
CRM, n8n, 바이브코딩
이렇게 하면 나중에 "바이브코딩"만 따로 찾아서 케어할 수 있게 됩니다.
💻 핵심 로직 코드
JavaScript
function processSingleSheet(...) {
// 1. Gemini가 헤더를 보고 열 번호를 찾아줌
let map = getColumnMappingFromGemini(headerRow, sampleRow);
for (let row of data) {
// 2. 주관식(서술형) 답변이 있으면 Gemini가 개인 관심사 추출
if (deepAnalyze === "1") {
let personalInterests = getInterestsFromGemini(userAnswer, keywordList);
}
// 3. [폼 공통 태그] + [개인 태그] 합쳐서 DB에 저장 (Upsert)
mergeToCustomerDB(email, formTag, personalInterests);
}
}
📊 결과와 배운 점
우여곡절 끝에 '고객DB' 시트 하나에 모든 정보가 모였습니다. 각 열(Column)은 CRM 마케팅 목적으로 쓰기엔 아직 부족하지만, 점차 확장시켜나갈 계획입니다.
2. 시행착오와 꿀팁
🤖 Gemini 모델 버전:
정신나간 제미나이가 자꾸 1.5가 최신버전이라고 우겨서, 말을 안들어요. 이것때문에 계속 에러가... 구형 모델 말고 최신
gemini-2.5-pro를 쓰세요.
3. 앞으로의 계획
이제 3년치 데이터 조각모음이 끝났으니, 다음 단계는 "자동화된 개인화 메시지 발송"입니다. Gemini가 뽑아준 '관심사'를 기반으로, 본격적인 CRM 자동화를 해볼 생각입니다!
😘