왜 커뮤니케이션 도구까지 자동화해야 할까
파일 정리, 데이터베이스 저장, API 호출만으로는 자동화가 실제 업무에 완전히 들어오지 못한다. 팀은 매일 이메일을 확인하고, 문서를 만들고, Slack이나 Teams에서 알림을 받고, 캘린더로 일정을 공유한다. 자동화 결과가 이 도구들로 전달되지 않으면 담당자는 다시 파일을 열고, 내용을 복사하고, 메시지를 작성해야 한다.
이 장의 목표는 자동화가 사람의 업무 흐름 안으로 자연스럽게 들어오게 만드는 것이다. 예를 들어 거래처가 보낸 발주서 첨부파일을 자동으로 저장하고, 조건에 따라 담당자에게 알림을 보내고, 검토가 필요한 건은 승인 요청으로 넘기는 식이다. 이때 중요한 원칙은 "자동 발송"보다 "안전한 전달"이다. 잘못된 메일 한 통, 잘못된 계약서 한 부, 잘못된 승인 알림 하나가 실제 업무 사고로 이어질 수 있기 때문이다.
따라서 이메일, 문서, 협업 도구 자동화는 다음 순서로 설계하는 것이 좋다.
입력을 가져온다. 이메일, 첨부파일, 양식 데이터, 데이터베이스 조회 결과가 입력이 된다.
규칙에 따라 분류하고 검증한다.
문서나 메시지 초안을 만든다.
테스트 모드에서 결과를 확인한다.
필요하면 사람이 승인한 뒤 발송한다.
발송 결과와 오류를 로그로 남긴다.
AI 생성 이미지
이메일 수신함에서 첨부파일 추출하기
이메일 자동화에서 가장 흔한 업무는 수신함의 첨부파일을 내려받아 분류하는 일이다. 예를 들어 매일 거래처에서 들어오는 발주서, 세금계산서, 정산 파일을 사람이 일일이 열어 저장하지 않고 자동으로 처리할 수 있다.
기본 흐름은 단순하다.
메일 서버에 로그인한다.
특정 조건의 메일을 검색한다.
첨부파일이 있는지 확인한다.
파일명을 정리해 저장한다.
보낸 사람, 제목, 수신일, 저장 경로를 로그로 남긴다.
정상 처리한 메일은 다시 처리하지 않도록 표시한다.
파이썬에서는 표준 라이브러리인 imaplib, email을 사용할 수 있다. 다만 Gmail, Microsoft 365 같은 서비스는 보안 정책상 단순 비밀번호 로그인을 제한하는 경우가 많다. 실무에서는 앱 비밀번호, OAuth, 또는 각 서비스의 API를 사용하는 방식이 더 안전하다.
아래 예제는 읽지 않은 메일을 가져온 뒤, 파이썬 코드 안에서 제목에 발주서가 들어 있는지 확인한다. IMAP 서버 검색 조건에 한글 제목을 직접 넣으면 서버나 계정 설정에 따라 인코딩 문제가 생길 수 있으므로, 처음에는 이렇게 단순하고 안전한 방식으로 시작하는 편이 좋다.
import imaplib
import email
from email.header import decode_header
from pathlib import Path
from datetime import datetime
MAIL_HOST = "imap.example.com"
MAIL_USER = "[email protected]"
MAIL_PASSWORD = "환경변수에서_읽기"
SAVE_DIR = Path("data/inbox_attachments")
SAVE_DIR.mkdir(parents=True, exist_ok=True)
ALLOWED_EXTENSIONS = {".xlsx", ".xls", ".csv", ".pdf"}
def decode_text(value):
if not value:
return ""
parts = decode_header(value)
result = ""
for text, encoding in parts:
if isinstance(text, bytes):
result += text.decode(encoding or "utf-8", errors="ignore")
else:
result += text
return result
def safe_filename(name):
cleaned = "".join(c for c in name if c.isalnum() or c in "._- ").strip()
return cleaned or "attachment"
def unique_path(path):
if not path.exists():
return path
stem = path.stem
suffix = path.suffix
parent = path.parent
count = 1
while True:
candidate = parent / f"{stem}_{count}{suffix}"
if not candidate.exists():
return candidate
count += 1
def download_attachments():
mail = imaplib.IMAP4_SSL(MAIL_HOST)
mail.login(MAIL_USER, MAIL_PASSWORD)
mail.select("INBOX")
# 읽지 않은 메일을 가져온 뒤, 제목 필터는 코드에서 적용한다.
status, data = mail.search(None, "UNSEEN")
if status != "OK":
raise RuntimeError("메일 검색 실패")
for mail_id in data[0].split():
status, msg_data = mail.fetch(mail_id, "(RFC822)")
if status != "OK":
continue
msg = email.message_from_bytes(msg_data[0][1])
subject = decode_text(msg.get("Subject"))
sender = decode_text(msg.get("From"))
if "발주서" not in subject:
continue
received_date = datetime.now().strftime("%Y%m%d")
saved_files = []
for part in msg.walk():
if part.get_content_disposition() != "attachment":
continue
filename = decode_text(part.get_filename())
if not filename:
continue
filename = safe_filename(filename)
suffix = Path(filename).suffix.lower()
if suffix not in ALLOWED_EXTENSIONS:
print(f"허용하지 않는 첨부파일 형식 건너뜀: {filename}")
continue
save_path = unique_path(SAVE_DIR / f"{received_date}_{filename}")
with open(save_path, "wb") as f:
f.write(part.get_payload(decode=True))
saved_files.append(save_path)
print(f"저장 완료: {sender} / {subject} / {save_path}")
# 실제 운영에서는 처리 이력 테이블 기록까지 성공한 뒤 표시하는 것이 안전하다.
if saved_files:
mail.store(mail_id, "+FLAGS", "\Seen")
mail.logout()
if __name__ == "__main__":
download_attachments()이 코드는 완성된 운영 시스템이라기보다 기본 골격이다. 실제 운영에서는 몇 가지를 반드시 보강해야 한다.
첫째, 첨부파일 확장자를 제한해야 한다. 엑셀, PDF, CSV처럼 업무상 필요한 형식만 저장하고 실행 파일은 차단한다. 둘째, 같은 메일을 반복 처리하지 않도록 메일 ID, 제목, 보낸 사람, 첨부파일명을 처리 이력 테이블에 남긴다. 읽음 표시만으로 중복 처리를 막으면 사람이 메일을 먼저 열어본 경우나 자동화가 중간에 실패한 경우를 놓칠 수 있다. 셋째, 첨부파일을 바로 업무 시스템에 반영하지 말고 검증 폴더나 스테이징 테이블을 거치게 한다.
분류도 처음부터 AI를 쓸 필요는 없다. 보낸 사람, 제목 키워드, 첨부파일명, 수신 계정만으로 분류할 수 있다면 규칙 기반이 더 안정적이다.
RULES = [
{"keyword": "발주서", "folder": "orders"},
{"keyword": "세금계산서", "folder": "tax"},
{"keyword": "정산", "folder": "settlement"},
]
def classify_email(subject, filename):
text = f"{subject} {filename}"
for rule in RULES:
if rule["keyword"] in text:
return rule["folder"]
return "needs_review"needs_review는 매우 중요한 분류다. 자동화가 판단하지 못한 항목을 억지로 처리하지 않고 사람이 확인하도록 분리하는 장치이기 때문이다.
문서를 템플릿으로 자동 생성하기
팀 업무에는 반복해서 작성하는 문서가 많다. 견적서, 계약서, 회의록, 주간 보고서, 검수 확인서가 대표적이다. 이 문서들은 매번 내용이 완전히 새롭지 않다. 정해진 양식 안에서 고객명, 금액, 기간, 담당자, 회의 안건 같은 값만 바뀌는 경우가 많다.
이런 문서는 템플릿 기반 자동화가 적합하다. 핵심은 문서 양식을 코드에 직접 만들지 않는 것이다. 사람이 관리하기 쉬운 템플릿 파일을 두고, 파이썬은 그 안의 자리표시자만 실제 값으로 바꾼다.
예를 들어 워드 템플릿에 다음과 같은 문구를 넣어둔다.
{{customer_name}} 귀하
아래와 같이 견적을 제출합니다.
- 견적 금액: {{amount}}원
- 계약 기간: {{start_date}} ~ {{end_date}}
- 담당자: {{owner}}파이썬에서는 python-docx 같은 라이브러리로 워드 문서를 다룰 수 있다.
from docx import Document
from pathlib import Path
TEMPLATE_PATH = Path("templates/quotation_template.docx")
OUTPUT_DIR = Path("output/documents")
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
def replace_text(paragraph, mapping):
for key, value in mapping.items():
placeholder = "{{" + key + "}}"
if placeholder in paragraph.text:
paragraph.text = paragraph.text.replace(placeholder, str(value))
def create_quotation(data):
doc = Document(TEMPLATE_PATH)
for paragraph in doc.paragraphs:
replace_text(paragraph, data)
output_path = OUTPUT_DIR / f"견적서_{data['customer_name']}.docx"
doc.save(output_path)
return output_path
sample = {
"customer_name": "한빛상사",
"amount": "3,500,000",
"start_date": "2026-06-01",
"end_date": "2026-06-30",
"owner": "김민수",
}
print(create_quotation(sample))이 예제도 출발점으로 보면 된다. python-docx에서 paragraph.text를 통째로 바꾸면 문단 안의 일부 서식이 사라질 수 있고, 표나 머리글, 바닥글 안의 자리표시자는 별도로 처리해야 한다. 실무 템플릿은 대부분 표를 많이 쓰기 때문에, 먼저 단순한 견적서 템플릿으로 검증한 뒤 필요한 영역을 하나씩 넓혀 가는 것이 좋다.
계약서처럼 법적 위험이 큰 문서는 자동으로 최종본을 발송하면 안 된다. 자동화는 초안을 만들고, 담당자나 법무 검토자가 확인한 뒤 발송하는 구조가 안전하다. 문서 파일명에도 상태를 명확히 표시한다.
계약서_초안_고객사명_20260601.docx계약서_검토완료_고객사명_20260602.pdf계약서_발송완료_고객사명_20260603.pdf
회의록과 주간 보고서도 같은 방식으로 만들 수 있다. 입력 데이터가 회의 안건, 참석자, 결정 사항, 할 일 목록이라면 템플릿에 맞춰 문서를 생성한다. 여기서 AI는 녹취나 긴 메모를 요약하는 보조 역할에 적합하다. 반면 참석자, 날짜, 프로젝트명, 승인자처럼 정확해야 하는 값은 데이터베이스나 양식 입력값을 기준으로 채우는 것이 좋다.
Slack, Teams, Workspace, Microsoft 365로 알림 보내기
협업 도구 자동화의 목적은 사람에게 "지금 무엇을 해야 하는지" 알려주는 것이다. 단순히 메시지를 많이 보내는 것이 목표가 아니다. 알림이 많아질수록 사람은 무시하게 된다. 좋은 알림은 짧고, 구체적이며, 다음 행동이 분명하다.
예를 들어 다음과 같은 알림은 실무에서 유용하다.
신규 발주서가 도착했으니 영업 담당자가 확인해야 한다.
계약서 초안이 생성되었으니 팀장이 승인해야 한다.
정산 파일 검증 중 오류가 발견되었으니 재업로드가 필요하다.
오늘 오후 회의 전에 주간 보고서가 자동 생성되었다.
Slack이나 Teams는 웹훅 또는 API로 메시지를 보낼 수 있다. 구조는 앞서 API 연동을 다룰 때 본 방식과 같다. URL, 인증 정보, JSON 본문, 상태 코드 확인, 로그 기록이 필요하다.
import os
import requests
SLACK_WEBHOOK_URL = os.getenv("SLACK_WEBHOOK_URL")
def send_slack_message(text, dry_run=True):
payload = {"text": text}
if dry_run:
print("[DRY-RUN] Slack 발송 예정:", payload)
return {"ok": True, "dry_run": True}
if not SLACK_WEBHOOK_URL:
raise RuntimeError("SLACK_WEBHOOK_URL 환경변수가 설정되어 있지 않습니다.")
response = requests.post(SLACK_WEBHOOK_URL, json=payload, timeout=10)
if response.status_code >= 400:
raise RuntimeError(f"Slack 발송 실패: {response.status_code} {response.text}")
return {"ok": True, "status_code": response.status_code}
send_slack_message("신규 발주서 3건이 도착했습니다. 확인 폴더를 검토해 주세요.")Microsoft Teams, Google Chat, Google Workspace, Microsoft 365도 방식은 비슷하다. 다만 조직 계정, 권한, 관리자 승인, 감사 로그 정책이 서비스마다 다르다. 개인 토큰 하나로 모든 채널에 메시지를 보내는 구조는 피해야 한다. 자동화 전용 계정을 만들고, 필요한 채널과 기능에만 최소 권한을 부여하는 것이 좋다.
승인 요청은 일반 알림보다 더 조심해야 한다. 메시지에는 판단에 필요한 정보와 승인 링크가 있어야 한다.
[승인 요청] 계약서 초안 검토
- 고객사: 한빛상사
- 계약 금액: 3,500,000원
- 계약 기간: 2026-06-01 ~ 2026-06-30
- 문서: output/documents/계약서_초안_한빛상사.docx
- 요청자: 영업지원 자동화 봇
승인 또는 반려 사유를 승인 시스템에 입력해 주세요.중요한 점은 Slack 메시지의 이모지 반응만으로 고위험 승인을 끝내지 않는 것이다. 금액 변경, 계약 발송, 고객 공지처럼 책임이 큰 업무는 별도의 승인 기록이 남는 시스템과 연결해야 한다. 메시지는 알림이고, 승인은 기록 가능한 절차여야 한다.
일정 공유와 보고 흐름 자동화하기
문서와 알림 다음으로 많이 자동화하는 영역은 일정 공유다. 예를 들어 매주 월요일 오전에 주간 보고서 초안을 만들고, 팀 채널에 링크를 공유하고, 금요일 오후에는 미완료 작업 목록을 담당자에게 보내는 흐름이다.
이 자동화는 보통 다음 단계로 구성된다.
데이터베이스나 업무 파일에서 이번 주 대상 데이터를 조회한다.
보고서 템플릿으로 문서를 생성한다.
문서 저장 위치를 협업 도구에 공유한다.
검토 담당자에게 알림을 보낸다.
발송 이력을 남긴다.
여기서 캘린더 API를 사용하면 회의 일정 생성, 참석자 초대, 리마인더 발송도 가능하다. 하지만 일정 자동화는 중복 생성이 특히 자주 발생한다. 같은 회의를 여러 번 만들거나, 참석자를 잘못 초대하면 혼란이 크다. 따라서 일정에는 고유 키를 두는 것이 좋다.
예를 들어 project_id + meeting_date + meeting_type을 기준으로 이미 생성된 일정인지 확인한 뒤 없을 때만 새로 만든다. 이것은 데이터베이스를 다룰 때 본 중복 실행 방지 원칙과 같다.
잘못된 자동 발송을 막는 운영 설계
커뮤니케이션 자동화에서 가장 위험한 부분은 발송이다. 파일 정리 자동화는 잘못되어도 내부에서 수정할 수 있는 경우가 많다. 하지만 이메일, 고객 문서, 협업 도구 메시지는 한 번 발송되면 여러 사람이 보게 된다. 외부 고객에게 나간 메일은 회수도 어렵다.
그래서 운영 설계에는 다음 네 가지 장치가 필요하다.
첫째, 테스트 모드다. dry_run=True일 때는 실제 발송하지 않고 수신자, 제목, 본문, 첨부파일 목록만 출력하거나 테스트 로그에 저장한다. 새 자동화는 반드시 테스트 모드로 며칠간 실행해 결과를 확인한 뒤 실제 발송으로 전환한다.
둘째, 승인 단계다. 고객 발송, 계약서 전달, 비용 승인, 인사 정보 공유처럼 위험도가 높은 업무는 사람이 확인해야 한다. 자동화는 초안을 만들고 승인 요청을 보낸다. 최종 발송은 승인 상태가 approved일 때만 실행한다.
def can_send(record):
if record["risk_level"] == "high" and record["approval_status"] != "approved":
return False
if not record.get("recipient"):
return False
if not record.get("subject"):
return False
return True셋째, 발송 로그다. 최소한 다음 정보는 남겨야 한다.
발송 시각
발송 도구
수신자 또는 채널
제목 또는 메시지 요약
첨부파일명
승인자
발송 결과
오류 메시지
넷째, 수신자 제한이다. 개발 중에는 실제 고객 주소로 발송하지 않도록 해야 한다. 환경이 dev일 때는 모든 메일을 내부 테스트 주소로 바꾸는 방식이 안전하다.
def resolve_recipient(original_email, env):
if env != "prod":
return "[email protected]"
return original_email이 작은 함수 하나가 큰 사고를 막을 수 있다.
규칙 기반 자동화와 AI 자동화의 경계
이 장의 업무 대부분은 규칙 기반 자동화가 먼저다. 제목에 특정 단어가 있으면 분류하고, 템플릿에 값을 채우고, 승인 상태가 완료되면 발송하는 작업은 명확한 규칙으로 처리할 수 있다. 이런 영역에 AI를 무리하게 넣으면 결과를 예측하기 어려워진다.
AI가 유용한 지점은 따로 있다. 긴 이메일에서 요청 사항을 요약하거나, 회의 메모에서 할 일을 뽑거나, 고객 문의를 대략적인 유형으로 나누는 작업이다. 하지만 AI가 만든 요약이나 분류는 최종 사실이 아니다. 중요한 발송, 계약, 승인에는 반드시 원본 데이터와 사람의 확인이 필요하다.
좋은 커뮤니케이션 자동화는 사람을 대체하지 않는다. 사람이 매번 복사하고 붙여넣던 일을 줄이고, 확인해야 할 대상을 정확한 시점에 전달한다. 자동화가 팀의 일상 도구 안에 들어올 때, 비로소 업무 흐름 전체가 빨라진다.
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.