구글 폼으로 문의 받고 바로 알림 오게 하기 — 접수 자동화 직접 만든 기록

지난 글에서 구글 시트에 줄이 차면 메일이 오게 만들었습니다. 이번에는 그 앞에 구글 폼을 붙였습니다. 홈페이지에 문의 폼을 걸어 두면, 고객이 제출한 것이 시트에 한 줄로 들어오고, 그 줄이 메일이 되는 흐름입니다.

지난 글에서 세 번 막혔던 자리는 이번에는 안 막혔습니다. 설치형 트리거를 처음부터 걸었더니 첫 제출에서 메일이 왔습니다. 대신 시간이 간 곳이 달랐습니다. 폼과 시트 사이 — 폼을 고치면 시트가 어떻게 되는가 — 였고, 다섯 통 중 한 통은 실행 기록이 「완료됨」인데 메일함에 없다가 15~20분쯤 뒤에 나타났습니다.

이 글은 그 순서 그대로입니다.

무엇을 만들었나

폼은 이름 · 연락처 · 문의 내용 세 칸이고 전부 필수입니다. 제출하면 응답 시트에 한 줄이 생기고, 시트에 붙은 Apps Script 가 「양식 제출 시」 트리거로 돌면서 정해진 주소로 메일 한 통을 보냅니다.

만든 순서는 이렇습니다. 순서가 중요해서 적어 둡니다.

  1. 폼을 만들고 질문 세 개를 넣습니다
  2. 폼의 「응답」 탭 → 「Sheets에 연결」 로 응답 시트를 만듭니다
  3. 그 시트에서 확장 프로그램 → Apps Script 를 열고 코드를 넣습니다
  4. 트리거를 「양식 제출 시」로 겁니다
  5. 폼을 「게시」 합니다

지난 글은 시트에서 시작했지만, 이번에는 시트를 제가 만들지 않았습니다. 폼이 만듭니다.

지난 글과 다른 것 셋

시트는 폼이 만듭니다

「Sheets에 연결」을 누르면 이 대화상자가 뜹니다.

구글 폼 응답 저장 위치 선택 대화상자 — 새 스프레드시트 만들기 · 기존 스프레드시트 선택

「새 스프레드시트 만들기」로 두고 「만들기」를 누르면 새 탭에 시트가 열립니다. 저는 27초 걸렸습니다.

여기서 하나. 시트 이름이 「제목 없는 설문지(응답)」 으로 만들어졌습니다. 폼 안의 제목은 이미 바꿔 뒀는데, 시트 이름은 폼의 파일 이름(왼쪽 위 작은 글자)을 따릅니다. 폼 제목과 파일 이름은 따로 갑니다. 시트 이름은 나중에 바꿔도 연결이 끊기지 않았습니다.

폼이 만든 응답 시트 — 타임스탬프 · 이름 · 연락처 · 문의 내용 네 열

열은 타임스탬프 | 이름 | 연락처 | 문의 내용 입니다. 첫 열은 제가 넣은 질문이 아니라 폼이 제출 시각을 적는 칸입니다. 이 시트는 지난 글의 빈 시트와 달리 「표(Form_Responses)」 형식으로 만들어졌고, 열 이름은 폼의 질문 제목 그대로입니다. 코드에서 이 이름으로 값을 읽게 되므로 질문 제목을 나중에 바꾸면 코드도 따라가야 합니다.

트리거는 「양식 제출 시」입니다

시트에서 연 Apps Script 의 트리거 대화상자입니다. 이벤트 소스를 「스프레드시트에서」로 두면 이벤트 유형이 네 개 나옵니다.

Apps Script 트리거 추가 대화상자 — 이벤트 유형에 열릴 시 · 수정 시 · 변경 시 · 양식 제출 시

기본값은 지난 글과 같이 「열릴 시」 입니다. 「양식 제출 시」 로 바꿔야 합니다. 지난 글에서 본 대로 「수정 시」는 사람이 손으로 고칠 때만 돌고, 폼이 넣는 줄은 모릅니다.

저장하면 권한 창이 뜹니다. 항목이 지난 글보다 하나 많습니다.

Apps Script 권한 창 — 스프레드시트 전체 · Google Drive 의 양식 보기·관리 · 내 이름으로 이메일 보내기
See, edit, create, and delete all your Google Sheets spreadsheets
View and manage your forms in Google Drive
Send email as you

「양식 보기·관리」 가 새로 붙었습니다. 코드에 폼을 다루는 줄은 한 줄도 없는데, 「양식 제출 시」 트리거를 거는 것만으로 이 항목이 들어옵니다. 이 계정은 회사 워크스페이스 계정이라 「확인되지 않은 앱」 경고는 이번에도 나오지 않았습니다.

값은 e.values 로 한 번에 옵니다

검색하면 가장 먼저 나오는 형태의 코드를 그대로 넣었습니다.

var TO = 'xxxx@xxxx.xx';

function onFormSubmit(e) {
  var v = e.values;   // [타임스탬프, 이름, 연락처, 문의 내용]
  MailApp.sendEmail(TO,
    '[문의 접수] ' + v[1],
    '접수 시각: ' + v[0] + '\n이름: ' + v[1] + '\n연락처: ' + v[2] + '\n내용: ' + v[3]
    + '\n\n' + e.range.getSheet().getParent().getUrl());
}

지난 글에서는 「수정 시」가 칸 단위로 발동해서 한 줄에 메일이 세 통 왔고, 그걸 막으려고 D열에 보낸 표시를 남겼습니다. 폼은 줄 전체가 한 번에 들어오고 e.values 에 그 줄이 배열로 담겨 옵니다. 세 통 문제가 없고, 보낸 표시도 필요 없었습니다.

타임스탬프도 지난 글과 다르게 옵니다. getValues() 가 날짜 객체를 주던 것과 달리 e.values[0] 은 이미 글자입니다.

2026. 9. 14 오후 9:35:36

그리고 함수 이름 onFormSubmit 은 onEdit 과 달리 구글이 알아서 부르는 이름이 아닙니다. 지난 글에서 이름이 onEdit 이라 한 번의 편집에 두 번 돌던 일이 이번에는 없었습니다. 실행 기록에 제출 한 건당 한 줄만 찍혔습니다.

게시하지 않으면 응답을 받지 않습니다

응답 탭에는 만들자마자 이렇게 적혀 있었습니다.

응답이 없습니다. 응답을 수락하려면 양식을 게시하세요.

오른쪽 위 「게시」 버튼을 누르면 「게시 양식」 대화상자가 뜹니다. 응답자 기본값은 「링크가 있는 모든 사용자에게 공개」 였고, 그 아래에 회사 이름으로 「(으)로 제한」 하는 선택지가 있었습니다. 홈페이지 문의로 쓰려면 기본값 그대로여야 합니다.

게시하면 버튼이 「게시됨」으로 바뀌고, 응답자 링크를 복사할 수 있습니다. 편집 주소와 다른 주소입니다 — /forms/d/e/…/viewform 형태이고, 고객에게 주는 것은 이쪽입니다.

응답자 화면에서 하나 더 봤습니다. 로그인하지 않은 브라우저로 열었더니 폼 맨 아래에 「이 설문지는 ○○○ 내부에서 생성되었습니다」 라고 회사 이름이 찍혀 있었습니다. 워크스페이스 계정으로 만든 폼은 고객에게 이 문구가 보입니다. 감출 방법은 찾아보지 않았습니다.

첫 제출 — 4~5초

로그인하지 않은 별도 브라우저에서 응답자 링크를 열고 세 칸을 채워 「제출」을 눌렀습니다.

시각
「제출」 클릭 21:35:32.7
시트 타임스탬프 21:35:36
실행 시작 (실행 기록) 21:35:37 · 1.77초
메일 도착 (메일함 표시) 21:35

메일이 왔습니다. 코드는 한 글자도 안 고쳤고, 편집기의 「실행」 버튼은 한 번도 안 눌렀습니다. 지난 글의 첫 에러 Cannot read properties of undefined (reading 'source') 는 「실행」 버튼을 누를 때 나는 것이라, 폼에서 직접 제출하면 나올 일이 없습니다.

이후 네 번 더 제출했습니다. 클릭에서 실행 시작까지 3~5초, 실행 자체는 1.1~1.9초, 코드 안에서 sendEmail 이 돌아온 시각까지 재면 클릭에서 4~5초였습니다.

막힌 지점 1 — 폼 중간에 칸을 넣었더니 시트에서는 맨 끝에 붙습니다

문의 폼은 운영하다 보면 고칩니다. 그래서 「이름」 다음에 「회사명」 을 끼워 넣어 봤습니다.

먼저, 게시된 폼을 고치면 재게시 없이 즉시 반영됐습니다. 응답자 링크를 다시 열었더니 회사명 칸이 이미 보였습니다.

그리고 제출했더니 시트가 이렇게 됐습니다.

응답 시트 — 폼에서 두 번째로 넣은 회사명이 시트에서는 E열 맨 끝에 붙은 화면

폼에서는 두 번째 칸인데 시트에서는 E열, 맨 끝입니다. 기존 열은 자리를 지켰고 새 열만 뒤에 붙었습니다. 실행 로그에 찍힌 e.values 원문입니다.

row=5.0 values=["2026. 9. 14 오후 10:22:45","이지연","010-5555-6666","namedValues 버전 첫 제출","사아자상사"]

위 코드는 이 상황에서도 안 깨집니다. v[1] 은 여전히 이름이니까요. 대신 회사명은 메일에 안 실립니다. 반대로 「이름」 앞에 칸을 넣었다면 어떻게 되는지는 시험하지 못했습니다. 이 계정에서 새 열이 늘 끝에 붙는 것만 확인했습니다.

그래서 번호 대신 열 이름으로 읽게 바꿨습니다. e.namedValues 는 { '이름': ['홍길동'], '연락처': ['010-…'], … } 형태라 열 순서와 무관합니다.

var TO = 'xxxx@xxxx.xx';

// '양식 제출 시' 설치형 트리거용
function onFormSubmit(e) {
  var nv = e.namedValues;            // 열 이름으로 읽습니다 — 열 순서가 바뀌어도 그대로
  var get = function (k) { return (nv[k] && nv[k][0]) ? nv[k][0] : ''; };
  var row = e.range.getRow();
  var subject = '[문의 접수] ' + get('이름') + (get('회사명') ? ' (' + get('회사명') + ')' : '');
  var body = '접수 시각: ' + get('타임스탬프')
           + '\n이름: ' + get('이름')
           + '\n회사명: ' + get('회사명')
           + '\n연락처: ' + get('연락처')
           + '\n내용: ' + get('문의 내용')
           + '\n\n' + e.range.getSheet().getParent().getUrl()
           + '#gid=' + e.range.getSheet().getSheetId() + '&range=A' + row;
  Logger.log('row=%s values=%s quota=%s', row, JSON.stringify(e.values), MailApp.getRemainingDailyQuota());
  MailApp.sendEmail(TO, subject, body);
  Logger.log('sent row=%s at %s', row, new Date().toISOString());
}

회사명을 비우고 제출하면 e.values 의 그 자리에는 빈 문자열이 옵니다. 필수가 아닌 칸은 늘 이렇게 오므로 get() 처럼 빈 값을 받아 주는 쪽이 낫습니다.

하나 더. 시험 중에 「첨부파일」 질문을 잠깐 넣었다가 지웠는데, 시트의 열은 그대로 남았습니다. 폼에서 질문을 지워도 시트 열은 안 지워집니다. 열을 지우려면 시트에서 직접 지워야 하는데, 그러면 이후 제출이 어느 열로 들어오는지는 확인하지 못했습니다.

막힌 지점 2 — 실행 기록은 「완료됨」인데 메일이 없습니다

회사명 칸을 넣고 처음 제출한 건 — 다섯 건 중 두 번째 — 입니다. 시트에는 줄이 들어왔고, 실행 기록에는 이렇게 찍혔습니다.

onFormSubmit   실행   2026. 9. 14. 오후 10:11:55   1.751초   완료됨

그런데 메일함에 없었습니다. 받은편지함, 스팸함, 전체보관함을 다 봤습니다. 3분 뒤에도, 8분 뒤에도, 13분 뒤에도 없었습니다. 그 사이에 다시 제출한 세 번째 건은 45초 안에 왔습니다.

그리고 21분 뒤에 다시 열었더니 있었습니다. 메일 목록에는 보낸 시각(오후 10:11) 으로 찍혀 있어서, 목록만 보면 늦게 온 티가 나지 않습니다.

원인은 모릅니다. 코드도, 트리거도, 받는 주소도 앞뒤 건과 같습니다. 이 건만 이랬고, 다섯 건 중 한 건입니다. 지난 글에서 「실행 기록은 완료됨인데 메일이 없는 상태가 제일 찾기 어렵다」고 적었는데, 이번에는 코드 탓이 아닌 경우도 있다는 것을 봤습니다.

그래서 이 흐름에서 원본은 시트이고 메일은 알림입니다. 메일이 안 왔다고 문의가 안 들어온 것은 아니라서, 하루에 한 번은 시트를 직접 보는 편이 안전합니다.

문의 접수로 쓸 때 걸리는 것

5~50인 회사가 홈페이지 문의를 이 폼으로 받는다고 놓고, 실제로 부딪힌 것만 적습니다.

첨부파일을 못 받습니다. 질문 유형을 「파일 업로드」로 바꾸면 이 안내가 뜹니다.

구글 폼 파일 업로드 질문 안내 — 응답하려면 사용자가 Google 에 로그인해야 한다는 문구
파일이 설문지 소유자의 Google Drive에 업로드됩니다.
파일 업로드 질문이 추가된 설문지에 응답하려면 사용자가 Google에 로그인해야 합니다.
신뢰하는 사용자에게만 이 설문지를 공유해야 합니다.

고객에게 구글 로그인을 요구하게 됩니다. 견적서나 도면을 받아야 하는 문의라면 이 폼으로는 어렵고, 저는 「계속」을 누르지 않고 취소했습니다.

같은 사람이 몇 번이든 보낼 수 있습니다. 설정 → 응답에 「응답 횟수 1회로 제한」이 있는데, 「로그인 필요」 아래에 있습니다. 로그인 없이 받는 폼에서는 못 씁니다. 이번 시험에서 로그인 없는 브라우저로 다섯 번 제출했고 다섯 번 다 들어왔습니다.

스팸 방지 장치가 없습니다. 폼에는 자동입력 방지 문자 같은 것이 없고, 링크가 공개되면 누구나 제출할 수 있습니다. 다만 이번 시험에서 실제로 스팸을 받지는 않았으므로, 얼마나 들어오는지는 말 못 합니다.

메일 한도가 있습니다. 실행 로그에 남은 한도를 찍어 봤더니 이 계정은 1,440~1,455통이었습니다(워크스페이스 계정). 하루 수십 건 문의라면 문제 없고, 수백 건이면 지난 글에 적은 대로 메일이 아닌 채널로 가야 합니다.

회사 이름이 보입니다. 위에 적은 대로, 로그인하지 않은 고객의 폼 화면 아래에 조직 이름이 찍힙니다.

요약

시트를 먼저 만들지 마십시오 폼의 「응답」 탭 → 「Sheets에 연결」 이 시트를 만듭니다
시트 이름이 「제목 없는 설문지(응답)」 폼의 파일 이름을 따릅니다. 폼 안의 제목과 별개입니다
트리거 기본값 「열릴 시」 「양식 제출 시」 로 바꾸십시오. 「수정 시」는 폼이 넣는 줄을 모릅니다
권한 창에 「양식 보기·관리」 「양식 제출 시」 트리거를 걸면 붙습니다. 코드와 무관합니다
e.values 줄 전체가 배열로 옵니다. 타임스탬프는 글자입니다
「응답을 수락하려면 양식을 게시하세요」 「게시」 를 눌러야 받습니다. 응답자 링크는 /d/e/…/viewform
폼 중간에 칸 추가 시트에서는 맨 끝 열에 붙습니다. e.namedValues 로 이름으로 읽으십시오
폼에서 질문 삭제 시트 열은 남습니다
「완료됨」인데 메일 없음 한 건이 13~21분 뒤에 왔습니다. 원인 모름. 시트가 원본입니다
파일 업로드 고객에게 구글 로그인을 요구합니다
응답 1회 제한 「로그인 필요」 아래에 있습니다

코드는 지난 글보다 짧게 끝났습니다. 시간이 간 곳은 코드가 아니라 폼과 시트 사이의 약속 — 누가 시트를 만들고, 열이 어디에 붙고, 언제 받기 시작하는가 — 였습니다.


참고
– Google Apps Script 공식 문서 — 이벤트 객체 (Event Objects: Google Sheets form submit — values, namedValues, range)
– Google Apps Script 공식 문서 — 설치형 트리거 (Installable Triggers)
– Google Forms 고객센터 — 파일 업로드 질문 · 양식 게시

Leave a Comment