구글 시트 알림, Apps Script 로 끝낼까 n8n 으로 보낼까 — 직접 둘 다 해본 기준

지난 글에서 구글 시트에 줄이 차면 메일이 오게 만들었습니다. Apps Script 였고, 시트 안에서 끝났습니다. 이번에는 같은 시트, 같은 목표를 n8n 으로 해 봤습니다.

결론부터 적으면, 시트 밖으로 나가는 순간 두 가지가 새로 필요해집니다. 하나는 구글 쪽 자격증명이고, 하나는 시트가 n8n 을 부를 수 있는 공개 주소입니다. Apps Script 에서는 둘 다 없던 것입니다. 하나에서는 접었고, 하나는 이 PC 에서 넘지 못했습니다. 그 사이에 n8n 쪽 절반은 돌아갔고 메일은 왔습니다.

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

무엇을 만들려 했나

시트는 지난 글과 같은 날짜 | 이름 | 내용 세 칸입니다. 줄이 다 차면 메일 한 통. n8n 은 설치 글의 그 PC, 그 n8n 입니다. 이번 기동은 91초 걸렸습니다. 다섯 번째 기동이고, 앞선 기동들(130~239초)보다 짧았습니다.

시트와 n8n 을 잇는 길은 두 가지가 있었습니다.

시트 → n8n n8n 이 새로 요구하는 것
A n8n 의 Google Sheets Trigger 노드가 1분마다 시트를 읽으러 감 구글 자격증명 (OAuth)
B 시트의 Apps Script 가 n8n 웹훅 주소를 호출 시트에서 부를 수 있는 공개 주소

A 부터 갔습니다. 노드 하나로 끝나 보였기 때문입니다.

경로 A — 막힌 지점 1: 자격증명 창이 구글 클라우드 콘솔로 보냅니다

Google Sheets Trigger 노드를 놓으면 자격증명을 만들라고 합니다. 창을 열면 이렇게 생겼습니다.

n8n Google Sheets Trigger 자격증명 창 — OAuth Redirect URL 과 Client ID·Client Secret 빈칸

Client ID 와 Client Secret 을 넣으라고 합니다. 지난 글의 Apps Script 는 권한 창에서 「허용」을 두 번 누르면 끝났는데, 여기는 그 값을 Google Cloud Console 에서 OAuth 클라이언트를 만들어 받아 와야 합니다. 창 아래에는 「Google Drive API, Google Sheets API 를 콘솔에서 사용 설정했는지 확인하라」는 안내도 있습니다.

마침 이 계정에는 다른 용도로 만들어 둔 OAuth 클라이언트가 하나 있었습니다. 그 값을 넣고 「Sign in with Google」 을 눌렀더니 구글 로그인 창이 이렇게 나왔습니다.

액세스 차단됨: 이 앱의 요청이 잘못되었습니다
400 오류: redirect_uri_mismatch
요청 세부정보: redirect_uri=http://localhost:5678/rest/oauth2-credential/callback
구글 로그인 창 — 액세스 차단됨, 400 오류 redirect_uri_mismatch

n8n 자격증명 창 맨 위에 있던 「OAuth Redirect URL」 을 콘솔의 「승인된 리디렉션 URI」에 등록해야 한다는 뜻입니다. n8n 은 그 주소를 보여 주기만 하고, 등록은 콘솔에서 사람이 합니다.

콘솔로 갔습니다. 들어가자마자 「본인 인증 — 계속하려면 먼저 본인임을 인증하세요」 비밀번호 창이 한 번. 클라이언트 화면에서 「URI 추가」에 주소를 넣고 「저장」을 눌렀더니 비밀번호 창이 또 한 번. 화면에는 「설정이 적용되는 데 5분에서 몇 시간이 걸릴 수 있습니다」라고도 적혀 있었습니다.

여기서 접었습니다. 시트 한 장의 알림을 위해 클라우드 콘솔 설정과 회사 계정 재인증 두 번, 그리고 반영 대기까지 — 실무자가 매번 할 일이 아니라고 봤습니다. 주소는 저장하지 않았고, 콘솔에 새로 만든 것은 없습니다.

하나 더 적어 둡니다. 이 자격증명이 요구하는 범위는 n8n 이 만든 로그인 주소에 그대로 들어 있었습니다. spreadsheets 외에 drive, drive.file, drive.metadata — 시트뿐 아니라 드라이브 전체입니다. 지난 글에서 Apps Script 가 「모든 Google Sheets 스프레드시트」를 요구했던 것과 넓이가 비슷하거나 더 넓습니다.

경로 B — 웹훅: n8n 쪽은 노드 두 개, 자격증명 없음

방향을 바꿨습니다. n8n 이 시트를 읽으러 가는 대신, 시트가 n8n 을 부르게 합니다. n8n 쪽은 Webhook → Send Email 두 노드이고, 구글 자격증명이 없습니다. Send Email 의 SMTP 는 지난 글과 같은 이 PC 의 시험용 메일 서버입니다.

n8n 캔버스 — Webhook → Send Email, 오른쪽 위 Published

Webhook 노드를 열면 주소가 두 개 나옵니다. Test URL (/webhook-test/…) 과 Production URL (/webhook/…). 시트가 부를 것은 Production 쪽입니다.

Webhook 노드 패널 — Test URL 과 Production URL 탭

막힌 지점 2 — 주소를 불렀더니 「등록되지 않았다」고 합니다

노드를 만들고 저장한 뒤, 시트 대신 이 PC 의 터미널에서 Production URL 을 불러 봤습니다.

HTTP/1.1 404 Not Found
{"code":404,
 "message":"The requested webhook \"POST sheet-alert\" is not registered.",
 "hint":"The workflow must be active for a production URL to run successfully.
         You can activate the workflow using the toggle in the top-right of the editor. …"}

저장은 했지만 Publish 를 안 한 상태였습니다. 지난 글에서 Error Workflow 가 회색이었던 것과 같은 이유입니다. 힌트가 말하는 「오른쪽 위의 토글」은 이 버전(2.8)에는 없고, 그 자리에 「Publish」 버튼이 있습니다. 누르면 「Workflow published — You can now make calls to your production webhook URL」 창이 뜹니다.

그리고 이 404 응답에는 stacktrace 로 이 PC 의 사용자 폴더 경로가 그대로 실려 있었습니다. C:\Users\사용자\AppData\… 식입니다. 지난 글에서 메일 본문에 실려 왔던 것과 같은 종류인데, 이번에는 부르는 쪽 누구에게나 갑니다.

Test URL 쪽도 불러 봤습니다. 이쪽은 Publish 와 상관없이 이렇게 답합니다.

Click the 'Execute workflow' button on the canvas, then try again.
(In test mode, the webhook only works for one call after you click this button)

버튼을 누른 뒤 한 번만 받는 주소입니다. 시트에 연결할 주소가 아닙니다.

받았습니다 — 1.8초

Publish 하고 같은 주소를 다시 불렀습니다.

시각
터미널에서 호출 01:54:46.46
n8n 실행 시작 (실행 125) 01:54:47.49
메일 서버 수신 01:54:48.28

호출에서 도착까지 1.8초입니다. 첫 실행이라 그렇고, 이어서 세 줄을 0.5초 간격으로 연달아 보냈더니 세 통이 각각 0.5초 안에 왔고 순서도 그대로였습니다. 실행 목록에는 다섯 건이 전부 webhook · success 로 남았습니다.

n8n 실행 목록 — 다섯 건이 webhook · Succeeded 로 찍힌 화면

메일 본문은 이렇게 왔습니다. Send Email 노드의 표현식은 웹훅이 받은 JSON 을 $json.body 로 읽습니다.

날짜: 2026-09-13
이름: 홍길동
내용: 테스트 문의 (curl)
제목  =[시트 알림] {{ $json.body.row }}행 추가: {{ $json.body.이름 }}
본문  =날짜: {{ $json.body.날짜 }}
      이름: {{ $json.body.이름 }}
      내용: {{ $json.body.내용 }}

첫 시도에서는 이름 칸이 비어 왔습니다. 원문을 보니 한글이 전부 깨져 있었습니다. n8n 이 아니라 보내는 쪽 문제였습니다 — Windows 터미널이 한글을 다른 인코딩으로 보냈고, n8n 은 UTF-8 로 읽었습니다. 본문을 UTF-8 파일로 만들어 보내니 정상이었습니다. 시트에서 부를 때는 구글이 보내는 쪽이라 이 문제가 없을 것으로 봅니다만, 아래 이유로 직접 확인하지는 못했습니다.

막힌 지점 3 — 시트에서 n8n 을 못 부릅니다. 주소가 localhost 입니다

이제 시트 쪽입니다. 지난 글의 코드에서 MailApp.sendEmail 한 줄을 UrlFetchApp.fetch 로 바꾼 것이 전부입니다. 줄이 다 찼고 D열이 비어 있으면 n8n 을 부르고, D열에 보낸 시각을 적습니다.

var HOOK = 'http://localhost:5678/webhook/sheet-alert';   // n8n 웹훅 주소
var LAST_COL = 3;          // A~C 가 입력 칸
var MARK_COL = 4;          // D 열에 '보낸 시각'을 적어 둡니다

// '수정 시' 설치형 트리거용
function alertOnRowEdit(e) {
  var sheet = e.range.getSheet();
  for (var row = e.range.getRow(); row <= e.range.getLastRow(); 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;                       // 이미 보낸 줄
    var res = UrlFetchApp.fetch(HOOK, {
      method: 'post',
      contentType: 'application/json',
      payload: JSON.stringify({ row: row, 날짜: vals[0], 이름: vals[1], 내용: vals[2] })
    });
    Logger.log('sent row=' + row + ' status=' + res.getResponseCode());
    mark.setValue(new Date());
  }
}

지난 글대로 「수정 시」 설치형 트리거를 걸었습니다. 저장을 누르니 권한 창이 떴는데, 지난번과 항목이 하나 다릅니다.

See, edit, create, and delete all your Google Sheets spreadsheets
Connect to an external service
Apps Script 권한 창 — Connect to an external service 항목이 추가된 화면

「외부 서비스에 연결」 이 새로 붙었습니다. UrlFetchApp 을 쓰면 나오는 항목입니다. 시트 안에서 끝날 때는 없던 권한입니다.

그런데 이 권한을 허용해도 이 코드는 n8n 에 닿지 않습니다. 주소가 http://localhost:5678 이기 때문입니다. localhost 는 「이 컴퓨터」라는 뜻이고, Apps Script 는 제 PC 가 아니라 구글 서버에서 돕니다. 구글 서버의 localhost 에는 n8n 이 없습니다. 지난 글에서 메일 안의 localhost 링크가 다른 기기에서 안 열렸던 것과 같은 문제인데, 이번에는 링크가 아니라 알림 자체가 못 갑니다.

이 시험에서는 여기서 멈췄습니다. 권한 창을 허용하고 줄을 넣어 봐야 결과가 뻔했고, 넘으려면 다음 둘 중 하나가 필요합니다.

n8n 을 서버에 올리거나, 이 PC 의 n8n 을 밖에서 부를 수 있게 터널을 뚫는 것. 예전 n8n 에는 n8n start --tunnel 이라는 시험용 옵션이 있었다고 알고 있는데, 이 버전(2.8.4)에서 n8n start --help 를 쳐 보니 남은 옵션은 -o(브라우저 열기) 하나뿐이었습니다. 따로 터널 도구를 깔아야 하는데, 이 PC 에서는 회사 보안 프로그램이 실행 파일 내려받기를 막아서 거기까지 가지 못했습니다. 그래서 이 글의 n8n 쪽 수치는 전부 같은 PC 안에서 부른 것입니다. 시트에서 부르면 구글 서버에서 이 PC 까지 오는 시간이 더해집니다.

같은 시트, 같은 목표 — 둘을 나란히

Apps Script (지난 글) n8n 웹훅 (이 글, 경로 B) n8n Sheets Trigger (경로 A)
설치 없음 n8n 설치 · 켤 때마다 1.5~4분 같음
구글 자격증명 권한 창에서 「허용」 2번 시트 쪽 권한 창 1번 (외부 서비스 연결 추가) · n8n 쪽 없음 클라우드 콘솔에서 OAuth 클라이언트 + 리디렉션 URI 등록
공개 주소 필요 없음 필요 — localhost 는 시트가 못 부름 필요 없음 (n8n 이 구글을 부름)
보내는 메일 서버 구글이 보냄 SMTP 따로 연결 같음
막힌 횟수 3 2 + 못 넘은 것 1 1에서 접음
걸린 시간 약 1시간 10분 약 25분 (n8n 쪽만) 약 35분 (접을 때까지)
발동에서 도착까지 편집 → 실행 5초 이내 호출 → 메일 0.5~1.8초 (같은 PC 안) 1분 폴링이 최소 단위 (노드 설정 기본값 everyMinute)
연속 입력 셀 단위로 3통 (D열 표시로 해결) 3건 연속 → 3통, 순서 유지 시험 못 함

「막힌 횟수」와 「걸린 시간」은 이 계정, 이 PC, 이 순서로 한 번 해 본 값입니다. 다른 환경에서 같으리라고는 말 못 합니다.

그래서 어느 쪽인가

시트 한 장에서 메일 한 통이면 Apps Script 입니다. 설치도, 자격증명도, 공개 주소도, 메일 서버도 없습니다. 지난 글의 함정 세 개만 피하면 됩니다.

n8n 이 맞는 경우는 시트가 시작점이되 끝이 메일이 아닐 때입니다. 여러 곳으로 나눠 보내거나, 다른 시스템에 넣거나, 조건이 복잡해질 때. 다만 그때도 이 글에서 본 대로 n8n 이 공개 주소에 있어야 시트가 부를 수 있고, 시트를 n8n 이 직접 읽게 하려면 클라우드 콘솔 작업이 따라옵니다. 이 PC 에서 npx n8n start 로 켠 n8n 은 시트에서 보이지 않습니다.

이 글에서 못 한 것을 그대로 적어 둡니다. 시트 → n8n 구간은 실측하지 못했습니다. 경로 A 는 콘솔 재인증에서 접었고, 경로 B 는 공개 주소가 없어 시트 쪽에서 부르지 못했습니다. n8n 을 서버에 올린 뒤에 같은 시트로 다시 재 볼 생각입니다.

요약

Google Sheets Trigger 의 Client ID·Secret 클라우드 콘솔에서 OAuth 클라이언트를 만들어야 합니다
400 오류: redirect_uri_mismatch n8n 이 보여 준 OAuth Redirect URL 을 콘솔의 리디렉션 URI 에 등록해야 합니다
콘솔 「본인 인증」 들어갈 때 한 번, 저장할 때 또 한 번 비밀번호를 묻습니다
웹훅 404 … is not registered Publish 해야 Production URL 이 열립니다. 힌트의 「토글」은 이 버전에 없습니다
Test URL 버튼을 누른 뒤 한 번만 받습니다. 시트에 연결할 주소가 아닙니다
404 응답의 stacktrace 이 PC 의 폴더 경로가 부르는 쪽에 그대로 갑니다
이름 칸이 비어 옴 보내는 쪽 인코딩. UTF-8 로 보내면 정상
Apps Script 권한 「외부 서비스에 연결」 UrlFetchApp 을 쓰면 추가됩니다
주소가 localhost 구글 서버에서 못 부릅니다. 공개 주소가 있어야 합니다
n8n start --tunnel 2.8.4 에는 없습니다 (--help 에 -o 뿐)

n8n 쪽 노드는 두 개였고 2초 안에 왔습니다. 시간이 간 곳은 노드가 아니라 시트와 n8n 사이의 경계 — 자격증명과 주소였습니다.


참고
– n8n 공식 문서 — Webhook 노드 (Webhook node), Test URL 과 Production URL
– n8n 공식 문서 — Google 자격증명 (Google OAuth2 single service)
– Google Apps Script 공식 문서 — URL Fetch Service (UrlFetchApp), 승인 범위

Leave a Comment