구글 시트가 바뀌면 알림 보내기 — 직접 만들어본 기록

구글 시트에 줄이 하나 추가되면 메일이 오게 만들었습니다. 코드는 20줄이 안 됩니다.

그런데 첫 버전은 한 통도 안 왔습니다. 코드는 “보냈다”고 했고 실행 기록에도 「완료됨」이 찍혔는데, 받은편지함은 비어 있었습니다. 그 뒤로 세 번 막혔고, 세 번 다 에러 문구가 달랐습니다.

이 글은 그 에러 문구 세 개와, 돌려보고 나서야 안 것들을 그대로 적은 것입니다.

무엇을 만들었나

시트에 날짜 | 이름 | 내용 세 칸짜리 줄이 추가되면 정해진 주소로 메일 한 통이 가는 흐름입니다.

도구는 Google Apps Script 를 골랐습니다. n8n 도 같은 일을 하지만 따로 깔아야 하고 켤 때마다 2분이 걸립니다. 이 글을 검색해서 오시는 분은 대개 시트를 이미 쓰고 계신 분이고, 시트 안에서 끝나는 쪽이 더 흔한 경로라고 봤습니다. 설치가 없고, 구글 계정만 있으면 됩니다.

들어가는 길은 시트 메뉴에서 확장 프로그램 → Apps Script 입니다. 편집기가 열리고, 거기에 코드를 붙여 넣습니다.

구글 시트 메뉴 확장 프로그램 → Apps Script

막힌 지점 1 — 「실행」 버튼을 누르면 에러가 납니다

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

function onEdit(e) {
  var sheet = e.source.getActiveSheet();
  var row = e.range.getRow();
  if (row === 1) return;
  var values = sheet.getRange(row, 1, 1, 3).getValues()[0];
  MailApp.sendEmail('xxxx@xxxx.xx', '[시트 알림] ' + row + '행이 바뀌었습니다', values.join(' | '));
}

저장하고, 편집기 위의 「실행」 을 눌렀습니다. 권한 승인 창이 뜨고, 승인하고 나니 이렇게 나왔습니다.

TypeError: Cannot read properties of undefined (reading 'source')
    at onEdit(Code:2:17)

이건 코드가 틀린 게 아닙니다. e 는 시트가 편집됐을 때 구글이 넣어 주는 값입니다. 편집기에서 「실행」을 누르면 아무도 e 를 안 넣어 주니 비어 있고, 그래서 e.source 에서 넘어집니다.

「실행」 버튼은 이 함수를 시험하는 용도가 아닙니다. 권한 승인 창을 띄우는 데는 쓸 수 있지만, 그 뒤에 나오는 이 에러는 무시하고 실제로 시트에 글자를 넣어서 시험해야 합니다.

막힌 지점 2 — 시트에 넣으니 「권한이 없다」고 합니다

시트 2행에 값을 넣었습니다. 메일이 안 왔습니다.

Apps Script 왼쪽 메뉴의 「실행」 (시계 아이콘 아래, 실행 기록 페이지)을 열어 보니 두 건이 「실패」였습니다. 실패 행을 누르면 로그가 펼쳐집니다.

Exception: 지정된 권한으로는 MailApp.sendEmail을(를) 호출할 수 없습니다.
필수 권한: https://www.googleapis.com/auth/script.send_mail
    at onEdit(Code:6:11)

영어 계정이면 You do not have permission to call MailApp.sendEmail 로 나오는 그 문구입니다.

권한은 방금 승인했는데 없다고 합니다. 여기서 한참 헤맸습니다.

원인은 onEdit 이라는 이름입니다. 함수 이름을 onEdit 으로 지으면 구글이 알아서 편집마다 불러 주는데(단순 트리거), 이렇게 불린 함수는 승인 여부와 관계없이 메일·외부 요청을 못 보냅니다. 승인은 편집기에서 「실행」할 때만 적용되고, 시트가 자동으로 부를 때는 적용되지 않습니다.

해결 — 트리거를 직접 겁니다

왼쪽 메뉴의 시계 아이콘(트리거) → 오른쪽 아래 「트리거 추가」.

Apps Script 트리거 추가 대화상자 — 이벤트 유형 기본값이 「열릴 시」

대화상자의 기본값이 「열릴 시」입니다. 그대로 저장하면 시트를 열 때마다 도는 트리거가 생깁니다. 「이벤트 유형 선택」을 「수정 시」로 바꿔야 합니다. 저장하면 권한 창이 한 번 더 뜹니다.

이렇게 걸린 트리거(설치형 트리거)로 불린 함수는 메일을 보낼 수 있습니다. 같은 값을 다시 넣었더니 메일이 왔습니다. 코드는 한 글자도 안 고쳤습니다.

막힌 지점 3 — 함수 이름을 바꿨더니 조용히 죽습니다

트리거를 걸고 나서 실행 기록을 보니 편집 한 번에 두 줄이 찍혔습니다. 「단순 트리거 — 실패」와 「실행 — 완료됨」. 이름이 onEdit 이라 구글이 알아서 부르는 것과, 제가 건 트리거가 부르는 것, 둘 다 돌고 있었습니다.

그래서 함수 이름을 alertOnRowEdit 으로 바꿨습니다. 그리고 다음 줄을 넣었더니 메일이 안 왔습니다.

Script function not found: onEdit

트리거는 함수 이름을 기억합니다. 코드에서 이름을 바꿔도 트리거는 옛 이름을 계속 찾습니다. 트리거 페이지에서 옛것을 지우고(메뉴 → 트리거 삭제 → 「완전삭제」) 새 이름으로 다시 걸어야 합니다.

이 실패는 아무 데도 알려 주지 않습니다. 트리거 추가 대화상자의 「트리거 실패 알림 설정」 기본값이 「매일 알림」 이라, 하루가 지나야 요약 메일이 옵니다. 만들 때 「즉시 알림」 으로 바꿔 두시는 편이 낫습니다.

권한 설정에서 걸리는 것

권한 창은 두 번 떴고, 요구하는 것이 달랐습니다.

언제 요구한 권한
편집기에서 「실행」 눌렀을 때 내 이메일 주소로 이메일 보내기
「수정 시」 트리거를 저장할 때 모든 Google Sheets 스프레드시트 보기·수정·생성·삭제 + 내 이름으로 이메일 보내기
트리거 저장 시 뜨는 권한 창 — 모든 스프레드시트 권한과 이메일 전송 권한

두 번째가 넓어 보이지만, 시트 하나에 붙은 스크립트에 트리거를 걸 때 늘 이렇게 나옵니다. 이 계정은 회사 워크스페이스 계정이라 흔히 이야기하는 「확인되지 않은 앱」 경고는 나오지 않았습니다. 개인 Gmail 계정에서는 그 경고가 한 단계 더 있다고 알려져 있는데, 직접 확인하지는 못했습니다.

하나 더. 시트 값을 프로그램(API)으로 넣는 쪽도 시험했는데, 같은 계정인데도 브라우저에서 만든 시트를 못 찾았습니다.

Requested entity was not found.

API 쪽 도구가 자기가 만든 시트만 볼 수 있는 권한으로 연결돼 있었습니다. 그 도구로 시트를 새로 만들고 나서야 줄을 넣을 수 있었습니다. 외부 도구에서 시트가 “없다”고 하면 시트 ID 오타보다 연결된 권한 범위를 먼저 의심하십시오.

돌려보니 알게 된 것

한 줄 넣었는데 메일이 세 통 옵니다

날짜, 이름, 내용을 Tab 으로 넘기며 넣었더니 칸마다 한 통씩 왔습니다. 「수정 시」는 줄 단위가 아니라 셀 단위로 발동합니다. 세 번째 메일만 세 칸이 다 차 있고, 앞의 두 통은 뒤 칸이 비어 있었습니다.

날짜가 이렇게 옵니다

Sat Sep 12 2026 00:00:00 GMT+0900 (Korean Standard Time) | 홍길동 | 테스트 문의

getValues() 는 날짜 칸을 날짜 객체로 가져오고, 그걸 글자로 붙이면 저렇게 됩니다. getDisplayValues() 로 바꾸면 시트에 보이는 그대로 2026-09-12 가 옵니다.

입력 순서가 다르면 놓칩니다

“마지막 칸(내용)이 채워질 때만 보내자”로 고쳤더니 세 통이 한 통이 됐습니다. 그런데 내용을 먼저 적고 이름을 나중에 적으면 한 통도 안 옵니다. 실행 기록은 세 번 다 「완료됨」인데 메일이 없는 상태 — 이게 제일 찾기 어려운 종류입니다.

결국 이렇게 바꿨습니다. 줄이 다 찼고, D열이 비어 있으면 보내고, D열에 보낸 시각을 적는다. 순서를 바꿔 넣어도, 세 칸을 한 번에 붙여 넣어도 한 통이고, 나중에 내용을 고쳐도 다시 오지 않았습니다.

var TO = 'xxxx@xxxx.xx';
var LAST_COL = 3;          // A~C 가 입력 칸
var MARK_COL = 4;          // D 열에 '알림 보낸 시각'을 적어 둡니다

// '수정 시' 트리거용 — 방금 고친 줄만 봅니다
function alertOnRowEdit(e) {
  var range = e.range;
  checkRows(range.getSheet(), range.getRow(), range.getLastRow());
}

// '변경 시' 트리거용 — 시트 전체를 훑습니다
function scanAndAlert(e) {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
  checkRows(sheet, 2, sheet.getLastRow());
}

function checkRows(sheet, first, last) {
  for (var row = first; row <= last; row++) {
    if (row === 1) continue;                                    // 헤더 줄
    var vals = sheet.getRange(row, 1, 1, LAST_COL).getDisplayValues()[0];
    if (vals.some(function (v) { return v === ''; })) continue; // 아직 덜 찬 줄
    var mark = sheet.getRange(row, MARK_COL);
    if (mark.getValue() !== '') continue;                       // 이미 보낸 줄
    MailApp.sendEmail(TO,
      '[시트 알림] ' + row + '행 추가: ' + vals[1],
      '날짜: ' + vals[0] + '\n이름: ' + vals[1] + '\n내용: ' + vals[2]
      + '\n\n' + sheet.getParent().getUrl());
    mark.setValue(new Date());
  }
}

몇 초 만에 오나

셀에서 Enter 를 누른 시각과 실행 기록의 시작 시각을 여섯 번 비교했습니다. 5초를 넘긴 적이 없었습니다. 실행 자체는 0.3~2초입니다. 메일은 같은 분 안에 받은편지함에 있었는데, 도착 시각을 초 단위로는 확인하지 못했습니다.

프로그램이 넣은 줄은 「수정 시」로 못 잡습니다

API 로 줄을 추가해 봤습니다. 실행 기록에 아무것도 안 찍혔습니다. 「수정 시」는 사람이 손으로 고칠 때만 발동합니다. 폼이나 다른 프로그램이 시트에 넣는 줄은 이 트리거가 모릅니다.

같은 함수를 「변경 시」 트리거로 걸었더니 잡혔습니다. 로그에는 이렇게 남았습니다.

sent row=3.0 via change:INSERT_ROW at 2026-09-12T00:04:31.320Z

변경 시 는 방금 고친 칸을 알려 주지 않아서, 위 코드의 scanAndAlert 처럼 시트 전체를 훑고 D열이 빈 줄만 보내는 방식이어야 합니다. 앞서 「수정 시」가 놓친 줄도 이 한 번의 실행에서 같이 나갔습니다.

Apps Script 실행 기록 — 단순 트리거 실패와 설치형 트리거 완료가 나란히

이 방법이 안 맞는 경우

메일 발송에 하루 한도가 있습니다. 이 계정에서 MailApp.getRemainingDailyQuota() 를 찍어 보니 1,380 통이 남아 있었습니다(워크스페이스 계정). 공식 한도표 기준으로 개인 Gmail 계정은 이보다 훨씬 적습니다. 하루 수백 건이 들어오는 시트라면 메일이 아니라 다른 채널로 가야 합니다.

받는 사람이 여럿이거나 카카오톡으로 받아야 하면 Apps Script 만으로는 어렵습니다. 알림톡은 채널사를 거쳐야 하고 템플릿 심사가 있습니다. 여러 곳으로 나눠 보내는 흐름은 n8n 쪽이 맞습니다.

여러 사람이 같은 시트를 고치는 경우, 트리거는 스크립트를 만든 사람 계정으로 돕니다. 그 사람이 퇴사하거나 권한을 잃으면 알림이 멈추고, 위에서 본 것처럼 멈춰도 하루 뒤에야 압니다.

요약

Cannot read properties of undefined (reading 'source') 편집기 「실행」으로는 시험 못 합니다. 시트에 직접 넣으십시오
MailApp.sendEmail을(를) 호출할 수 없습니다 함수 이름이 onEdit 이면 못 보냅니다. 트리거를 직접 거십시오
트리거 기본값 「열릴 시」 「수정 시」 로 바꿔야 합니다
Script function not found 이름을 바꾸면 트리거를 다시 걸어야 합니다
한 줄에 메일 세 통 셀 단위로 발동합니다. D열에 보낸 표시를 남기십시오
프로그램이 넣은 줄 「수정 시」는 모릅니다. 「변경 시」 로 거십시오
실패 알림 「매일」 「즉시」 로 바꿔 두십시오

코드는 짧습니다. 시간이 간 곳은 코드가 아니라 트리거와 권한이었습니다.


참고
– Google Apps Script 공식 문서 — 단순 트리거의 제한 (Simple Triggers: Restrictions)
– Google Apps Script 공식 문서 — 서비스 할당량 (Quotas for Google Services)

Leave a Comment