시작하기 전에 — 적게 받고, 혼자 봅니다
세 가지를 처음부터 지킵니다.
- 적게 받습니다. 폼에서 필수로 받는 것은 문의 유형과 내용뿐입니다. 연락처는 선택이고, 받은 연락처는
문의함에만 있습니다. - 혼자 봅니다. 이 스프레드시트는 원장님만 엽니다. 강사와 나눠 보려면 이 최소 버전의 범위 밖입니다.
- 목적을 적습니다. 폼 설명란에 "상담 연락 목적으로만 쓰고, 답변 뒤 3개월 안에 지웁니다" 같은 한 줄을 적습니다. 문구와 보관 기간은 학원 사정에 맞게 정하시고, 적은 대로 지키는 것이 중요합니다.
자동 회신도 없습니다. 답변 초안은 원장님이 읽고 고쳐서 본인 손으로 보냅니다. 이전 두 편의 "확정 관문"과 "사람이 결정하는 칸"이 이번 편에서는 "사람이 답하는 칸"입니다.
완성 상태와 준비물
문의함에 한 행이 생기고, 유형과 답변 초안이 채워져 있고, 행이 노랑(미답변)입니다. "환불"이 들어간 문의는 초안 없이 빨강(직접 통화)입니다. 상태를 "답변함"으로 바꾸고 ③을 누르면 색이 사라집니다.| 준비물 | 메모 |
|---|---|
| 구글 계정 | 원장 본인 계정. 트리거는 켠 사람의 계정으로 돕니다. |
| PC의 크롬 브라우저 | 폼 편집기와 스크립트 편집기는 모바일에서 다루기 어렵습니다. |
| 최근에 실제로 받은 문의 5개 | 유형표의 키워드와 초안을 우리 학원 말로 고칠 때 씁니다. 지어내지 마세요. |
1. 폼을 만든다 — 질문 다섯 개
Google Forms · 응답을 스프레드시트로
- forms.google.com에서 새 폼을 만들고 제목을 정합니다. 예:
○○학원 문의실행 확인: 사용자 - 질문 다섯 개를 아래 제목 그대로 만듭니다. 스크립트가 질문 제목으로 답을 찾기 때문에 제목이 다르면 찾지 못합니다.
- 응답 탭에서 스프레드시트에 연결을 눌러 새 스프레드시트를 만듭니다. 이 스프레드시트가 이번 편의 작업 파일입니다. 확인 필요: 한국어 UI의 정확한 버튼 이름
| 질문 제목 (그대로) | 형식 | 필수 | 선택지 |
|---|---|---|---|
문의 유형 | 객관식 | 예 | 상담 신청 · 수강료 · 시간표·반 배정 · 결석·보강 · 기타 |
자녀 반 | 단답 또는 드롭다운 | 아니오 | 우리 학원 반 이름 |
내용 | 장문 | 예 | — |
연락 방법 | 객관식 | 아니오 | 카카오 · 전화 · 답장 불필요 |
연락처 | 단답 | 아니오 | — |
폼의 문의 유형 선택지는 유형표의 유형 이름과 같아야 합니다. 나중에 유형을 늘리면 폼 선택지와 유형표 둘 다 고칩니다.
2. 시트에 스크립트를 붙이고 ① 시트 준비
Apps Script 편집기 · onOpen
- 폼이 만든 스프레드시트를 엽니다. 응답 시트 한 장이 이미 있습니다. 확인 필요: 응답 시트의 기본 이름
- 상단 메뉴 확장 프로그램 → Apps Script를 열고,
function myFunction() {}를 지운 뒤 아래 코드 전체를 붙여 넣고 저장합니다. 실행 확인: 사용자 - 스프레드시트로 돌아가 새로고침합니다. 메뉴 줄에 문의 정리가 생깁니다.
- ① 시트 준비를 누릅니다. 처음 한 번은 권한 승인 창이 뜹니다. 승인 뒤 ①을 한 번 더 누릅니다.
문의함과 유형표 시트가 생기고, 유형표에 기본 유형 여섯 줄이 들어 있습니다. 안내창에 폼 응답 시트 이름이 보이면 스크립트가 폼을 찾은 것입니다. "아직 폼 응답 시트가 보이지 않습니다"가 뜨면 폼에서 시험 제출을 한 번 한 뒤 ①을 다시 누릅니다.Code.gs 전체 보기 · 복사
/**
* Parent inquiry triage — minimal teaching version.
* Google Form -> response sheet -> on submit: pick a type, attach a draft reply -> 문의함. A person replies.
*
* Rules this file enforces:
* 1. Nothing is sent to parents. The script labels and drafts; the owner reads, edits, and replies by hand.
* 2. The rules live in the 유형표 sheet, not in code. Types, keywords and drafts are edited there.
* 3. Exactly one form-submit trigger exists at a time; connecting again replaces the old one.
*/
var SHEET_INBOX = '문의함';
var SHEET_RULES = '유형표';
var Q = { type: '문의 유형', cls: '자녀 반', body: '내용', how: '연락 방법', contact: '연락처' };
var FORM_QUESTIONS = [Q.type, Q.cls, Q.body, Q.how, Q.contact];
var INBOX_HEADERS = ['접수시각', '유형', '반', '내용', '연락 방법', '연락처', '답변 초안', '상태', '처리 메모', '응답행'];
var RULE_HEADERS = ['유형', '키워드', '답변 초안', '직접 통화'];
var HANDLER = 'classifySubmission';
var STATUS = { open: '미답변', call: '직접 통화', done: '답변함' };
var COLORS = { open: '#fff2a8', call: '#f8cbc4' };
var UNKNOWN = '확인 필요';
var OTHER = '기타';
// Seeded by ①. Edit them in the sheet afterwards, not here.
var DEFAULT_RULES = [
['상담 신청', '상담, 레벨테스트, 등록, 입학, 신규', '안녕하세요. 상담 신청 감사합니다. 가능한 요일과 시간대를 두 개 정도 알려 주시면 맞춰 연락드리겠습니다.', ''],
['수강료', '수강료, 학원비, 결제, 입금, 영수증, 카드', '안녕하세요. 수강료 관련 문의 주셔서 감사합니다. 확인 후 정확한 금액과 방법을 다시 안내드리겠습니다.', ''],
['시간표·반 배정', '시간표, 시간, 요일, 반 배정, 반 변경, 옮기', '안녕하세요. 시간표와 반 배정은 현재 인원을 확인해야 해서, 가능한 선택지를 정리해 다시 연락드리겠습니다.', ''],
['결석·보강', '결석, 보강, 빠지, 못 가, 아파, 여행, 결석계', '안녕하세요. 알려 주셔서 감사합니다. 보강 가능한 시간을 확인해 안내드리겠습니다.', ''],
['직접 통화', '환불, 불만, 민원, 항의, 실망, 화가, 그만', '', 'Y'],
[OTHER, '', '안녕하세요. 문의 주셔서 감사합니다. 내용 확인 후 연락드리겠습니다.', '']
];
// ---------- Menu (simple trigger: menu only) ----------
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('문의 정리')
.addItem('① 시트 준비', 'setupSheets')
.addItem('② 폼 연결 (제출되면 바로 분류)', 'enableFormTrigger')
.addItem('③ 밀린 응답 다시 분류', 'recheckAll')
.addSeparator()
.addItem('④ 폼 연결 끊기', 'disableFormTrigger')
.addToUi();
}
// ---------- Pure helpers (no Apps Script services; testable in Node) ----------
function headerMap(headerRow) {
var map = {};
for (var i = 0; i < headerRow.length; i++) {
var key = String(headerRow[i]).trim();
if (key) map[key] = i;
}
return map;
}
function missingHeaders(map, required) {
return required.filter(function (h) { return !(h in map); });
}
/** namedValues maps a question title to an array; take the first non-empty string. */
function firstValue(namedValues, question) {
var arr = namedValues && namedValues[question];
if (!arr) return '';
if (!Array.isArray(arr)) return String(arr).trim();
for (var i = 0; i < arr.length; i++) {
var s = String(arr[i] || '').trim();
if (s) return s;
}
return '';
}
/** 유형표 rows (header included) -> [{ type, keywords[], draft, call }]. Blank types are skipped. */
function parseRules(rows) {
if (!rows || !rows.length) return [];
var map = headerMap(rows[0]);
var out = [];
for (var r = 1; r < rows.length; r++) {
var type = String(rows[r][map['유형']] || '').trim();
if (!type) continue;
var kw = String(rows[r][map['키워드']] || '').split(/[,,/]/)
.map(function (k) { return k.trim().toLowerCase(); })
.filter(function (k) { return k; });
out.push({
type: type,
keywords: kw,
draft: String(rows[r][map['답변 초안']] || '').trim(),
call: /^y|^예|^true/i.test(String(rows[r][map['직접 통화']] || '').trim())
});
}
return out;
}
function countHits(text, keywords) {
var n = 0;
for (var i = 0; i < keywords.length; i++) if (text.indexOf(keywords[i]) !== -1) n++;
return n;
}
/**
* Picks a type and a draft.
* 1. Any "직접 통화" keyword anywhere in the body wins: no draft, the owner calls.
* 2. The form's own 유형 choice is trusted unless it is blank or 기타.
* 3. Otherwise the type whose keywords hit the body most often; ties go to the earlier row.
* 4. Nothing hits: 확인 필요 with no draft.
*/
function classify(answers, rules) {
var body = String(answers.body || '').toLowerCase();
var chosen = String(answers.type || '').trim();
var byType = {};
rules.forEach(function (r) { byType[r.type] = r; });
for (var i = 0; i < rules.length; i++) {
if (rules[i].call && countHits(body, rules[i].keywords) > 0) {
return { type: rules[i].type, draft: '', status: STATUS.call, call: true };
}
}
var type = null;
if (chosen && chosen !== OTHER && byType[chosen]) {
type = chosen;
} else {
var best = 0;
rules.forEach(function (r) {
if (r.call || r.type === OTHER) return;
var hits = countHits(body, r.keywords);
if (hits > best) { best = hits; type = r.type; }
});
if (!type && chosen === OTHER && byType[OTHER]) type = OTHER;
}
if (!type) return { type: UNKNOWN, draft: '', status: STATUS.open, call: false };
return { type: type, draft: byType[type].draft, status: STATUS.open, call: false };
}
/** Row color by status. Done rows get no color. */
function statusColor(status) {
if (status === STATUS.call) return COLORS.call;
if (status === STATUS.open) return COLORS.open;
return null;
}
// ---------- ① Sheets ----------
function setupSheets() {
var ss = SpreadsheetApp.getActive();
ensureSheet_(ss, SHEET_INBOX, INBOX_HEADERS);
var rules = ensureSheet_(ss, SHEET_RULES, RULE_HEADERS);
if (rules.getLastRow() < 2) {
rules.getRange(2, 1, DEFAULT_RULES.length, RULE_HEADERS.length).setValues(DEFAULT_RULES);
}
var resp = findResponseSheet_(ss);
SpreadsheetApp.getUi().alert(resp
? '시트 준비가 끝났습니다. 폼 응답 시트: "' + resp.getName() + '"'
: '시트 준비가 끝났습니다. 아직 폼 응답 시트가 보이지 않습니다. 폼을 이 스프레드시트에 연결한 뒤 시험 제출을 한 번 해 주세요.');
}
function ensureSheet_(ss, name, headers) {
var sheet = ss.getSheetByName(name) || ss.insertSheet(name);
var lastCol = Math.max(sheet.getLastColumn(), 1);
var existing = headerMap(sheet.getRange(1, 1, 1, lastCol).getValues()[0]);
var toAdd = missingHeaders(existing, headers);
if (toAdd.length) {
var start = Object.keys(existing).length ? sheet.getLastColumn() + 1 : 1;
sheet.getRange(1, start, 1, toAdd.length).setValues([toAdd]).setFontWeight('bold');
sheet.setFrozenRows(1);
}
return sheet;
}
/** The form-linked sheet is the one whose header row holds every form question. */
function findResponseSheet_(ss) {
var sheets = ss.getSheets();
for (var i = 0; i < sheets.length; i++) {
var s = sheets[i];
if (s.getName() === SHEET_INBOX || s.getName() === SHEET_RULES) continue;
if (s.getLastColumn() < 1) continue;
var map = headerMap(s.getRange(1, 1, 1, s.getLastColumn()).getValues()[0]);
if (missingHeaders(map, FORM_QUESTIONS).length === 0) return s;
}
return null;
}
function readRules_(ss) {
var sheet = ss.getSheetByName(SHEET_RULES);
if (!sheet || sheet.getLastRow() < 2) throw new Error('유형표가 비어 있습니다. 메뉴 ①을 먼저 실행하세요.');
return parseRules(sheet.getRange(1, 1, sheet.getLastRow(), sheet.getLastColumn()).getValues());
}
// ---------- On submit: classify one response ----------
function classifySubmission(e) {
var ss = SpreadsheetApp.getActive();
var rules = readRules_(ss);
var nv = e && e.namedValues ? e.namedValues : {};
var answers = {
type: firstValue(nv, Q.type), cls: firstValue(nv, Q.cls), body: firstValue(nv, Q.body),
how: firstValue(nv, Q.how), contact: firstValue(nv, Q.contact)
};
var when = e && e.values && e.values[0] ? e.values[0] : new Date();
var rowNo = e && e.range ? e.range.getRow() : '';
appendToInbox_(ss, answers, when, rowNo, classify(answers, rules));
}
/** ③ Classifies every response row that is not in 문의함 yet, then recolors 문의함 by status. */
function recheckAll() {
var ss = SpreadsheetApp.getActive();
var resp = findResponseSheet_(ss);
if (!resp) throw new Error('폼 응답 시트를 찾지 못했습니다. 폼을 이 스프레드시트에 연결하고 시험 제출을 한 번 해 주세요.');
var rules = readRules_(ss);
var inbox = ss.getSheetByName(SHEET_INBOX);
var imap = headerMap(inbox.getRange(1, 1, 1, inbox.getLastColumn()).getValues()[0]);
var seen = {};
if (inbox.getLastRow() > 1) {
inbox.getRange(2, imap['응답행'] + 1, inbox.getLastRow() - 1, 1).getValues()
.forEach(function (v) { if (v[0] !== '') seen[String(v[0])] = true; });
}
var rmap = headerMap(resp.getRange(1, 1, 1, resp.getLastColumn()).getValues()[0]);
var added = 0;
if (resp.getLastRow() > 1) {
var rows = resp.getRange(2, 1, resp.getLastRow() - 1, resp.getLastColumn()).getValues();
for (var i = 0; i < rows.length; i++) {
var rowNo = i + 2;
if (seen[String(rowNo)]) continue;
var v = rows[i];
var answers = {
type: String(v[rmap[Q.type]] || '').trim(), cls: String(v[rmap[Q.cls]] || '').trim(),
body: String(v[rmap[Q.body]] || '').trim(), how: String(v[rmap[Q.how]] || '').trim(),
contact: String(v[rmap[Q.contact]] || '').trim()
};
if (!answers.body && !answers.type) continue;
appendToInbox_(ss, answers, v[0], rowNo, classify(answers, rules));
added++;
}
}
recolorInbox_(inbox);
SpreadsheetApp.getUi().alert(added + '건을 새로 분류했습니다. 상태 색을 다시 칠했습니다.');
}
function appendToInbox_(ss, a, when, rowNo, c) {
var inbox = ss.getSheetByName(SHEET_INBOX);
if (!inbox) throw new Error('메뉴 ① 시트 준비를 먼저 실행하세요.');
var tz = Session.getScriptTimeZone();
var whenText = Object.prototype.toString.call(when) === '[object Date]' ? Utilities.formatDate(when, tz, 'yyyy-MM-dd HH:mm') : String(when);
var row = [whenText, c.type, a.cls, a.body, a.how, a.contact, c.draft, c.status, '', rowNo];
inbox.appendRow(row);
var r = inbox.getLastRow();
inbox.getRange(r, 1, 1, INBOX_HEADERS.length).setBackground(statusColor(c.status));
}
function recolorInbox_(inbox) {
if (inbox.getLastRow() < 2) return;
var map = headerMap(inbox.getRange(1, 1, 1, inbox.getLastColumn()).getValues()[0]);
var n = inbox.getLastRow() - 1;
var statuses = inbox.getRange(2, map['상태'] + 1, n, 1).getValues();
var colors = statuses.map(function (s) {
var fill = statusColor(String(s[0]).trim());
var line = [];
for (var k = 0; k < INBOX_HEADERS.length; k++) line.push(fill);
return line;
});
inbox.getRange(2, 1, n, INBOX_HEADERS.length).setBackgrounds(colors);
}
// ---------- ② ④ Form-submit trigger: never more than one ----------
function enableFormTrigger() {
var ss = SpreadsheetApp.getActive();
if (!findResponseSheet_(ss)) throw new Error('폼 응답 시트를 찾지 못했습니다. 폼을 먼저 연결하고 시험 제출을 한 번 해 주세요.');
deleteOurTriggers_();
ScriptApp.newTrigger(HANDLER).forSpreadsheet(ss).onFormSubmit().create();
SpreadsheetApp.getUi().alert('폼이 연결되었습니다. 이제 제출되는 즉시 문의함에 분류됩니다. 이미 쌓인 응답은 ③으로 처리하세요.');
}
function disableFormTrigger() {
var n = deleteOurTriggers_();
SpreadsheetApp.getUi().alert(n ? '폼 연결을 끊었습니다.' : '연결된 폼 트리거가 없습니다.');
}
function deleteOurTriggers_() {
var n = 0;
ScriptApp.getProjectTriggers().forEach(function (t) {
if (t.getHandlerFunction() === HANDLER) { ScriptApp.deleteTrigger(t); n++; }
});
return n;
}
코드에서 눈여겨볼 것 세 가지입니다.
- 규칙은 코드에 없습니다.
DEFAULT_RULES는 ①이 유형표에 처음 한 번 넣어 주는 씨앗일 뿐입니다. 그 뒤로는 시트의 유형표가 정답이고, 코드를 다시 열 일이 없습니다. - 민원이 먼저입니다.
classify는 "직접 통화" 유형의 키워드가 본문에 하나라도 있으면 다른 판단을 멈추고 초안 없이 빨강으로 넘깁니다. 환불·불만 문의에 틀에 박힌 초안이 붙는 일을 막습니다. - 응답 시트는 이름이 아니라 머리글로 찾습니다.
findResponseSheet_가 질문 다섯 개가 모두 있는 시트를 찾습니다. 그래서 응답 시트 이름이 무엇이든 상관없고, 1단계의 질문 제목이 중요합니다.
3. 유형표를 우리 학원 말로 고친다
유형표 시트 · 유형 · 키워드 · 답변 초안 · 직접 통화
- 최근 실제 문의 5개를 꺼내, 학부모가 실제로 쓴 낱말을 해당 유형의
키워드칸에 쉼표로 덧붙입니다. "빠지", "못 가"처럼 어미를 뺀 짧은 형태가 더 잘 잡힙니다. 답변 초안을 우리 학원 말투로 고칩니다. 약속처럼 읽히는 말("꼭 넣어 드리겠습니다")은 피합니다.직접 통화행의 키워드를 봅니다. 여기 있는 낱말이 들어오면 초안 없이 빨강이 됩니다. 늘리거나 줄이세요.- 유형을 늘리려면 행을 추가하고, 폼의
문의 유형선택지에도 같은 이름을 추가합니다.
유형표가 커질수록 키워드가 겹치기 시작합니다. "시간"은 시간표 문의에도, 상담 가능 시간을 묻는 문의에도 나옵니다. 스크립트는 더 많이 맞는 유형을 고르고, 같으면 위에 있는 행을 고릅니다. 겹침이 잦아지면 폼의 유형 선택을 필수로 두는 것이 키워드를 늘리는 것보다 낫습니다.
4. ② 폼 연결, 시험 제출, ③ 밀린 응답
onFormSubmit 트리거 · 하나만
- ② 폼 연결을 누릅니다. 트리거 권한을 묻는 창이 한 번 더 뜰 수 있습니다. 실행 확인: 사용자
- 폼을 열어 시험 문의를 하나 제출합니다. 예: 유형 "기타", 내용 "다음 주 수요일 결석하고 보강 가능할까요".
문의함을 엽니다. 몇 초 안에 한 행이 생기고 유형이 "결석·보강", 초안이 채워져 있고 노랑입니다.- ②를 켜기 전에 쌓인 응답이 있으면 ③ 밀린 응답 다시 분류를 누릅니다. 문의함에 없는 응답만 골라 넣고, 전체 행의 색을 상태에 맞게 다시 칠합니다.
②는 이전 편의 ③과 같은 방식으로 트리거를 하나만 유지합니다. 공식 문서에 따르면 트리거는 만든 사람의 계정으로 실행되고, 같은 스프레드시트에 다른 계정이 설치한 트리거는 보이지 않습니다. 원장 계정으로 켜세요. — 설치형 트리거 가이드, 2026-09-03 갱신본
5. 문의함에서 일하는 법
읽기 · 고치기 · 직접 답하기 · 상태 바꾸기
- 노랑 행의
답변 초안을 읽고, 그 문의에 맞게 고칩니다. 초안은 출발점이지 답이 아닙니다. - 고친 글을 학부모가 고른
연락 방법으로 직접 보냅니다. 카카오면 카카오로, 전화면 전화로. 상태를답변함으로 바꾸고, 필요하면처리 메모에 한 줄 남깁니다.- 빨강 행은 초안이 없습니다. 바로 전화합니다. 통화 뒤 상태를
답변함으로 바꿉니다. - 하루 끝에 ③을 한 번 누르면 답변함 행의 색이 사라지고, 남은 노랑·빨강이 내일 할 일입니다.
색은 ③을 누를 때 다시 칠해집니다. 상태를 바꾸는 즉시 색이 바뀌지는 않습니다. 이것은 일부러 둔 단순함입니다. 셀을 고칠 때마다 반응하는 트리거를 넣으면 코드와 권한이 늘어나고, 하루 한 번 ③이면 충분합니다.
자주 막히는 곳
| 증상 | 원인과 조치 |
|---|---|
| "폼 응답 시트를 찾지 못했습니다" | 폼이 이 스프레드시트에 연결되지 않았거나, 질문 제목이 1단계와 다르거나, 아직 제출이 한 건도 없습니다. 질문 제목 다섯 개를 글자 단위로 맞추고 시험 제출을 한 번 합니다. |
| 제출했는데 문의함에 안 생긴다 | ②를 누르지 않았거나 다른 계정으로 눌렀습니다. 편집기 왼쪽 트리거 메뉴에 classifySubmission이 있는지 보고, 없으면 ②를 다시 누릅니다. 이미 제출된 응답은 ③으로 가져옵니다. |
| 유형이 엉뚱하다 | 키워드가 겹쳤습니다. 폼의 유형 선택을 필수로 두면 선택이 키워드보다 먼저입니다. 겹치는 낱말은 한쪽 키워드에서 뺍니다. |
| "확인 필요"가 많다 | 유형표 키워드가 학부모 말과 다릅니다. 실제 문의에서 쓴 낱말을 키워드에 더합니다. 유형을 "기타"로 고른 문의는 키워드가 없어도 기타 초안을 받습니다. |
| 같은 문의가 두 번 들어온다 | ②를 두 번 눌러도 트리거는 하나입니다. ③은 응답행 번호로 이미 넣은 것을 건너뜁니다. 문의함의 응답행 열을 지우면 ③이 다시 넣으니 그 열은 비워 두지 마세요. |
| 상태를 바꿨는데 색이 그대로다 | 정상입니다. ③을 누를 때 다시 칠해집니다. |
한도
개인 구글 계정 기준으로 공식 할당량 표에 적힌 값입니다. 문의 한 건에 실행 한 번이니 하루 수십 건이어도 여유가 큽니다.
| 항목 | 개인 계정 | Workspace |
|---|---|---|
| 트리거 수 (사용자·스크립트당) | 20개 | 20개 |
| 트리거 총 실행 시간 | 90분 / 일 | 6시간 / 일 |
| 한 번 실행 시간 | 6분 | 6분 |
Apps Script 할당량 문서, 2026-09-03 갱신본.
혼자 다시 해 보기
이 가이드를 닫고, 새 폼과 새 스프레드시트에서 처음부터 다시 만듭니다. 아래가 전부 되면 끝입니다.
- 질문 다섯 개짜리 폼을 만들고 스프레드시트에 연결했다
- 스크립트를 붙이고 ①로 문의함·유형표를 만들었다
- 유형표의 키워드와 초안을 우리 학원 말로 고쳤다
- ②를 켜고 시험 제출이 몇 초 안에 문의함에 분류되는 것을 봤다
- "환불"이 든 제출이 초안 없이 빨강이 되는 것을 봤다
- 상태를 답변함으로 바꾸고 ③으로 색이 사라지는 것을 봤다
이 최소 버전에 없는 것
가르치기 위해 줄인 버전입니다. 실제 운영에는 아래가 더 필요합니다.
- 자동 회신. 이 시리즈에서는 학부모에게 보내는 일을 자동화하지 않습니다
- 카카오·전화 문의를 폼으로 모으는 안내 문구와 프로필 링크 설정
- 상태를 바꾸는 즉시 색이 바뀌는 셀 편집 트리거
- AI 분류. 본문이 통째로 밖으로 나가야 해서 넣지 않았습니다. 넣으려면 유료 등급과 가명 처리가 먼저입니다
- 답변까지 걸린 시간, 유형별 건수 같은 통계
- 강사와 나눠 보는 구조와 연락처 분리
이 글은 리드마스터 AX 컨설팅(학원 AI 전환 1-Day)에서 하루 동안 다루는 내용 가운데 하나를, 집에서 혼자 다시 해 볼 수 있게 줄인 복습 자료입니다. 이전 편: 수강료·재등록, 오늘 챙길 것 · 수업 메모를 학부모 리포트로 · 다음 편: 블로그 글 하나를 네 군데에.
도구 사용법과 한도는 각 서비스의 공식 문서에서 확인했고, 확인한 주소와 날짜는 출처 장부에 있습니다. 실행 확인: 사용자 표시는 구글 로그인이 필요해 문서로만 확인한 단계입니다. 결과는 학원 환경에 따라 다를 수 있습니다.