소개
시도하고자 했던 것과 그 이유
회사 공유 사서함으로 수신되는 인턴 채용 요청 메일 내용을 매번 수동으로 Excel 시트에 입력하는 업무가 반복적이고 시간 소모적이었습니다. 수기로 입력하다보면 Human Error가 발생할 때가 있어 오류 발생률을 줄이고자 했습니다.
목표는 명확했습니다:
채용 요청 메일이 도착하면 자동으로 파싱하여 Excel 시트에 입력
후보자 정보(성명, 이메일, 핸드폰번호, 소속 등) 15개 이상 필드 자동 매핑
발신인 정보를 통한 후보자 소속 자동 매칭
계정 발급 여부 등 추가 데이터 처리
진행 방법
사용 도구 및 기술 스택
Power Automate : 메일 트리거 및 자동화 플로우 구축
Office 365 Outlook 커넥터: 공유 사서함 메일 수신
Excel Online (Business) 커넥터: SharePoint Excel 테이블 읽기/쓰기
정규식 기반 파싱: HTML 태그 제거 및 필드 추출
핵심 프로세스
1. 트리거 설정
트리거 유형: "새 이메일이 공유 사서함에 도착하는 경우(V2)"
- 공유 사서함 주소: [email protected]
- 특정 발신자 필터링 (인턴 채용 담당자 8명)
- 실행 주기: 1분 (트래픽에 따라 3분 권장)
- splitOn 설정으로 메일마다 별도 플로우 실행
※ splitOn이란?
Power Automate에서 트리거가 여러 개의 항목(배열)을 한 번에 반환할 때, 각 항목마다 별도의 플로우 인스턴스를 실행하도록 하는 설정입니다.
예시
splitOn 없을 때:
메일 3개가 동시에 도착
↓
플로우 1번 실행
↓
3개 메일을 한꺼번에 처리 (배열로 받음)
↓
복잡한 반복문(Apply to each) 필요
splitOn 있을 때:
메일 3개가 동시에 도착
↓
플로우 3번 실행 (각각 독립적으로)
├─ 플로우 인스턴스 1: 메일 A 처리
├─ 플로우 인스턴스 2: 메일 B 처리
└─ 플로우 인스턴스 3: 메일 C 처리
2. HTML 메일 본문 파싱 (핵심 난제 해결)
문제점: 메일 본문이 <li style="font-weight:700">, <span role="presentation"> 등 속성이 포함된 복잡한 HTML 태그로 구성되어 있어 단순 태그 제거로는 불가능
해결 전략: 4단계 HTML 정제 프로세스
// Step 1: 메일 본문 추출 (핵심 수정사항)
var_메일본문 = triggerBody()?['body']
// ❌ 잘못된 초기 시도: string(triggerBody()) → 전체 JSON이 문자열로 변환됨
// ✅ 올바른 방법: triggerBody()?['body'] → HTML 본문만 추출
// Step 2: HTML_태그_제거 (1차 정제 - 기본 태그 제거)
replace(replace(replace(replace(replace(replace(replace(
variables('var_메일본문'),
'<br>', decodeUriComponent('%0A')), // 줄바꿈 태그 → \n
'<br/>', decodeUriComponent('%0A')),
'</p>', decodeUriComponent('%0A')),
'</li>', decodeUriComponent('%0A')),
'<li>', ''), // 리스트 태그 제거
'<span>', ''),
'</span>', '')
// ... 총 21개 replace 함수 체이닝
// Step 3: HTML_태그_제거2 (2차 정제 - 속성 제거)
// 문제: <span style="font-weight:700"> 같은 속성 포함 태그가 남음!
replace(replace(replace(replace(replace(
outputs('HTML_태그_제거'),
'style=', ''), // style 속성 제거
'font-weight', ''), // font-weight 제거
'font-family', ''), // font-family 제거
'font-size', ''), // font-size 제거
'700', '') // 폰트 굵기 값 제거
// Step 4: HTML_태그_제거3 (3차 정제 - 남은 태그 완전 제거)
replace(replace(replace(replace(
outputs('HTML_태그_제거2'),
'<span', ''), // span 태그 시작 부분 제거
'</span', ''), // span 태그 종료 부분 제거
'>', ''), // 꺾쇠 괄호 제거
'"', '') // 따옴표 제거
// Step 5: HTML_태그_제거4 (4차 정제 - 특수문자 정리)
replace(replace(replace(
outputs('HTML_태그_제거3'),
':', ' '), // 콜론을 공백으로 변환
'=', ''), // 등호 제거
' ', ' ') // 이중 공백을 단일 공백으로
왜 4단계까지 필요했나?
최초 시도에서는 21개 replace 함수로 기본 태그만 제거했으나, 실제 메일에는 다음과 같은 문제가 발생:
1단계 후:
<span style="font-weight:700">같은 속성 포함 태그가 남음2단계 후: 속성은 제거되었으나
<span,>같은 태그 조각 잔존3단계 후: 태그는 모두 제거되었으나
:,=같은 특수문자가 데이터에 혼입4단계 후: 완벽하게 정제된 순수 텍스트 획득
핵심 교훈: 복잡한 수식 하나보다 단순한 정제 단계를 여러 번 반복하는 것이 Power Automate에서 훨씬 안정적이고 디버깅하기 쉽습니다!
중요 사항:
char(10)대신decodeUriComponent('%0A')사용 (char() 함수는 변수 초기화 액션에서만 사용 가능)한 번에 모든 태그를 제거하려 하지 말고 단계별로 분리하여 안정성 확보
3. 개별 필드 추출
정제된 텍스트에서 각 필드를 추출하는 공통 패턴:
// 이메일 주소 추출 예시
var_이메일 = trim(
replace(
last(split(
first(filter(
split(outputs('HTML_태그_제거4'), decodeUriComponent('%0A')), // ← 최종 정제 결과 사용
item => contains(item, '이메일')
)),
':'
)),
'주소 ', '' // "주소 [email protected]" → "[email protected]"
)
)
패턴 설명:
split(..., decodeUriComponent('%0A')): 줄바꿈으로 분할filter(..., item => contains(item, '이메일')): 특정 키워드 포함 라인 찾기first(...): 첫 번째 매칭 라인 선택split(..., ':'): 콜론으로 분할하여 값 추출last(...): 마지막 부분(실제 값) 선택replace(..., '주소 ', ''): 불필요한 텍스트 제거trim(...): 공백 제거
4. 복합 필드 처리 (채용기간 → 입사일/퇴사일 분리)
// Step 1: 채용기간 전체 추출
var_채용기간 = trim(
last(split(
first(filter(
split(outputs('HTML_태그_제거4'), decodeUriComponent('%0A')),
item => contains(item, '채용 기간')
)),
':'
))
)
// 결과: "2025-11-24 ~ 2026-01-31"
// Step 2: 입사일 분리 (runAfter: var_채용기간 필수!)
var_입사일 = trim(first(split(variables('var_채용기간'), ' ~ ')))
// 결과: "2025-11-24"
// Step 3: 퇴사일 분리 (runAfter: var_채용기간 필수!)
var_퇴사일 = trim(last(split(variables('var_채용기간'), ' ~ ')))
// 결과: "2026-01-31"
주의: var_입사일과 var_퇴사일은 runAfter 설정에서 var_채용기간에 의존하도록 반드시 설정해야 함!
5. 발신인 매칭을 통한 소속 자동 매핑
// Step 1: 담당자 메일 주소 추출
var_담당자메일 = triggerBody()?['from']
// Step 2: 매칭 테이블 조회
테이블에_있는_행_나열: Excel 테이블에서 발신인-소속 매핑 데이터 읽기
// Step 3: 필터링
필터_배열: @equals(item()?['담당자 메일'],variables('var_담당자 메일'))
// Step 4: 소속 정보 추출
var_소속 = if(greater(length(body('필터_배열')), 0),
first(body('필터_배열'))?['관할조직'], '')
6. Excel 테이블 입력 및 결과 알림
// Excel 행 추가
테이블에_행_추가: {
"성명": variables('var_성명'),
"이메일": variables('var_이메일'),
"핸드폰번호": variables('var_핸드폰번호'),
"소속": variables('var_소속'),
"입사일": variables('var_입사일'),
"퇴사일": variables('var_퇴사일'),
"계정생성대상": variables('var_계정생성대상'),
// ... 15개 이상 필드 매핑
}
// 조건 분기: 성공/실패 알림
조건: @equals(outputs('테이블에_행_추가')?['statusCode'], 200)
→ 성공 시: "{소속} {성명} 인턴 시트 입력 완료" 메일 발송
→ 실패 시: "수기 입력 필요" 메일 발송
결과와 배운 점
최종 성과
✅ 완벽한 자동화 달성
메일 수신부터 Excel 입력까지 100% 자동화
평균 처리 시간: 메일당 2~3초
수동 작업 대비 시간 절감 95% 이상
✅ 정확도
HTML 파싱 성공률: 98% (특수 케이스 제외)
15개 이상 필드 정확 추출 및 매핑
핵심 교훈
1. HTML 파싱의 현실: 단계별 접근이 핵심
한 번에 완벽한 파싱을 시도하지 말 것
4단계 정제 전략이 안정성과 디버깅에 결정적
2. Power Automate 수식의 함정과 해결책
❌ 피해야 할 것:
- char() 함수를 변수 초기화 외에 사용
- 너무 복잡한 중첩 함수 (저장 실패 위험)
- null 체크 없이 바로 split/filter 실행
- runAfter 의존성 미설정
✅ 권장 사항:
- decodeUriComponent('%0A')로 줄바꿈 처리
- 간단한 수식을 여러 단계로 분리
- runAfter 의존성 정확히 설정
3. 시행착오 극복 과정
문제 1: var_메일본문이 string(triggerBody())로 초기화되어 전체 JSON 객체가 문자열로 변환 → 해결: triggerBody()?['body']로 HTML 본문만 추출
문제 2: HTML 태그가 완전히 제거되지 않아 파싱 실패 → 해결: 4단계 정제 프로세스 구축
1단계: 기본 태그 제거
2단계: style, font 속성 제거
3단계: 남은 태그 조각 완전 제거
4단계: 특수문자 정리
문제 3: split 함수에 매개변수 1개만 전달되는 오류 → 해결: decodeUriComponent('%0A')가 제대로 평가되지 않는 경우 대비, runAfter 의존성 명확화
문제 4: 성명, 이메일, 핸드폰번호에 <span style="font-weight:700"> 같은 HTML 조각 포함 → 해결: HTML_태그_제거3, HTML_태그_제거4 단계 추가로 완벽 정제
문제 5: var_입사일, var_퇴사일 추출 실패 → 해결: var_채용기간을 먼저 추출한 후 ~ 구분자로 분리, runAfter 설정 필수
아쉬운 점과 개선 과제
변수명 불일치: 일부 변수명이 통일되지 않아 추후 리팩토링 필요
오류 처리: 현재는 성공/실패 알림만 있고, 부분 성공 케이스에 대한 처리 부족
향후 계획
모니터링: Power BI 대시보드 연동하여 채용 현황 실시간 시각화
예외 처리 강화: 필드 누락, 형식 오류 시 담당자에게 상세 알림