Google Sheets 데이터를 기반으로 모두싸인 문서 생성하기
이 가이드는 Google Apps Script(GAS)를 활용하여 Google Spreadsheet의 데이터를 기반으로 모두싸인 템플릿을 사용해 문서를 자동 생성하고 전송하는 방법을 안내합니다.
본 예제는 모두싸인 API Key와 템플릿 ID를 활용한 API 호출을 기반으로 구성되어 있습니다.
API 메뉴
는 등록된 사용자에게만 표시됩니다. API 기능 이용을 희망하신다면 모두싸인 고객센터로 연락해 주세요.
📍 Step 1: 모두싸인 API Key 발급 받기
- 모두싸인 로그인 후 상단 메뉴에서 [설정 → API 키 관리]로 이동
- 새 API Key를 생성하여 복사해둡니다.
이 키는 모든 API 요청에 사용되므로 외부에 노출되지 않도록 주의하세요.
📍 Step 2: 템플릿 생성 및 템플릿 ID 확인
- 모두싸인 → 템플릿로 이동
- 문서를 템플릿으로 저장하고 필요한 문서 편집(매핑 대상 필드)를 설정합니다.
- 해당 템플릿의 ID를 확인해 복사해둡니다.
템플릿의 필드 키는 이후
requesterInputMappings
에 사용되니 정확히 확인하세요.
📍 Step 3: Google Sheet 준비하기
시트를 아래처럼 구성해주세요. (컬럼 순서 및 위치는 자유롭게 설정 가능하지만 코드와 일치해야 합니다.)
📍 Step 4: Google Apps Script 작성
확장 프로그램 → Apps Script
클릭- 아래 코드를 복사해서 붙여넣고,
apiKey
,templateId
,requester
정보를 자신의 값으로 수정하세요.
const SHEET_NAME = "테스트";
const API_URL = "https://api.modusign.co.kr/documents/request-with-template";
const EMAIL = "[email protected]"; // <-- 본인의 이메일 입력
const API_KEY = PropertiesService.getScriptProperties().getProperty("API_KEY"); // <-- 본인의 API Key 입력
const TEMPLATE_ID = "cecaea70-307f-11f0-a0e2-5f510e16c1d3"; // <-- 템플릿 ID 입력
const START_COLUMN = 7; // '문서편집' 입력란 필드 시작
const NUM_COLUMNS = 6; // '문서편집' 입력란 필드 갯수
const TRIGGER_COLUMN_INDEX = 13; // 문서전송 트리거 컬럼: N열 (0-indexed)
function sendRowToModusign(sheet, rowNumber) {
Logger.log(`🚀 sendRowToModusign 실행: rowNumber = ${rowNumber}`);
const { row, headerRow } = getRowAndHeader(sheet, rowNumber);
const payload = buildPayload(TEMPLATE_ID, headerRow, row, START_COLUMN, NUM_COLUMNS);
Logger.log("📥 요청 페이로드: " + JSON.stringify(payload));
Logger.log("📤 요청 전송 시작");
const options = buildRequestOptions(EMAIL, API_KEY, payload);
const response = UrlFetchApp.fetch(API_URL, options);
const responseCode = response.getResponseCode();
const responseText = response.getContentText();
Logger.log("📤 응답 코드: " + responseCode);
if (responseCode >= 400) {
const message = `❌ 전송 실패 [${responseCode}]: ${parseModusignError(responseCode, responseText)}`;
Logger.log(message);
const html = HtmlService.createHtmlOutput(`
<div style="padding:20px; font-family:sans-serif;">
${message}
</div>
`).setWidth(400).setHeight(200);
SpreadsheetApp.getUi().showModalDialog(html, "전송 실패");
sheet.getRange(rowNumber, TRIGGER_COLUMN_INDEX + 1).setValue("전송 실패");
return;
}
else if(responseCode === 201){
const message = `✅ ${row[2]}님의 문서 전송 성공`;
const html = HtmlService.createHtmlOutput(`
<div class="modal-body">
<p>${message}</p>
</div>
`).setWidth(400).setHeight(200);
SpreadsheetApp.getUi().showModalDialog(html, "알림");
sheet.getRange(rowNumber, TRIGGER_COLUMN_INDEX + 1).setValue("전송 완료");
}
}
function createInputMappings(headerRow, row, startColumn, numColumns) {
const mappings = [];
for (let j = 0; j < numColumns; j++) {
const header = headerRow[startColumn + j]?.trim();
const cellValue = row[startColumn + j];
if (!header) continue;
// "계약 연봉 총액:text" → dataLabel = "계약 연봉 총액", fieldType = "text"
const [dataLabel, fieldType = "text"] = header.split(":");
// 타입에 따른 value 처리
let parsedValue;
if (fieldType === "checkbox") {
// Google Sheets에서 checkbox는 true/false 또는 체크/미체크로 반환됨
parsedValue = cellValue === true || String(cellValue).toLowerCase() === "true";
} else {
parsedValue = String(cellValue);
}
mappings.push({
dataLabel,
value: parsedValue
});
}
return mappings;
}
function buildPayload(templateId, headerRow, row, startColumn, numColumns) {
const name = row[2]; // C열
const title = row[3]; // D열
const methodType = row[4]; // E열
const contact = methodType === "EMAIL" ? row[5] : row[6];
const requesterInputMappings = createInputMappings(headerRow, row, startColumn, numColumns);
return {
templateId: templateId,
document: {
title,
participantMappings: [
{
role: "임직원", //템플릿에 설정된 'role' 값으로 설정 필요
name: name,
signingMethod: {
type: methodType,
value: contact,
},
},
],
requesterInputMappings: requesterInputMappings
}
};
}
function buildRequestOptions(email, apiKey, payload) {
return {
method: "post",
contentType: "application/json",
headers: {
Authorization: "Basic " + Utilities.base64Encode(email + ":" + apiKey),
"Content-Type": "application/json",
},
payload: JSON.stringify(payload),
muteHttpExceptions: true,
};
}
function getRowAndHeader(sheet, rowNumber) {
return {
row: sheet.getRange(rowNumber, 1, 1, sheet.getLastColumn()).getValues()[0],
headerRow: sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0]
};
}
function parseModusignError(responseCode, responseText) {
try {
const errorJson = JSON.parse(responseText);
if (responseCode === 403 && errorJson.type === "ForbiddenException") {
return "권한 오류입니다. API 키 또는 이메일 인증을 확인해주세요.";
}
if (responseCode === 422 && errorJson.type === "RequesterInputDataLabelNotMatchedException" && errorJson.title === "Data label of requester input is not matched") {
return "템플릿에 정의되지 않은 필드 이름(dataLabel)이 포함되어 있습니다. 시트의 H~M 열 제목을 템플릿에 맞춰 수정해주세요.";
}
if (responseCode === 422 && errorJson.type === "UnprocessableEntityException") {
return "서명자 필드를 필드 이름에 포함되어 있습니다. 문서 편집 필드에 맞춰 수정해주세요.";
}
if (responseCode === 401 && errorJson.type === "UnauthorizedException" && errorJson.title === "Unauthorized") {
return "API Key가 맞는지 확인해주세요.";
}
return errorJson.message || responseText;
} catch (err) {
return responseText;
}
}
function onEditHandler(e) {
const sheet = e.source.getActiveSheet();
const editedRow = e.range.getRow();
const editedColumn = e.range.getColumn();
const triggerValue = e.value;
const oldValue = e.oldValue || "";
if (sheet.getName() !== SHEET_NAME) return;
// N열: 전송 트리거
if (
editedColumn === TRIGGER_COLUMN_INDEX + 1 &&
triggerValue === "전송" &&
oldValue !== "전송"
) {
Logger.log("📤 전송 조건 만족, sendRowToModusign 실행!");
sendRowToModusign(sheet, editedRow);
return;
}
}
📍Step 5: 트리거(Trigger) 설정하여 자동 실행하기
Google Sheets에서 특정 이벤트 발생 시 자동으로 API가 호출되도록 트리거를 설정할 수 있습니다.
이 가이드는 예제 코드 중 onEditHandler(e)
함수가 자동 실행되도록 트리거(Trigger)를 설정하는 방법을 안내합니다.
✅ 1. 스크립트 편집기 열기
- Google Sheets 상단 메뉴에서
확장 프로그램 → Apps Script
클릭
✅ 2. 트리거 메뉴 열기
- Apps Script 화면 상단 메뉴에서 🕒 아이콘 또는
좌측 메뉴 → 트리거
클릭
✅ 3. 새로운 트리거 추가하기
- 오른쪽 하단의
+ 트리거 추가
버튼 클릭 - 다음 항목을 설정합니다:

항목 | 설정값 |
---|---|
함수 선택 | onEditHandler |
이벤트 소스 선택 | 스프레드시트에서 |
이벤트 유형 선택 | 수정 시 |
✅ 4. 트리거 저장 및 권한 승인
- 트리거를 저장하면 처음 한 번은 승인 요청 팝업이 뜹니다.
- Google 계정으로 로그인하고, ‘고급’ → 프로젝트 이름 → 허용 클릭
✅ 5. 테스트 방법
- 시트의 "전송" 열에 값을 입력하면 자동으로 API가 호출되고, 전송 성공 여부가 자동으로 표시됩니다.
💡 참고
- 전송은
N열(13번째 열)
에 "전송" 이라고 입력하면 실행됩니다.
보안 관련 안내
API Key는 절대 외부에 노출되지 않도록 주의하세요.
Google Apps Script에는 Script Properties 기능으로 키를 숨길 수 있습니다.PropertiesService.getScriptProperties().getProperty("API_KEY");
Updated 1 day ago