콘텐츠로 이동

메일 관리 시스템 — 받은편지함을 "실행 가능한 일"로 바꾸다

Gmail에서 메일 하나를 받았을 때, 우리 시스템은 그 메일을 읽지 않고 끝나는 정보가 아니라 일정·할일·프로젝트·메모·신규 거래처 같은 실제로 움직이는 업무로 바꿉니다. 이 글은 SL.AIMS 업무 포털의 메일 관리 시스템이 어떻게 동작하는지, 화면 한 장 한 장과 함께 풀어낸 기록입니다. (2026-06-19 라이브)

1. 들어가며 — 왜 또 메일 시스템인가

섹션 제목: “1. 들어가며 — 왜 또 메일 시스템인가”

배터리팩을 만드는 중소 제조사인 에스엘파워는 하루에 수십 통의 메일이 오갑니다. 거래처 견적 요청, 클레임, 납기 문의, 구매 발주, 정부 공문, 신규 리드, 뉴스레터, 그리고 적지 않은 스팸. Gmail은 훌륭한 메일함이지만 — 그 메일을 회사 시스템의 ‘할일’로 옮기는 일은 늘 사람의 손에 달려 있었습니다.

엑셀에 옮기고, ERP에 다시 입력하고, 누구에게 전달하고, 일정을 잡고, 견적서를 만들고… 같은 정보가 메일·메신저·엑셀·ERP에 흩어집니다. 누락이 발생합니다. 며칠 뒤 비슷한 메일이 또 오면 처음부터 다시 똑같은 결정을 합니다.

메일 관리 시스템은 그 비효율을 끊는 도구입니다. 메일 한 통 → 한 클릭 → ERP 데이터의 변환 비용을 0에 가깝게 만들고, AI가 분석한 결과를 사람이 반드시 승인하게 한 뒤, 그 결정을 기억해서 다음번에 비슷한 메일이 오면 먼저 제안합니다.

2. 시스템 개요 — Gmail 위에 얇게 얹은 ‘업무 변환기’

섹션 제목: “2. 시스템 개요 — Gmail 위에 얇게 얹은 ‘업무 변환기’”

SL.AIMS는 단일 백엔드(aims-api, NestJS)와 단일 프론트(portal, Next.js 14) 구조입니다. 메일 기능은 그 안에 있는 업무 포털(Workspace) 모듈의 한 화면입니다. Gmail API와 직접 통신하고, 결정 로그·행동 기억은 우리 자체 DB(PostgreSQL)에 저장합니다.

Gmail API OAuth2 / IMAP readonly · modify · send aims-api · NestJS WorkspaceGmailService WorkspaceMailAiService WorkspaceGoogleController PII 마스킹 · 결정 로그 행동 기록 · HITL 권한 가드 portal · Next.js MailScreen / MailDetail MailQuickCreate / ComposeModal PostgreSQL 16 AgtDecisionLog · workspace_actions tasks · projects · schedules · notes
그림 1 — Gmail · aims-api · portal · PostgreSQL의 연결 구조.
구성요소역할
WorkspaceGmailServiceGmail API 호출·페이징·첨부 다운로드·읽음·스팸/휴지통 처리
WorkspaceMailAiService회신 초안 생성(LLM 게이트웨이), 담당자 추천(L1 규칙), PII 마스킹
AgtDecisionLog모든 AI 결정 기록 — 입력 요약·결정 결과·추론 요약·사람 승인 여부
workspace_actions사용자 행동 기억 — 메일에서 무엇을 만들었는지·누구에게 배정했는지

3. 메일 받기 — 2일치 라이브 페이징과 검색

섹션 제목: “3. 메일 받기 — 2일치 라이브 페이징과 검색”

화면을 열면 받은편지함이 2일치 단위로 표시됩니다. Gmail API의 after:/before: 쿼리를 우리 시스템이 직접 만들어 “오늘과 어제”의 메일만 가져옵니다. 다음 페이지를 누르면 그 이전 2일로 점프합니다. 상단에는 전체 · 읽지 않음 · 중요 · 고객사 · 스팸 칩 필터가 놓이고, 각 메일에는 자동 라벨(고객사/중요/스팸)이 붙습니다.

발신제목라벨시각
김민수 · 한빛테크[견적요청] BMS-2412 모델 200대 — 납기 7월 말 가능 여부고객사 · 읽지 않음11:32
박지영 · 대한셀[클레임] 4월 출하분 BSM-08 — 셀 밸런싱 이슈 발생중요 · 읽지 않음10:14
이재훈 · 부품넷[발주확인] PO-2412-039 — 자재 입고 완료, 인보이스 첨부09:48
배터리뉴스[뉴스레터] 6월 셋째 주 — 글로벌 셀 가격 동향과 정책 이슈뉴스레터어제
unknown@promo.example.com★축하합니다★ 당첨되셨습니다! 지금 확인하기자동 스팸어제

그림 2 — 받은편지함. 2일치 페이징, 칩 필터, 자동 라벨링(고객사/중요/스팸)이 한 줄에 보입니다.

왜 2일치인가? 1일치만 보면 어제와 오늘 사이 흐름이 끊깁니다. 7일치를 한 번에 가져오면 페이지가 무거워지고 Gmail API의 list 응답이 부풀어 첫 화면이 느려집니다. 2일은 그 사이 균형점입니다. 그리고 다음 페이지에서 2일씩 점프하니까 일주일 메일을 보는 데 3~4번 클릭이면 충분합니다.

실시간이라는 점도 중요합니다. 우리 DB에 미러링하지 않고 매번 Gmail에 직접 묻습니다. 사용자가 Gmail 웹에서 메일을 읽거나 라벨을 바꾸면 우리 포털에도 즉시 반영됩니다. 동기화 지연이 없습니다.

4. 메일 분석 — PII 마스킹과 키워드 추출

섹션 제목: “4. 메일 분석 — PII 마스킹과 키워드 추출”

메일을 클릭하면 본문이 펼쳐집니다. 이 시점에 시스템이 조용히 두 가지를 합니다 — (1) PII 마스킹, (2) 도메인 키워드 추출.

PII 마스킹 — 외부 AI에 보내기 전 6종 자동 정규식

섹션 제목: “PII 마스킹 — 외부 AI에 보내기 전 6종 자동 정규식”

AI에 메일 본문을 보낼 때는 주민등록번호·전화번호·이메일·계좌번호·JWT·API 키 6종을 자동으로 [REDACTED]로 가립니다. AI가 본 적 없어야 할 정보를 입력 단계에서 제거합니다.

// PiiSanitizerService — 외부 LLM 호출 전 모든 텍스트가 통과
const maskedSubject = pii.sanitize(input.subject);
const maskedBody = pii.sanitize(input.body);
// 예: "010-1234-5678 김민수 부장님께" → "[PHONE] 김민수 부장님께"

키워드 추출 — 제목·발신자·본문에서 업무 단서 뽑기

섹션 제목: “키워드 추출 — 제목·발신자·본문에서 업무 단서 뽑기”

AI 모델을 부르기 전에 먼저 L1 규칙(키워드 매칭)으로 가벼운 분류를 합니다. 비용도 들지 않고 결정도 안정적이기 때문입니다.

제목에 들어있는 단어분류·라우팅
클레임, 불량, 품질품질팀 자동 추천
구매, 발주, 견적구매팀 자동 추천
영업, 납기, 납품영업팀 자동 추천
★축하, 당첨, 무료스팸 후보 라벨

5. AI 회신 초안 — 게이트웨이·승인 전 발송 금지

섹션 제목: “5. AI 회신 초안 — 게이트웨이·승인 전 발송 금지”

메일 상세 화면의 “AI 초안” 버튼을 누르면 시스템이 회신 초안을 작성합니다. 단, 이 초안은 절대 자동 발송되지 않습니다 — 발송 전에 사람이 반드시 검토하고 확정 버튼을 누른 메일만 Gmail로 나갑니다.

상세 화면 상단에는 회신·전체회신·전달·AI 초안·일정 추가·리드 등록·삭제 버튼이 놓입니다. 예를 들어 한빛테크 김민수 부장의 견적 요청 메일에서 “AI 초안”을 누르면, 아래와 같은 초안이 검토 대기 상태로만 표시됩니다.

수신 메일 · 김민수 〈buyer@example.com〉 · 한빛테크 · 11:32 [견적요청] BMS-2412 모델 200대 — 납기 7월 말 가능 여부 문의

안녕하세요, 에스엘파워 BMS-2412 모델 200대 견적을 요청드립니다. 납기는 7월 말까지 가능한지 확인 부탁드립니다. 회신 부탁드립니다. 감사합니다.

그림 3 — AI 회신 초안. PII 마스킹 후 LLM 호출, 결과는 승인 대기 상태로만 표시됩니다(✓ 보내기 / ✏ 수정 / ↻ 다시 생성 / ✕ 폐기).

LLM 게이트웨이 — 모델을 화면에서 바꾼다

섹션 제목: “LLM 게이트웨이 — 모델을 화면에서 바꾼다”

회신 초안의 LLM 호출은 LlmService 게이트웨이를 통해 이뤄집니다. 관리자 화면(/admin/ai-models)에서 MAIL_REPLY_DRAFT 라우트의 모델을 OpenAI gpt-4o-mini로, 내부 테스트에서는 더 저렴한 모델로 — 코드 한 줄 안 고치고 라우팅을 바꿀 수 있습니다. 어떤 LLM을 어떻게 도입할지 정한 기준은 〈어떤 LLM을 도입할 것인가〉에 따로 적었습니다.

실패도 결정 로그 — 무엇이 실패했는지 추적

섹션 제목: “실패도 결정 로그 — 무엇이 실패했는지 추적”

AI가 성공하든 실패하든 AgtDecisionLog에 기록이 남습니다. 실패한 시도조차도 — 왜 실패했고 무엇을 시도했는지 사라지지 않습니다.

agentName: workspace-mail-ai
decisionType: MAIL_DRAFT_REPLY
modelVersion: gpt-4o-mini
inputSummary: "[견적요청] BMS-2412 모델 200대 …"
decisionResult: GENERATED
reasoningSummary: AI 회신 초안 생성(발송 전 사람 승인 필요)
humanReviewRequired: true

6. 메일에서 일·계획 만들기 — 할일·프로젝트·일정·메모·리드

섹션 제목: “6. 메일에서 일·계획 만들기 — 할일·프로젝트·일정·메모·리드”

메일을 회신만 하는 시스템은 결국 메일함입니다. 진짜 가치는 메일 한 통에서 회사의 다음 행동을 만들어내는 것에 있습니다. 상세 화면의 “더보기” 버튼을 누르면 작은 패널이 펼쳐지고, 할일 · 프로젝트 · 일정 · 메모 · 신규 리드 중 하나를 한 번에 만들 수 있습니다.

예를 들어 대한셀 박지영 부장의 [클레임] 메일에서 “할일”을 고르면, 다음처럼 칸이 자동으로 채워집니다.

항목자동 채움 값
제목[클레임 대응] 대한셀 BSM-08 셀 밸런싱 이슈 원인 분석
담당자정한수 품질팀장 · AI 추천
마감일2026-06-22 (3일 후) · 클레임 응대 SLA 적용
근거📧 원본 메일(gmail:18f9a2c1) — 본문·발신자·첨부 자동 첨부

그림 4 — 빠른 생성 패널. 메일의 제목·발신자·본문·첨부가 자동으로 새 할일/프로젝트/일정/메모에 들어갑니다.

이 패널의 핵심은 입력 자동화입니다.

  • 제목은 메일 제목 앞에 동작 동사([클레임 대응])를 붙여 제안합니다.
  • 담당자는 키워드 매칭으로 자동 추천 — 다음 섹션에서 자세히 설명합니다.
  • 마감일은 유형별 사내 SLA(예: 클레임 3일, 견적 1일, 일반 5일)에 따라 계산됩니다. SLA 기준은 〈SLA 정책과 그 판단 기준〉에 정리했습니다.
  • **근거(Evidence)**는 항상 원본 메일에 대한 링크가 자동 첨부됩니다. 누구든 만들어진 할일에서 한 클릭으로 원본 메일로 갈 수 있습니다.

각 액션이 어떤 테이블로 가는지

섹션 제목: “각 액션이 어떤 테이블로 가는지”
버튼저장 위치핵심 필드
📋 할일workspace_taskstitle, ownerId, dueDate, evidence(json: mailId)
📦 프로젝트workspace_projectsname, customerId, status, externalRefs(json: gmail thread)
📅 일정workspace_schedules + Google Calendarstart, end, attendees, source=‘mail’
📝 메모workspace_notestitle, body, rawPayload(원본 인용)
🌟 리드workspace_leadscompany, contact, status=‘신규’, source=‘mail’

일정 추가는 특별합니다. 우리 DB에만 쓰는 게 아니라 Google Calendar(primary)에도 양방향 동기화됩니다. 메일에서 일정을 추가하면 그 사용자의 Google 캘린더에도 동시에 등록되어, 모바일·외부 캘린더 앱에서 똑같이 보입니다.

7. 담당자 자동 추천 — L1 규칙 기반

섹션 제목: “7. 담당자 자동 추천 — L1 규칙 기반”

메일의 가장 큰 마찰은 “누구에게 보낼까”입니다. 우리 시스템은 메일 제목·발신자·본문에서 키워드를 뽑아 부서를 추론하고, 그 부서의 활성 직원 중 가장 오래 근속한 1명을 자동 추천합니다.

메일 도착 [클레임] BSM-08 키워드 매칭 "클레임" → 품질팀 HR 조회 department ~ "품질" 담당자 제안 정한수 품질팀장 사용자 확정 맞다/다른 사람
그림 5 — 담당자 추천 흐름. 키워드로 부서를 정하고, HR에서 그 부서의 사람을 찾아 제안하면, 마지막 확정은 사람이 합니다.
// WorkspaceMailAiService.suggestAssignee — L1 규칙
if (subj.includes('클레임') || subj.includes('불량')) deptKeyword = '품질';
else if (subj.includes('발주') || subj.includes('견적')) deptKeyword = '구매';
else if (subj.includes('납기') || subj.includes('납품')) deptKeyword = '영업';
const user = await prisma.user.findFirst({
where: { isActive: true,
employee: { department: { name: { contains: deptKeyword } } } },
orderBy: { createdAt: 'asc' }
});

왜 LLM이 아니라 규칙인가? — 비용·신뢰성·설명 가능성 때문입니다. 부서가 늘어나거나 분기가 복잡해지면 추후 L2(통계)·L3(ML)·L4(LLM)로 끌어올릴 수 있지만, 현재 우리 회사 규모에선 키워드 매칭이 거의 모든 경우에 맞습니다. 틀려도 사용자가 즉시 다른 사람으로 바꿉니다. 그리고 그 ‘바꿈’ 자체가 다음 학습의 데이터가 됩니다 — 행동 기억입니다. (계층 설계는 〈에이전트 자가학습〉 참고)

8. 행동 기억 — 그 결정을 어떻게 저장하는가

섹션 제목: “8. 행동 기억 — 그 결정을 어떻게 저장하는가”

사용자가 ”✓ 할일 만들기” 버튼을 누르면 시스템은 두 가지를 동시에 합니다.

  1. 할일을 생성합니다. (workspace_tasks INSERT)
  2. 그 행동을 기억합니다. (workspace_actions INSERT — recordAction)
action: "mail_to_task"
mailId: "gmail:18f9a2c1"
mailFrom: "buyer2@example.com"
mailSubjectPattern: "[클레임] *"
createdEntity: "task" · "task_id_3829"
ownerId: "user_quality_lead" # AI 추천 수락
aiSuggestion: { ownerId: "user_quality_lead", reason: "키워드 클레임 → 품질팀" }
accepted: true
createdAt: 2026-06-19T10:18:42Z

중요한 건 AI 추천을 수락했는지 여부(accepted)와 발신자·제목 패턴입니다. 같은 발신자(buyer2@example.com)에게서 같은 패턴의 메일([클레임] *)이 또 오면, 시스템이 이 기록을 찾아냅니다.

왜 별도 테이블인가 — AgtDecisionLog와의 차이

섹션 제목: “왜 별도 테이블인가 — AgtDecisionLog와의 차이”

AgtDecisionLogAI의 결정 자체를 기록합니다(회신 초안 생성, 담당자 추천 등). workspace_actions사용자가 실제로 무엇을 했는지를 기록합니다. 둘은 서로 다른 시점·다른 목적입니다. 결정 로그는 감사용·재현용이고, 행동 기록은 학습용·제안용입니다.

9. 다음 제안 — 비슷한 메일이 또 오면

섹션 제목: “9. 다음 제안 — 비슷한 메일이 또 오면”

며칠 뒤, 대한셀의 박지영 부장에게서 또 클레임 메일이 옵니다. 사용자가 이 메일을 클릭하는 순간, 시스템이 조용히 workspace_actions를 조회합니다. 같은 발신자·같은 제목 패턴의 과거 행동이 있는지.

”✓ 이번에도 같은 방식으로 만들기”를 누르면 한 클릭으로 할일이 생성됩니다. 담당자·마감일·근거가 자동으로 채워집니다. 그리고 이 클릭조차도 workspace_actions에 또 기록됩니다(accepted=true, source=suggestion). 같은 패턴이 반복될수록 시스템은 그 패턴을 더 강하게 학습합니다.

  • HITL (Human-in-the-loop) — 제안은 어디까지나 제안입니다. 자동으로 만들지 않습니다.
  • 제안 끄기 — 사용자가 원하지 않으면 발신자별로 제안을 끌 수 있습니다.
  • 패턴 신뢰도 — 같은 결정이 1회뿐이면 제안하지 않습니다. 3회 이상 같은 결정이 반복된 경우에만 노출합니다.
  • 최근성 가중치 — 1년 이상 전 행동은 신뢰도가 낮아집니다.

10. 스팸 관리 — 분류·해지·휴지통

섹션 제목: “10. 스팸 관리 — 분류·해지·휴지통”

실제 업무 메일에는 항상 일정 비율의 스팸이 섞입니다. 우리 시스템은 스팸을 세 가지 방식으로 다룹니다.

1) 자동 스팸 분류 — 도착 시점 라벨링

섹션 제목: “1) 자동 스팸 분류 — 도착 시점 라벨링”

메일이 도착하면 Gmail이 이미 스팸 라벨(SPAM)을 붙인 메일은 그대로 가져오고, 추가로 우리 시스템은 제목/발신자 도메인에 대한 가벼운 규칙(예: ★축하, 당첨, 무료, 처음 보는 도메인 + 마케팅 키워드)으로 “의심 스팸” 라벨을 따로 붙입니다. 즉, Gmail의 SPAM과 우리 시스템의 ‘의심 스팸’은 별도로 표시되어, 잘못 분류된 메일을 사람이 가려낼 수 있습니다.

2) 한 클릭 스팸 처리 — 또는 해지

섹션 제목: “2) 한 클릭 스팸 처리 — 또는 해지”

메일 상세 화면의 ⋯ 더보기 → 스팸 처리를 누르면 Gmail에 POST /api/v1/workspace/google/gmail/messages/:id/spam이 호출되어 그 메일이 Gmail의 SPAM 폴더로 이동합니다. 반대로 ‘스팸 아님(Not Spam)‘으로 되돌리는 버튼도 있습니다 — Gmail이 잘못 분류한 경우.

뉴스레터처럼 합법적이지만 안 받고 싶은 메일에는 “구독 해지” 버튼이 표시됩니다. 본문에 List-Unsubscribe 헤더가 있으면 그 URL로 자동 요청하고, 없으면 메일 본문에서 “unsubscribe” 링크를 찾아 안내합니다.

사용자가 휴지통으로 보낸 메일은 Gmail의 정책에 따라 30일 후 자동 삭제되고, 우리 시스템이 발견한 ‘의심 스팸’ 중 30일 이상 방치된 메일은 정기 작업이 자동으로 휴지통으로 옮깁니다. 결정 로그가 남으므로 추후 추적·복구가 가능합니다.

11. 거버넌스 — HITL과 결정 로그

섹션 제목: “11. 거버넌스 — HITL과 결정 로그”

이 시스템의 한 가지 단단한 원칙이 있습니다.

세 가지가 모두 강제됩니다.

  1. HITL (Human-in-the-Loop) — AI가 생성한 회신 초안은 자동 발송되지 않습니다. 담당자 추천도 자동 배정되지 않습니다. 스팸 처리도 자동 처리되지 않습니다. 오직 사용자가 버튼을 눌러야 진행됩니다.
  2. PII 마스킹 — 외부 LLM에 본문을 보내기 전 6종 PII는 반드시 정규식으로 제거됩니다. OpenAI 콘솔이나 로그 시스템에 절대로 원본 PII가 흘러가지 않습니다.
  3. 결정 로그 — AI 결정 하나하나가 AgtDecisionLog에 기록됩니다. 무엇을 입력으로 받았는지(inputSummary), 무엇을 결정했는지(decisionResult), 어떤 모델로 만들었는지(modelVersion), 그리고 사람의 승인을 필요로 했는지(humanReviewRequired)까지. 감사·재현·롤백·CEO 검토 어디든 동일한 기록입니다.

이 HITL·결정 로그의 골격은 〈전자결재 시스템〉에서 다진 “사건 → 판단 → 사람의 승인” 구조와 같고, AI가 할 수 있는 일과 없는 일의 경계는 〈에이전트가 할 수 있는 일과 없는 일〉에 따로 적었습니다.

L1~L5 단계 — AI 계층의 자기 위치 인식

섹션 제목: “L1~L5 단계 — AI 계층의 자기 위치 인식”
계층현재 메일 시스템에서의 사용
L1 규칙키워드 → 부서 매핑 / 스팸 의심 라벨
L2 통계같은 발신자·같은 제목 패턴 빈도 (다음 로드맵)
L3 ML스팸 자동 분류기 (현재 Gmail에 위임)
L4 LLM회신 초안 생성 (OpenAI gpt-4o-mini)
L5 Agent받은 메일 자율 분류·후속 행동 자동 제안 (HITL 필수)

지금 라이브된 메일 시스템은 6월 19일 기준 1차 완성판입니다. 다음 단계들이 줄을 서고 있습니다.

  • L2 통계 기반 제안 강화 — 발신자별·제목패턴별 결정 빈도 집계해서 신뢰도 지표 노출
  • 첨부 자동 인식 — PDF 견적서·xlsx 발주서를 OCR/파싱해서 자재·금액 자동 추출 후 ERP에 미리 채움
  • 메일에서 견적서 만들기 — 견적 요청 메일 → 견적서 양식 자동 채움 → 결재 라인 자동 지정
  • 회의록 → 메일 회신 — 회의 직후 회의록 일부를 메일 인용으로 자동 회신
  • 모바일 메일 알림 — SL-On 모바일 앱에서 중요 메일만 푸시 알림 (모든 메일이 아니라 키워드/발신자 기반)
  • 발신자별 자동 라우팅 — 학습된 패턴이 3회 이상 일치하면 자동으로 담당자에게 메일 사본 전달
  • 스팸 자체 분류기 — Gmail의 SPAM 라벨 외에 우리 도메인 특화 스팸(가짜 견적·피싱·BEC) 학습
  • 다중 메일함 — 영업·구매·CS 부서별 공용 메일함 도입
  • 크로스 모듈 컨텍스트 — 메일이 거래처 마스터·과거 견적·과거 클레임과 연결되어 자동으로 컨텍스트 표시
  • 완전 자율 응대 (반복 문의 한정) — FAQ 수준의 반복 문의에 한해 AI가 자동 응대, 단 매번 결정 로그 + CEO 검토 보고

마치며 — 메일은 결국 사람의 일

섹션 제목: “마치며 — 메일은 결국 사람의 일”

이 시스템은 메일을 사람 대신 처리해주는 도구가 아닙니다. 사람이 결정해야 할 부분에만 사람이 집중하도록 만드는 도구입니다. 누구에게 보낼지, 언제까지 해야 할지, 이번에는 어떤 답을 줄지 — 그 핵심 결정은 여전히 사람이 합니다. 시스템은 그 결정을 더 빨리, 더 일관되게, 더 안전하게 하도록 옆에서 보조합니다.


이 글은 SL.AIMS를 만들며 구축한 업무 포털 모듈 중 하나입니다. 전체 그림은 〈사례연구: SL.AIMS〉에 있습니다.