컴퓨터/google spreadsheet

Google Forms 응답을 Slack으로 보내는 완벽 가이드

풍경소리^^ 2025. 5. 22. 15:33

Google Forms 응답을 Slack으로 보내는 완벽 가이드 (Apps Script 속성 사용)

이 가이드는 Google Forms가 제출될 때, 해당 응답 데이터를 Slack으로 자동 전송하는 방법을 안내합니다.

준비물:

  • Google 계정 (Gmail, Google Drive, Google Forms 접근 가능)
  • Slack 워크스페이스 및 채널 (알림을 받을 채널)
  • 해당 Slack 채널에 Incoming Webhooks 통합 앱 추가 및 웹훅 URL 발급 (가장 먼저 해야 할 중요 작업)

1단계: Slack Incoming Webhook URL 발급

  1. Slack 워크스페이스에 로그인합니다.
  2. 알림을 받을 특정 채널 (예: #입사자슬랙웹훅)을 선택합니다.
  3. 채널 이름 위에서 마우스 오른쪽 버튼을 클릭하거나, 채널 헤더의 드롭다운 메뉴를 클릭합니다.
  4. 통합 > 앱 추가 (또는 앱 디렉터리 열기)로 이동합니다.
  5. Incoming WebHooks 앱을 검색하여 추가합니다. (이미 추가되어 있다면 해당 앱을 찾아 구성합니다.)
  6. 웹훅 URL을 생성합니다. (일반적으로 Add to Slack 또는 Add configuration 버튼을 누른 후, 알림을 보낼 채널을 선택하면 URL이 생성됩니다.)
  7. 생성된 웹훅 URL (https://hooks.slack.com/services/...로 시작)을 정확하게 복사합니다. 이 URL은 나중에 Apps Script에서 사용합니다.

2단계: Google Forms 및 스프레드시트 준비

  1. 새로운 Google Forms 설문지를 만듭니다 (또는 기존 설문지를 사용합니다).
  2. 설문지 상단 메뉴에서 응답 탭을 클릭합니다.
  3. 초록색 스프레드시트 아이콘을 클릭하여 응답을 받을 새로운 스프레드시트를 만듭니다. (기존 스프레드시트에 연결할 수도 있습니다.)
    • 스프레드시트가 열리면, 설문지 질문 제목들이 스프레드시트의 첫 번째 행(헤더)에 자동으로 매핑됩니다. (예: "성명", "입사일" 등)

3단계: Apps Script 코드 작성 및 설정

  1. 연결된 스프레드시트에서 확장 프로그램 > Apps Script를 클릭하여 Apps Script 편집기를 엽니다.
  2. 기존 코드를 모두 지우고, 아래 제공된 코드를 붙여넣습니다.
/**
 * 구글 설문지 응답이 제출될 때 슬랙으로 알림을 보내는 앱스크립트 코드
 *
 * Copyright (c) 2025 Hyunjung Cho
 * MIT 라이센스에 따라 배포됩니다.
 * https://opensource.org/licenses/MIT
 */

/**
 * 폼 제출 시 실행되는 함수
 * @param {Object} e 폼 제출 이벤트 객체
 */
function onFormSubmit(e) {
  // 스크립트 속성에서 웹훅 URL 가져오기
  // 'SLACK_WEBHOOK_URL'은 스크립트 속성에서 설정할 키(이름)입니다.
  const SLACK_WEBHOOK_URL = PropertiesService.getScriptProperties().getProperty('SLACK_WEBHOOK_URL');

  // 웹훅 URL이 제대로 설정되었는지 확인
  if (!SLACK_WEBHOOK_URL) {
    Logger.log("오류: SLACK_WEBHOOK_URL 스크립트 속성이 설정되지 않았습니다. 슬랙 알림을 보낼 수 없습니다.");
    return; // 웹훅 URL이 없으면 함수 실행 중단
  }

  // 폼 응답 데이터 가져오기 (Google Forms 질문 제목과 정확히 일치해야 합니다)
  const formResponse = e.namedValues;

  // --- 여기에 실제 구글 폼의 질문 제목과 매핑되는 변수를 정의합니다 ---
  // 예시: const 성명 = formResponse["성명"] ? formResponse["성명"].toString() : "정보 없음";
  // 실제 폼 질문 제목에 맞게 수정하세요.
  const 성명 = formResponse["성명"] ? formResponse["성명"].toString() : "정보 없음";
  const 입사일 = formResponse["입사일"] ? formResponse["입사일"].toString() : "정보 없음";
  const 주민등록번호 = formResponse["주민등록번호"] ? formResponse["주민등록번호"].toString() : "정보 없음";
  const 생일 = formResponse["생일"] ? formResponse["생일"].toString() : "정보 없음";
  const 주소 = formResponse["주소"] ? formResponse["주소"].toString() : "정보 없음";
  const 휴대폰번호 = formResponse["휴대폰번호"] ? formResponse["휴대폰번호"].toString() : "정보 없음";
  const 여권상영문이름 = formResponse["여권상 영문이름"] ? formResponse["여권상 영문이름"].toString() : "정보 없음";
  const 여권번호 = formResponse["여권 번호"] ? formResponse["여권 번호"].toString() : "정보 없음";
  const 여권만료일 = formResponse["여권 만료일"] ? formResponse["여권 만료일"].toString() : "정보 없음";
  const 최종출신학교 = formResponse["최종 출신학교"] ? formResponse["최종 출신학교"].toString() : "정보 없음";
  const 전공 = formResponse["전공"] ? formResponse["전공"].toString() : "정보 없음";
  const 학력 = formResponse["학력"] ? formResponse["학력"].toString() : "정보 없음";
  const 병역 = formResponse["병역"] ? formResponse["병역"].toString() : "정보 없음";
  const 이메일 = formResponse["이메일"] ? formResponse["이메일"].toString() : "정보 없음";
  // --- 매핑 끝 ---


  // 메시지 구성
  let message = "✨ *입사자 등록*\n";
  message += `성명 : ${성명}\n`;
  message += `입사일 : ${입사일}\n`;
  message += `주민등록번호 : ${주민등록번호}\n`;
  message += `생일 : ${생일}\n`;
  message += `주소 : ${주소}\n`;
  message += `휴대폰번호 : ${휴대폰번호}\n`;
  message += `여권상 영문이름 : ${여권상영문이름}\n`;
  message += `여권 번호 : ${여권번호}\n`;
  message += `여권 만료일 : ${여권만료일}\n`;
  message += `최종 출신학교 : ${최종출신학교}\n`;
  message += `전공 : ${전공}\n`;
  message += `학력 : ${학력}\n`;
  message += `병역 : ${병역}\n`;
  message += `이메일 : ${이메일}\n`;

  // 스프레드시트 링크 추가
  const spreadsheetUrl = SpreadsheetApp.getActiveSpreadsheet().getUrl();
  message += `\n<${spreadsheetUrl}|📊 자세한 내용은 여기에서 확인>`;

  // Slack에 전송할 페이로드 구성
  const payload = {
    "text": message
  };

  // Slack에 POST 요청 보내기
  const options = {
    "method": "post",
    "contentType": "application/json",
    "payload": JSON.stringify(payload)
  };

  // 웹훅 URL로 요청 전송
  try {
    const response = UrlFetchApp.fetch(SLACK_WEBHOOK_URL, options);
    Logger.log("슬랙 알림 전송 성공: " + response.getContentText());
  } catch (error) {
    Logger.log("슬랙 알림 전송 실패: " + error.toString());
  }
}

 

  1. 변경 사항 저장: Ctrl + S (Mac: ⌘ + S)를 눌러 코드를 저장합니다.

4단계: 스크립트 속성(Properties) 설정 (가장 중요)

스크립트 속성에 Slack 웹훅 URL을 안전하게 저장합니다.

  1. Apps Script 편집기 왼쪽 사이드바에서 **톱니바퀴 아이콘 (프로젝트 설정)**을 클릭합니다.
  2. 아래로 스크롤하여 "스크립트 속성" 섹션을 찾습니다.
  3. "+ 스크립트 속성 추가" 버튼을 클릭합니다.
    • 속성 칸에 정확하게 SLACK_WEBHOOK_URL 이라고 입력합니다. (대소문자 구분 중요!)
    • 칸에 1단계에서 복사한 실제 Slack 웹훅 URL을 붙여넣습니다. (예: https://hooks.slack.com/services/T01234/B01234/F01234)
  4. "스크립트 속성 저장" 버튼을 클릭합니다.

5단계: 트리거 설정 (핵심)

폼 제출 시 onFormSubmit 함수가 자동으로 실행되도록 트리거를 설정합니다.

  1. Apps Script 편집기 왼쪽 사이드바에서 **시계 아이콘 (트리거)**을 클릭합니다.
  2. 오른쪽 하단의 "+ 트리거 추가" 버튼을 클릭합니다.
  3. 다음과 같이 설정합니다:
    • 실행할 함수 선택: 드롭다운에서 onFormSubmit을 선택합니다.
    • 이벤트 소스 선택: 스프레드시트에서를 선택합니다.
    • 이벤트 유형 선택: 양식 제출 시를 선택합니다.
  4. 저장 버튼을 클릭합니다.
  5. 권한 승인: 저장을 클릭하면 Google 계정 권한 승인 팝업이 나타날 수 있습니다.
    • 권한 검토 또는 허용 버튼을 클릭합니다.
    • 본인의 Google 계정을 선택합니다.
    • (경고 메시지가 나오더라도) 고급을 클릭하고 ~으로 이동을 선택하여 모든 필요한 권한을 허용해 줍니다. (예: Google Docs, 외부 URL 연결, 스프레드시트 보기 등)
    • 이 단계가 완료되어야 스크립트가 Slack에 메시지를 보낼 수 있습니다.

6단계: 테스트 및 확인

  1. Apps Script 편집기에서 함수를 직접 실행하지 마십시오. (그래야 e.namedValues 오류가 발생하지 않습니다.)
  2. Google Forms 설문지 링크로 이동하여 새로운 응답을 제출합니다.
  3. 응답을 제출한 후, Apps Script 편집기로 돌아와서 왼쪽 사이드바의 **실행 아이콘 (세 줄에 화살표 달린 모양)**을 클릭합니다.
  4. 가장 최근 onFormSubmit 함수가 완료됨 상태로 실행되었는지 확인합니다.
  5. 해당 실행 기록을 클릭하여 상세 로그에 슬랙 알림 전송 성공: ... 메시지가 출력되었는지 확인합니다.
  6. Slack 채널(예: #입사자슬랙웹훅)을 확인하여 알림 메시지가 도착했는지 최종 확인합니다.

문제 발생 시 디버깅:

  • Slack 알림이 오지 않는데, 실행 로그에 슬랙 알림 전송 실패가 있다면:
    • 로그의 자세한 오류 메시지를 확인합니다.
    • Slack 웹훅 URL이 유효한지, Slack에서 해당 웹훅 통합이 활성화되어 있는지 다시 확인합니다.
  • 실행 로그에 onFormSubmit 기록 자체가 없거나 실패로 표시된다면:
    • 5단계의 트리거 설정을 다시 한번 꼼꼼히 확인하고 권한 승인을 완료했는지 확인합니다.
    • 브라우저 캐시를 지우고 다시 시도해봅니다.

이 가이드라인을 따라하시면 다른 설문지를 작성할 때도 쉽게 적용하실 수 있을 것입니다.

 

신버전 웹훅 아이콘 변경 "icon_emoji": ":fire:"

/**
 * 구글 설문지 응답이 제출될 때 슬랙으로 알림을 보내는 앱스크립트 코드
 * (Block Kit을 사용하여 메시지 시각화 개선 - 주민등록번호 포함)
 *
 * Copyright (c) 2025 Hyunjung Cho
 * MIT 라이센스에 따라 배포됩니다.
 * https://opensource.org/licenses/MIT
 */

function onFormSubmit(e) {
  const SLACK_WEBHOOK_URL = PropertiesService.getScriptProperties().getProperty('SLACK_WEBHOOK_URL');

  if (!SLACK_WEBHOOK_URL) {
    Logger.log("오류: SLACK_WEBHOOK_URL 스크립트 속성이 설정되지 않았습니다. 슬랙 알림을 보낼 수 없습니다.");
    return;
  }

  // 폼 응답 데이터 가져오기 (Google Forms 질문 제목과 정확히 일치해야 합니다)

  const formResponse = e.namedValues;

  // 필요한 필드 매핑 (주민등록번호 포함)
  const 성명 = formResponse["성명"] ? formResponse["성명"].toString() : "정보 없음";
  const 입사일 = formResponse["입사일"] ? formResponse["입사일"].toString() : "정보 없음";
  const 주민등록번호 = formResponse["주민등록번호"] ? formResponse["주민등록번호"].toString() : "정보 없음"; // 주민등록번호 활성화
  const 생일 = formResponse["생일"] ? formResponse["생일"].toString() : "정보 없음";
  const 주소 = formResponse["주소"] ? formResponse["주소"].toString() : "정보 없음";
  const 휴대폰번호 = formResponse["휴대폰번호"] ? formResponse["휴대폰번호"].toString() : "정보 없음";
  const 여권상영문이름 = formResponse["여권상 영문이름"] ? formResponse["여권상 영문이름"].toString() : "정보 없음";
  const 여권번호 = formResponse["여권 번호"] ? formResponse["여권 번호"].toString() : "정보 없음";
  const 여권만료일 = formResponse["여권 만료일"] ? formResponse["여권 만료일"].toString() : "정보 없음";
  const 최종출신학교 = formResponse["최종 출신학교"] ? formResponse["최종 출신학교"].toString() : "정보 없음";
  const 전공 = formResponse["전공"] ? formResponse["전공"].toString() : "정보 없음";
  const 학력 = formResponse["학력"] ? formResponse["학력"].toString() : "정보 없음";
  const 병역 = formResponse["병역"] ? formResponse["병역"].toString() : "정보 없음";
  const 이메일 = formResponse["이메일"] ? formResponse["이메일"].toString() : "정보 없음";

  const spreadsheetUrl = SpreadsheetApp.getActiveSpreadsheet().getUrl();

  // Slack 메시지 구성 (Block Kit 사용)
  const blocks = [
    {
      "type": "header",
      "text": {
        "type": "plain_text",
        "text": "✨ 신규 입사자 등록",
        "emoji": true
      }
    },
    {
      "type": "divider"
    },
    {
      "type": "section",
      "fields": [
        { "type": "mrkdwn", "text": `*성명: ${성명}` },
        { "type": "mrkdwn", "text": `*입사일: ${입사일}` },
        { "type": "mrkdwn", "text": `*생일: ${생일}` },
        { "type": "mrkdwn", "text": `*휴대폰 번호: ${휴대폰번호}` }
      ]
    },
    {
      "type": "section",
      "fields": [
        { "type": "mrkdwn", "text": `*주민등록번호: ${주민등록번호}` }, // 주민등록번호 추가
        { "type": "mrkdwn", "text": `*이메일: ${이메일}` }
      ]
    },
    {
      "type": "section",
      "fields": [
         { "type": "mrkdwn", "text": `*주소: ${주소}` }
      ]
    },
    {
      "type": "divider"
    },
    // 이전의 "자세한 개인 정보 (주민등록번호, 여권 등)는 보안상 별도 표기하지 않습니다." 섹션 제거
    {
      "type": "section",
      "fields": [
        { "type": "mrkdwn", "text": `*여권상 영문이름: ${여권상영문이름}` },
        { "type": "mrkdwn", "text": `*여권 번호: ${여권번호}` },
        { "type": "mrkdwn", "text": `*여권 만료일: ${여권만료일}` }
      ]
    },
    {
      "type": "divider"
    },
    {
      "type": "section",
      "fields": [
        { "type": "mrkdwn", "text": `*최종 출신학교: ${최종출신학교}` },
        { "type": "mrkdwn", "text": `*전공: ${전공}` },
        { "type": "mrkdwn", "text": `*학력: ${학력}` },
        { "type": "mrkdwn", "text": `*병역: ${병역}` }
      ]
    },
    {
        "type": "divider"
    },
    {
      "type": "context",
      "elements": [
        {
          "type": "mrkdwn",
          "text": `<${spreadsheetUrl}|📊 모든 응답 자세히 보기>`
        }
      ]
    }
  ];

  // Slack에 전송할 페이로드 구성
  const payload = {
    "text": "새로운 입사자 등록 알림",
    "blocks": blocks,
    "username": "입사자 알림 봇",
    "icon_emoji": ":fire:"
  };

  const options = {
    "method": "post",
    "contentType": "application/json",
    "payload": JSON.stringify(payload)
  };

  try {
    const response = UrlFetchApp.fetch(SLACK_WEBHOOK_URL, options);
    Logger.log("슬랙 알림 전송 성공: " + response.getContentText());
  } catch (error) {
    Logger.log("슬랙 알림 전송 실패: " + error.toString());
  }
}