데이터는 쌓았다 — 이제 그 데이터가 일하게 만들 차례다.
이 글은 생산성 1편 → 2편 → 3편에서 이어집니다.
3편까지 만든 것, 그리고 아직 남은 문제
Order_List에 발주 데이터가 쌓이기 시작했다.
PI와 PO에 PI 번호 하나만 입력하면 거래처·품목·금액이 자동으로 채워졌다.
Customer_Master와 Item_Master를 마스터 데이터로 연결해서 입력 실수도 줄였다. 거기다가 구글 환경을 이용해서 팀이 같은 파일을 실시간으로 보는 구조가 됐다.
근데 월말에 팀장님이 말씀하신다.
"이번 달 거래 현황 정리해서 공유해줄 수 있어요?"

Order List에 데이터는 다 있었지만 나는 그걸 또 열어서 월 기준으로 필터 걸고, 바이어 별로 합계 내고, 복사해서, 메일을 썼다. 한 두시간이 걸렸다.
그때 깨달았다.
데이터를 쌓는 구조는 만들었는데, 그 데이터가 의사결정에 도움을 주지 못했다.
이번 글에서 할 것은 딱 두 가지다.
기존 파일에 시트 하나를 추가하고, 코드 하나를 연결하는 것이다.
- 쌓인 데이터를 보여주는 대시보드 시트 추가
- 그 대시보드를 매달 알아서 공유하는 자동 알림 시스템
(Google App Script + Zapier)
이걸 완성하니 월말에 작성하던 보고서와 알림이 내 손을 떠나게 되었다.
오늘 만들어 볼 업무 자동화 시스템

기존 시트는 손대지 않는다.
대시보드 시트를 새로 만들고, GAS와 Zapier를 연결하는 것만 하면 된다.
이게 완성되면 없어지는 수동 작업들
- 월말 Order_List 필터링 → 없어짐
- 고객 별 합계 직접 계산 → 없어짐
- 보고서 메일 직접 발송 → 없어짐
- 슬랙 수동 알림 → 없어짐
왜 대시보드가 필요할까?
대시보드(Dashboard)는 자동차나 비행기의 계기판처럼, 여러 데이터 소스에서 가져온 핵심 성과 지표와 정보를 시각화(그래프, 차트)하여 한 화면에 보여주는 도구입니다.
Order_List는 데이터를 넣는 곳이다. (데이터를 input 하는 곳)
거기서 "이번 달 AURORA TRADING 거래 합계가 얼마지?"를 보려면 직접 필터를 걸거나 계산해야 한다.
대시보드는 그 질문에 자동으로 답해주는 시트다.
Order_List에 데이터가 쌓이면 대시보드가 알아서 의사결정에 필요한 숫자만 보여주는 것이다.
구글 시트로 만든 오더 월간 집계 대시보드
Order_List 열 구조 먼저 확인
QUERY 수식을 쓰려면 어느 열에 뭐가 있는지 알아야 한다.
| 열 | 항목 | 예시 |
|---|---|---|
| A | Season | FW26 |
| B | Date | 2026-01-02 |
| C | PO | PO20260102-01 |
| D | PI | PI20260102-01 |
| E | Customer | AURORA TRADING |
| F | Style | TRM-225 |
| G | Item_Code | I002 |
| H | Item | Zipper #5 Nylon |
| I | Size | XL |
| J | Color | BEIGE |
| K | QTY | 1000 |
| L | Price | 0.996 |
| M | Price_Amount | 996.00 |
| N | Cost | 0.5484 |
| O | Cost_Amount | 548.40 |
| P | Factory | Hanoi C |
우리가 매월 1일에 '고객 별 이번 달 금액'을 알고 싶다고 한다면 '고객', '이번 달, '합계'에 해당하는 열을 찾아야 하는 것이다. 위의 데이터에서 해당하는 열은 다음과 같다.
- B열 = data
- E열 = customer
- M열 = Price_Amount
- O열 = Cost_Amount
대시보드 시트 구성
새 시트를 만들고 이름을 DASHBOARD로 지정한다.
3가지 집계 블록을 순서대로 배치한다.
블록 1 : 이번 달 요약 (수동 입력 없이 자동 계산)

- 수식1. 총 매출/총 원가/총 수량 집계 - SUMIFS 함수 사용
위 수식은 실제 총 매출에 적용된 수식으로 SUMIFS는 조건이 여러 개인 셀을 계산하는 것이다.
위의 내용을 풀어보면,
- "M열(Price_Amount)을 더할 건데"
- "B열(Date)이 이달 1일 이상인 것만"
- "그리고 B열이 다음 달 1일 미만인 것만"
결국, 이 달 데이터만 골라서 합산하고 싶다는 의미의 이야기로 해석하면 된다.
블록 2&3 — Customer/Factory별 월간 매출/원가 (QUERY)

- 수식2. 고객 별, 공장 별 집계 - QUERY 함수 사용
위 수식은 Customer별 월간 매출에 사용한 수식으로 Query 함수를 사용했다.
이 Query 함수는 엑셀에서 피벗 테이블 써본 적 있다면 바로 이해될 것인데 쉽게 피벗 테이블을 수식으로 만든 개념으로 이해하면 될 것 같다.
- 필터 → 조건에 맞는 데이터만 보기
- 피벗 테이블 → 항목 별로 묶어서 합계 보기
위의 수식을 풀어서 설명해 보면 아래와 같이 이해해 볼 수 있다.
- SELECT - 어떤 열을 보여줄지 - 메뉴 고르기
- WHERE - 어떤 조건인 것만 - 필터 걸기
- GROUP BY - 무엇을 기준으로 묶을지 - 피벗 테이블
- ORDER BY - 어떤 순서로 정렬할지 - 정렬 버튼
추가로 숫자 표만으로도 의사 결정에 충분하지만, 차트를 추가하면 순위와 비율 등을 더 쉽게 볼 수 있다.
표 범위를 선택하고 삽입 → 차트를 클릭하고 추가하면 별도 설정 없이 구글 시트가 자동으로 차트를 생성해준다.
GAS 실습 — 월간 보고서 자동 발송
대시보드 시트가 완성됐다면, 이제 이걸 이메일로 매달 자동으로 공유하는 코드를 짠다.

GAS가 하는 일
매월 1일 오전 9시 → Order_list 시트에서 전월 데이터 집계
→ DASHBOARD 시트 링크 + 요약 내용을 이메일 본문에 담아
→ 지정한 수신자에게 자동 발송(구글 앱 스크립트 코드도 AI를 활용해서 작성하면 된다.)
코드 설명
Apps Script 에디터를 열면 (확장 프로그램 → Apps Script) 아래 작성한 코드를 붙여 넣는다.
- 사용한 코드 :
전체 코드 (누르면 보여요! )
// ============================================================
// 설정값 — 여기만 수정하면 됩니다
// ============================================================
const CONFIG = {
INPUT_SHEET: 'Order_List', // 데이터가 쌓이는 시트
DASHBOARD_SHEET: 'DASHBOARD', // 집계 대시보드 시트
RECIPIENTS: 'manager@company.com', // 수신자 이메일 (쉼표로 여러 명 가능)
COL: {
DATE: 1, // B열: Date
CUSTOMER: 4, // E열: Customer
PRICE_AMOUNT: 12, // M열: Price_Amount
COST_AMOUNT: 14, // O열: Cost_Amount
QTY: 10, // K열: QTY
FACTORY: 15 // P열: Factory
}
};
// ============================================================
// 메인 함수 — 트리거로 자동 실행됩니다
// ============================================================
function sendMonthlyReport() {
try {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const now = new Date();
// 전월 기준으로 집계 (매월 1일에 실행하므로)
const targetMonth = now.getMonth() === 0 ? 11 : now.getMonth() - 1;
const targetYear = now.getMonth() === 0 ? now.getFullYear() - 1 : now.getFullYear();
const monthLabel = `${targetYear}년 ${targetMonth + 1}월`;
// 1. 데이터 집계
const summary = getMonthlyData(ss, targetYear, targetMonth);
// 2. 이메일 발송
sendReportEmail(ss, summary, monthLabel);
Logger.log(`✅ ${monthLabel} 월간 보고서 발송 완료`);
} catch (e) {
Logger.log('❌ 오류: ' + e.message);
GmailApp.sendEmail(
CONFIG.RECIPIENTS,
'[오류] 월간 보고서 자동 발송 실패',오류 내용: ${e.message}\n\n수동 발송이 필요합니다.
);
}
}
// ============================================================
// 데이터 집계 함수
// ============================================================
function getMonthlyData(ss, year, month) {
const sheet = ss.getSheetByName(CONFIG.INPUT_SHEET);
const [headers, ...rows] = sheet.getDataRange().getValues();
// 해당 월 데이터만 필터링
const monthlyRows = rows.filter(row => {
const date = new Date(row[CONFIG.COL.DATE]);
return date.getFullYear() === year && date.getMonth() === month;
});
// 총 매출 (Price_Amount 합계)
const totalPrice = monthlyRows.reduce((sum, row) =>
sum + (Number(row[CONFIG.COL.PRICE_AMOUNT]) || 0), 0);
// 총 원가 (Cost_Amount 합계)
const totalCost = monthlyRows.reduce((sum, row) =>
sum + (Number(row[CONFIG.COL.COST_AMOUNT]) || 0), 0);
// 총 수량 (QTY 합계)
const totalQty = monthlyRows.reduce((sum, row) =>
sum + (Number(row[CONFIG.COL.QTY]) || 0), 0);
// Customer별 매출 합계 (내림차순)
const byCustomer = {};
monthlyRows.forEach(row => {
const customer = row[CONFIG.COL.CUSTOMER] || '미입력';
byCustomer[customer] = (byCustomer[customer] || 0) + (Number(row[CONFIG.COL.PRICE_AMOUNT]) || 0);
});
// Factory별 원가 합계 (내림차순)
const byFactory = {};
monthlyRows.forEach(row => {
const factory = row[CONFIG.COL.FACTORY] || '미입력';
byFactory[factory] = (byFactory[factory] || 0) + (Number(row[CONFIG.COL.COST_AMOUNT]) || 0);
});
return { totalPrice, totalCost, totalQty, byCustomer, byFactory };
}
// ============================================================
// 이메일 발송 함수
// ============================================================
function sendReportEmail(ss, summary, monthLabel) {
const sheetUrl = ss.getUrl();
const dashboardGid = ss.getSheetByName(CONFIG.DASHBOARD_SHEET)?.getSheetId();
const dashboardUrl = ${sheetUrl}#gid=${dashboardGid};
// Customer별 실적 텍스트
const customerLines = Object.entries(summary.byCustomer)
.sort((a, b) => b[1] - a[1])
.map(([customer, amount]) => · ${customer}: USD ${amount.toLocaleString()})
.join('\n');
// Factory별 원가 텍스트
const factoryLines = Object.entries(summary.byFactory)
.sort((a, b) => b[1] - a[1])
.map(([factory, cost]) => · ${factory}: USD ${cost.toLocaleString()})
.join('\n');
const subject = [월간 보고서] ${monthLabel} 거래 실적;
const body =
`안녕하세요,
${monthLabel} 거래 실적을 공유드립니다.
──────────────────────────
📊 ${monthLabel} 요약
──────────────────────────
▶ 총 매출 (Price_Amount) : USD ${summary.totalPrice.toLocaleString()}
▶ 총 원가 (Cost_Amount) : USD ${summary.totalCost.toLocaleString()}
▶ 총 수량 (QTY) : ${summary.totalQty.toLocaleString()} pcs
[Customer별 매출]
${customerLines || ' 데이터 없음'}
[Factory별 원가]
${factoryLines || ' 데이터 없음'}
──────────────────────────
📎 상세 대시보드 보기
${dashboardUrl}
※ 본 메일은 매월 1일 오전 9시 자동 발송됩니다.`;
GmailApp.sendEmail(CONFIG.RECIPIENTS, subject, body);
}
// ============================================================
// 트리거 설정 — 최초 1회만 실행하세요
// ============================================================
function createMonthlyTrigger() {
ScriptApp.getProjectTriggers().forEach(trigger => {
if (trigger.getHandlerFunction() === 'sendMonthlyReport') {
ScriptApp.deleteTrigger(trigger);
}
});
ScriptApp.newTrigger('sendMonthlyReport')
.timeBased()
.onMonthDay(1)
.atHour(9)
.create();
Logger.log('✅ 트리거 설정 완료: 매월 1일 오전 9시 자동 발송');
}
// ============================================================
// 테스트용 — 실제 발송 전에 이걸 먼저 실행해보세요
// ============================================================
function testSendReport() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const now = new Date();
const summary = getMonthlyData(ss, now.getFullYear(), now.getMonth());
sendReportEmail(ss, summary, ${now.getFullYear()}년 ${now.getMonth() + 1}월 (테스트));
Logger.log('✅ 테스트 발송 완료. 이메일을 확인하세요.');
}
코드는 4개의 함수로 구성된다.
- 각각의 역할:
| 함수명 | 역할 |
|---|---|
sendMonthlyReport() | 메인 함수. 트리거가 실행하는 진입점 |
getMonthlyData() | Order_list 시트에서 전월 데이터 집계 |
sendReportEmail() | 집계 결과를 이메일로 발송 |
createMonthlyTrigger() | 트리거 등록. 최초 1회만 실행 |
설치 방법
- 구글 시트 상단 메뉴 →
확장 프로그램→Apps Script클릭 - 기존 코드 전체 삭제 후 위 코드 붙여넣기
- 상단 CONFIG에서 수신자 이메일 수정
(manager@company.com → 실제 사용할 이메일로) testSendReport선택 → ▶ 실행 → 실행 완료 확인 → 이메일 확인- 정상 확인되면
createMonthlyTrigger실행 → 트리거 등록 완료
※처음 실행 시 권한 승인 팝업이 뜬다.
고급→프로젝트로 이동클릭하면 된다. 이후에는 묻지 않는다.
구글 대시보드에서 확장 프로그램 > 앱 스크립트 추가 > GAS 코드 추가를 통해 이메일 자동 발송하는 방법
Zapier 연동 : 슬랙 알림 자동화
GAS는 구글 환경 안에서 움직인다.
이메일은 보낼 수 있지만, 슬랙 같은 외부 도구나 앱과 연결하려면 Zapier와 같은 외부 자동화 도구가 필요하다.
GAS vs Zapier 차이점
| GAS | Zapier | |
|---|---|---|
| 연결 범위 | 구글 환경 안 | 구글 포함 8,000개 외부 앱 |
| 코드 필요 여부 | 필요 | 불필요 (드래그앤드롭) |
| 적합한 상황 | 구글 내부 자동화 | 외부 툴 연결 |
시나리오 — Order_List 새 행 추가 → 슬랙 알림
Order_List에 새 거래 데이터가 추가되는 순간, 슬랙 팀 채널에 자동으로 알림이 간다.

Step 1. Zapier 접속 → Create 클릭 → Zap 클릭
Step 2. Trigger 설정
- App:
Google Sheets - Event:
New Spreadsheet Row(새 행 추가될 때) - 연결할 스프레드시트:
FW26_order_list_test - 시트:
Order_List선택

Step 3. Action 설정
- App:
Slack - Event:
Send Channel Message - Channel: 알림 받을 채널 선택 (예: #오더-알림)

- Message: 아래처럼 설정
Season: {{A열 - Season}}
Customer: {{E열 - Customer}}
PO: {{C열 - PO}} / PI: {{D열 - PI}}
Style: {{F열 - Style}} | Item: {{H열 - Item}} | QTY: {{K열 - QTY}} | Price_Amount: USD {{M열 - Price_Amount}}
Factory: {{P열 - Factory}}
Step 4. Test → 슬랙 채널에서 알림 확인 → Publish

이게 전부다. 코드 없이 5분이면 이제 우리가 사용하는 협업 도구에서도 알림을 받을 수 있게 된다.
많은 자동화 툴이 있지만 Zapier는 트리거 → 액션 구조가 수직적이고 직관적이라고 느껴져서 자동화를 처음 접하는 사람도 따라하기 쉽다고 생각한다.
여기서 자동화의 감을 잡고 나면, 조건 분기나 노드 개념이 필요해지는 순간이 온다. 그때가 Make나 n8n으로 넘어갈 타이밍인 것 같다.
1~4편으로 완성된 업무 자동화 시스템
4편까지 오면서 만든 것을 정리해보자.

처음에 "자동화하고 싶다"고 했을 때 시도하기가 막막했던 이유는 자동화 도구나 함수나 개발 언어부터 배우려고 했기 때문이었던 것 같다.
하나씩 시도를 해보면서 느낀 것은 순서가 있다는 것이었다.
- 내 업무를 되짚어 보고
- 작게 작동하는 MVP 하나 만들어 보고
- 그것을 팀이 함께 쓰는 구조로 확장하고
- 데이터를 쌓아보고 그것을 의사결정에 연결해 보는 것.
이 순서대로 왔더니 자연스럽게 여기까지 오게 되었다.
오늘 바로 해볼 수 있는 제안하자면,
내 업무에서 "데이터는 있는데 내가 직접 취합하고 있는 것" 하나를 찾아보자.
그리고 이 글의 내용 중 하나 AI에 넣고 "내 업무에서 적용할 수 있을지" 물어보자.
거기서 나온 것 중 가장 작은 것 하나, 오늘 바로 만들어 보자.