/** * webforai.kr 문의 폼 수신기 (Google Apps Script 웹앱) * * 하는 일 * 1. 폼 POST 를 받아 구글시트에 행 추가 * 2. 같은 실행 안에서 알림 메일 발송 (sales@o2o.kr, haewonkam@o2o.kr) * * 왜 트리거가 아니라 여기서 메일을 보내는가 * 시트 onChange 트리거는 스크립트가 쓴 변경에는 늦거나 누락되는 경우가 있다. * 행을 추가한 직후 같은 실행에서 보내면 "시트에 들어오면 알림" 을 가장 확실하게 지킨다. * * ───────────────────────────────────────────────────────────── * 설치 순서 (haewon 이 직접) * * 1. 구글시트를 새로 만든다. 이름 예: "webforai 문의 접수" * 2. 시트에서 확장프로그램 → Apps Script 를 연다. * 3. 기본 Code.gs 내용을 지우고 이 파일 전체를 붙여넣는다. * 4. 아래 SHARED_TOKEN 을 임의의 문자열로 바꾼다 (예: 영문+숫자 24자). * 빈 문자열로 두면 토큰 검사를 하지 않는다. * 5. 배포 → 새 배포 → 유형 "웹 앱" * - 실행 계정: 나 * - 액세스 권한: 모든 사용자 ← 이 값이어야 사이트에서 호출된다 * 배포하면 https://script.google.com/macros/s/AKfyc.../exec 주소가 나온다. * 6. 첫 배포 때 권한 승인을 한 번 묻는다. 승인한다 * (시트 쓰기 + 메일 발송 권한). * 7. 그 주소를 Vercel 환경변수에 넣는다 (infinith-discovery 프로젝트): * VITE_LEAD_ENDPOINT = https://script.google.com/macros/s/AKfyc.../exec * VITE_LEAD_TOKEN = 4번에서 정한 문자열 (토큰을 쓸 때만) * 넣은 뒤 재배포해야 반영된다 (Vite 는 빌드 시점에 값을 넣는다). * * 코드를 고친 뒤에는 반드시 "배포 → 배포 관리 → 편집 → 버전: 새 버전" 으로 * 다시 배포해야 반영된다. 저장만 하면 기존 배포는 옛 코드 그대로다. * ───────────────────────────────────────────────────────────── */ /** 4번에서 정한 값. 사이트의 VITE_LEAD_TOKEN 과 같아야 한다. 빈 문자열이면 검사 안 함. */ var SHARED_TOKEN = ''; /** 알림 받을 주소. */ var NOTIFY_TO = 'sales@o2o.kr,haewonkam@o2o.kr'; /** 행이 쌓일 시트 탭 이름. 없으면 만든다. */ var SHEET_NAME = '문의'; var HEADERS = ['접수시각', '성함', '회사', '전화', '이메일', '문의내용', '유입', 'User-Agent']; function doPost(e) { try { var body = {}; if (e && e.postData && e.postData.contents) { body = JSON.parse(e.postData.contents); } if (SHARED_TOKEN && body.token !== SHARED_TOKEN) { return json_({ ok: false, error: 'unauthorized' }); } // 이름이 비어 있으면 받지 않는다. 사이트 폼도 같은 규칙으로 막는다. var name = trim_(body.name); if (!name) { return json_({ ok: false, error: 'name required' }); } var row = [ new Date(), name, trim_(body.company), trim_(body.phone), trim_(body.email), trim_(body.message), trim_(body.source), e && e.parameter ? trim_(e.parameter.ua) : '', ]; appendRow_(row); notify_(row); return json_({ ok: true }); } catch (err) { // 실패를 삼키지 않는다. 실행 로그에 남기고 사이트에도 실패를 알린다. console.error('doPost 실패: ' + err); return json_({ ok: false, error: String(err) }); } } /** 브라우저로 주소를 열었을 때 살아 있는지 확인용. */ function doGet() { return json_({ ok: true, service: 'webforai lead intake' }); } function appendRow_(row) { var ss = SpreadsheetApp.getActiveSpreadsheet(); var sh = ss.getSheetByName(SHEET_NAME); if (!sh) { sh = ss.insertSheet(SHEET_NAME); } if (sh.getLastRow() === 0) { sh.appendRow(HEADERS); sh.getRange(1, 1, 1, HEADERS.length).setFontWeight('bold'); sh.setFrozenRows(1); } sh.appendRow(row); } function notify_(row) { var subject = '[webforai 문의] ' + row[1] + (row[2] ? ' · ' + row[2] : ''); var lines = [ '성함: ' + (row[1] || '-'), '회사: ' + (row[2] || '-'), '전화: ' + (row[3] || '-'), '이메일: ' + (row[4] || '-'), '유입: ' + (row[6] || '-'), '', '문의 내용:', row[5] || '-', '', '시트: ' + SpreadsheetApp.getActiveSpreadsheet().getUrl(), ]; MailApp.sendEmail({ to: NOTIFY_TO, subject: subject, body: lines.join('\n'), // 회신하면 문의한 사람에게 바로 가게 한다. 이메일을 안 남겼으면 설정하지 않는다. replyTo: row[4] || undefined, }); } function json_(obj) { return ContentService.createTextOutput(JSON.stringify(obj)).setMimeType( ContentService.MimeType.JSON ); } function trim_(v) { return v === null || v === undefined ? '' : String(v).trim(); } /** * 설치 확인용. Apps Script 편집기에서 이 함수를 한 번 실행하면 * 시트에 테스트 행이 들어가고 알림 메일이 오는지 바로 볼 수 있다. */ function testRun() { var row = [new Date(), '테스트', '설치 확인', '010-0000-0000', 'test@example.com', '설치 확인용 테스트입니다.', 'test', '']; appendRow_(row); notify_(row); }