본문으로 건너뛰기
취준생을 위한 n8n 실전 워크플로우 레시피 · 23/44시리즈 보기 →

받은 메일을 AI가 자동 분류해 DB에 쌓기 — n8n Gmail 자동 분류 워크플로우

· 약 8분
Datapopcorn CEO / AI automation educator
이 글과 연결된 글 보기
7편 · 20개 연결
100%
n8n Data Table 처음 써보기 — 트렌드 3개를 합쳐 매일 아침 분석 주제 뽑는 워크플로우받은 메일을 AI가 자동 분류해 DB에 쌓기 — n8n Gmail 자동 분류 워크플로우n8n으로 정적 웹서버 운영하기 — 수료증 발급부터 진위 확인까지, 멱등 처리로 안전하게벡터DB 없이 실시간으로 사이트를 읽는 챗봇 — 경복대학교 안내 챗봇 해부n8n으로 반복업무 자동화 포트폴리오 만들기구글시트에 쌓인 데이터를 인스타그램 카드뉴스로 — n8n 시트 인스타그램 자동발행서브워크플로우를 Tool로 감싸 AI Agent에게 서버 점검시키기 — 그리고 시스템 프롬프트만으로는 부족한 이유

드래그로 이동 · 휠/두 손가락으로 확대 · 방향키로 글 선택 · 0으로 맞춤. 실선: 본문 링크, 점선: 시리즈·주제의 대표 연결.

표시된 글 7편 목록
전체 글 연결 탐색

Gmail Trigger로 받은 메일을 그때그때 Gemini가 읽고 정산·협업·요청·문의·채용·기타 중 하나로 분류한 뒤 Supabase 테이블에 한 행씩 쌓는 n8n 워크플로우입니다. 메일함을 사람이 눈으로 훑는 대신, 나중에 "이번 달 정산 요청이 몇 건이었는지"를 SQL 한 줄로 물어볼 수 있는 데이터로 바꿔주는 구조입니다.

이 워크플로우 하나로 보여줄 수 있는 것​

지난 편이 슬랙에 올라온 사진 한 장을 AI가 평가해 답글을 다는 것이었다면, 이 편은 매일 쏟아지는 이메일을 사람이 감당 가능한 데이터로 바꾸는 문제입니다. 짧게 말하면 "Gmail에 도착하는 메일을 AI로 분류해서 DB에 쌓는 워크플로우를 만들었다"인데, 안에는 세 가지 역량이 들어 있습니다.

  • 트리거 노이즈 다루기 — 소스를 통제할 수 없을 때(메일함엔 뭐가 올지 모릅니다) 필요한 것만 걸러내는 판단
  • LLM 분류 스키마 설계 — 카테고리마다 다른 필드를 뽑아내는 조건부 JSON 스키마를 프롬프트로 짜는 경험
  • 시트가 아니라 DB에 적재 — 사람이 읽는 표 대신 나중에 쿼리할 수 있는 테이블에 결과를 쌓는 선택

세 번째가 특히 다른 편들과 다른 지점입니다. 시트는 열어서 눈으로 보기엔 좋지만, "지난주에 들어온 문의만" 같은 질문에는 매번 필터를 새로 걸어야 합니다. DB에 쌓아두면 그 질문에 SQL 한 줄로 답할 수 있고, 필요하면 다시 시트나 대시보드로 뽑아낼 수도 있습니다.

배경 — 회사 메일함엔 온갖 게 다 옵니다​

회사 대표 메일 계정에는 정산 요청, 협업 제안, 채용 지원, 단순 문의가 하루에도 여러 통씩 뒤섞여 들어옵니다. 사람이 매번 열어서 "이건 정산팀 이야기, 이건 채용 관련"이라고 나누고 넘겨야 하는데, 바쁠 땐 건너뛰고 나중에 찾으려면 메일함을 검색어로 다시 뒤져야 합니다. 이 워크플로우는 데이터팝콘 Gmail 계정에 실제로 연결해 만들어 둔 것으로, 메일이 도착하는 즉시 분류하고 저장까지 자동으로 끝내는 구조를 시험한 흔적입니다. 설치나 기본 노드 설명은 n8n으로 반복업무 자동화 포트폴리오 만들기에 정리돼 있으니, 이 글에서는 워크플로우 자체만 다룹니다.

구조 — Trigger / Task / Target​

  • Trigger — Gmail Trigger, 1분마다 새 메일 폴링
  • Task — 메일 필드 추출 → Gemini가 카테고리·우선순위·요약과 카테고리별 부가 필드를 JSON으로 분류 → 응답 문자열을 다시 객체로 파싱해 병합
  • Target — Supabase emails 테이블

여기에 매일 아침 9시에 도는 두 번째 흐름이 하나 더 붙어 있습니다. Supabase에 쌓인 데이터를 다시 꺼내 카테고리별로 구글시트에 나눠 담는 흐름인데, 왜 따로 두는지는 "만들기" 마지막 단계에서 설명합니다.

만들기​

1. Gmail Trigger — 1분마다 폴링​

Gmail Trigger 노드에서 Poll Times를 Every Minute으로 둡니다. 새 메일이 도착하면 최대 1분 안에 워크플로우가 시작됩니다. 원본에는 이 노드의 Filters 값이 비어 있는 상태로 남아 있는데, 왜 비어 있는지와 실제로 채우려면 어떻게 해야 하는지는 아래 "원본 캔버스에 남아 있는 흔적"에서 다룹니다.

2. Extract Email Data — 필요한 필드만 뽑기​

Set 노드(Extract Email Data)에서 뒤에 쓸 필드만 평평하게 펴 둡니다.

  • email_id: {{ $json.id }}
  • thread_id: {{ $json.threadId }}
  • received_at: {{ $json.date }}
  • sender: {{ $json.from.value[0].address }}
  • subject: {{ $json.subject }}
  • original_contents: {{ $json.text }}

Gmail Trigger가 돌려주는 원본 객체는 훨씬 크고 중첩돼 있어서, 이 단계를 거치지 않으면 다음 노드의 프롬프트에서 매번 긴 경로를 다시 써야 합니다.

3. Gemini AI Classification — 카테고리·우선순위·부가 필드​

Google Gemini 노드(모델: gemini-2.5-pro)를 쓰고, JSON 출력 옵션을 켰습니다. System Message는 짧습니다.

당신은 이메일을 분류하는 전문가입니다. 이메일의 내용을 분석하여 카테고리(정산/협업/요청/문의), 긴급도(긴급/보통/낮음) 반드시 JSON 형식으로만 응답하세요.

본문 프롬프트에는 발신자·제목·본문을 채워 넣고, 원하는 응답 스키마를 예시로 제시합니다. 발췌하면 이렇습니다.

{
"category": "정산" 또는 "협업" 또는 "요청" 또는 "문의" 또는 "채용" 또는 "기타",
"priority": "긴급" 또는 "보통" 또는 "낮음",
"summary": "이메일 한 줄 요약",

// category가 "정산"일 경우에만
"issuer": "발행 주체",
"amount": "금액 (숫자만)",
"due_date": "결제기한 (YYYY-MM-DD)",

// category가 "채용"일 경우에만
"applicant_name": "지원자 이름",
"applicant_email": "지원자 이메일",
"recruitment_stage": "채용 단계 (서류접수, 면접대기, 최종합격 등)"
}

분류 기준:
- 정산: 비용, 결제, 청구서, 세금계산서, 카드 결제 관련
- 채용: 입사지원, 이력서 접수, 면접 일정, 채용 공고 관련

우선순위 기준:
- 긴급: "긴급", "urgent", "ASAP" 키워드 포함, 당일/익일 마감

주의사항:
- 정보가 없으면 해당 필드는 null로 반환

카테고리마다 필요한 필드가 다르다는 걸 프롬프트 안에 주석처럼 적어 둔 게 특징입니다. 정산 메일에는 금액과 결제기한이, 채용 메일에는 지원자 정보가 필요하다는 걸 모델에게 조건부로 알려주는 방식입니다. "정보가 없으면 null로 반환"이라는 주의사항도 명확치 않은 필드를 모델이 지어내지 않게 막아줍니다.

4. Prepare Supabase Data — 문자열을 다시 객체로​

Gemini 노드의 응답은 텍스트 형태로 들어옵니다({{ $json.content.parts[0].text }}). 다음 Set 노드에서 contents_property 필드에 {{ $json.content.parts[0].text.parseJson() }}를 넣어 문자열을 다시 객체로 바꾸고, 2단계에서 뽑아둔 email_id·subject 같은 값도 {{ $('Extract Email Data').item.json.email_id }}처럼 노드 이름으로 다시 끌어와 함께 담습니다. parseJson()은 JSON 형식 문자열을 실제 객체로 바꿔주는 n8n 내장 메서드로, 이 변환을 거쳐야 다음 노드부터 contents_property.category처럼 점 표기로 필드에 바로 접근할 수 있습니다.

5. Supabase 저장​

Supabase 노드(Operation: Insert, Table: emails, Data to Send: Auto-Map Input Data)에 연결하면 지금까지 만든 필드가 컬럼명 그대로 테이블에 들어갑니다. 별도의 매핑 설정 없이 앞 노드가 만든 필드 이름을 그대로 컬럼으로 쓰는 방식이라, 필드를 추가하고 싶으면 Set 노드와 테이블 컬럼을 같이 늘리면 됩니다.

6. 쌓인 데이터를 다시 꺼내 쓰기​

같은 캔버스에 매일 아침 9시에 도는 두 번째 흐름이 붙어 있습니다. Schedule Trigger → Supabase(Get All, emails 테이블) → Switch 노드가 contents_property.category 값을 보고 정산·요청·채용 세 갈래로 나누고, 갈래마다 Set 노드로 필요한 필드만 추려 구글시트의 시트 세 개에 각각 추가합니다. 메일 한 통이 그냥 쌓이는 게 아니라 카테고리별 보고서 형태로 다시 나오는 지점이 여기입니다. 저장을 먼저 하고 시트는 나중에 뽑아내는 순서 덕분에, 시트 양식을 바꾸고 싶으면 Supabase 데이터는 그대로 두고 이 두 번째 흐름만 고치면 됩니다.

원본 캔버스에 남아 있는 흔적 — 시작하지 못한 필터링​

Gmail Trigger 옆 스티키 노트에는 이렇게 적혀 있습니다.

이메일 필터링
- 특정 수신자만 체크

불필요한 메일들 모두를 받을 것 같아 걱정.

프로모션 메일을 거르자.

그런데 정작 Gmail Trigger 노드의 Filters 값은 비어 있는 상태입니다. 만든 사람이 문제를 미리 적어두긴 했지만, 실제 설정까지는 이어지지 않은 것입니다. 이 상태로 Active를 켜면 광고 메일, 뉴스레터, 자동 알림 메일까지 전부 Gemini로 보내 분류를 시키게 되고, 필요 없는 분류 호출만 늘어납니다.

스티키 노트가 말한 "수신자"를 실제로 걸러내려면 주의할 점이 하나 있습니다. Gmail Trigger의 필터에는 Sender(발신자), Search(Gmail 검색 연산자), Read Status(읽음 상태), Label Names or IDs(라벨), Include Spam and Trash 다섯 항목이 있는데, 수신자를 직접 지정하는 필드는 없습니다. Sender는 보낸 사람 기준이라 "받는 사람"을 거르는 용도로는 안 맞습니다. Search 필드에 Gmail 검색 문법을 그대로 쓸 수 있어서, to: 연산자로 수신자를 지정하고 -category:promotions로 프로모션 메일을 빼는 방식이 이 스티키 노트가 원했던 걸 실제로 구현하는 길입니다.

같은 캔버스에는 다 채워지지 않은 흔적이 몇 개 더 있습니다. Supabase 저장 노드와 나란히 붙은 Slack 알림 노드는 캔버스에서 꺼진 상태(Disabled)이고, 옆의 Discord 노드는 연결된 자격 증명 이름 자체에 오류를 뜻하는 표시가 남아 있어 정상 작동하지 않았을 가능성이 있습니다. 또 하나 붙어 있는 Convert to File 노드는 결과를 어디로도 넘기지 않는 막다른 길입니다. 확실하게 살아 있는 경로는 Gemini 분류 → Supabase 저장입니다. 알림 노드 두 개는 Slack이 꺼져 있고 Discord는 자격 증명 상태가 불확실하며, Convert to File은 막다른 길로 남아 있습니다.

한계​

  • priority가 계산만 되고 쓰이지 않음 — Gemini가 매번 우선순위(긴급/보통/낮음)까지 뽑아내지만, 뒤 어떤 노드도 이 값을 참조하지 않습니다. 지금은 긴급 메일이라고 먼저 알려주는 로직이 없습니다.
  • 분류 결과가 컬럼 하나에 통째로 들어감 — contents_property는 Supabase에 하나의 필드로 저장되는데, 그 안에 category·amount·applicant_name 같은 값이 중첩된 객체로 들어 있습니다. "정산 메일만" 같은 검색을 하려면 일반 조건절이 아니라 JSON 경로를 다루는 문법이 필요합니다.
  • 재분류 경로가 없음 — Gemini가 메일 하나를 잘못 분류해도 고칠 방법이 워크플로우 안에 없습니다. 사람이 Supabase 테이블을 직접 열어 수정해야 합니다.
  • 시트로 나가는 카테고리가 절반뿐 — 매일 9시 흐름의 Switch 노드는 정산·요청·채용 세 갈래만 만들어 두었고, 협업·문의·기타로 분류된 메일은 Supabase에는 남지만 시트로는 넘어가지 않습니다.

포트폴리오·면접 포인트​

이 워크플로우는 빈 곳이 많이 보이는 만큼, 채워 넣을 지점도 뚜렷합니다. Gmail Trigger의 Search 필드로 실제 필터를 구현하거나, priority가 "긴급"일 때만 별도로 알림을 보내는 분기를 추가하거나, Switch 노드에 나머지 세 카테고리를 마저 연결하는 것 모두 그대로 포트폴리오 개선 사례가 됩니다. 원래 있던 워크플로우를 고쳐서 무엇이 왜 문제였는지 설명할 수 있으면, 처음부터 완성된 걸 그대로 따라 만든 사람보다 더 설득력 있는 이야기가 됩니다.

면접에서 "왜 시트가 아니라 DB에 저장했나요?"라는 질문이 오면 이렇게 답할 수 있습니다. 시트는 사람이 읽기엔 좋지만 카테고리·기간별로 몇 건이 왔는지 세려면 매번 필터를 새로 걸어야 합니다. DB에 넣어두면 SQL 한 줄로 그 답이 나오고, 필요할 때 시트나 대시보드로 다시 뽑아낼 수도 있습니다. 실제로 이 워크플로우도 저장이 끝난 뒤 매일 아침 카테고리별로 시트에 재배포하는 두 번째 흐름이 붙어 있다는 점까지 덧붙이면, 저장 형식을 왜 그렇게 골랐는지 생각해본 사람으로 보입니다.

다음 액션​

다음 편은 같은 Gmail 트리거 구조에 고객 문의 메일을 얹어, 분류에서 그치지 않고 AI가 답장 초안까지 준비하는 흐름으로 이어집니다. 분류만 하던 이 워크플로우에 응답이라는 한 단계가 더 붙는 셈인데, 뼈대는 이번 편과 크게 다르지 않습니다.