인턴 채용 메일 자동화 사례 - Power Automate 활용기

소개

시도하고자 했던 것과 그 이유

회사 공유 사서함으로 수신되는 인턴 채용 요청 메일 내용을 매번 수동으로 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. 1단계 후: <span style="font-weight:700"> 같은 속성 포함 태그가 남음

  2. 2단계 후: 속성은 제거되었으나 <span, > 같은 태그 조각 잔존

  3. 3단계 후: 태그는 모두 제거되었으나 :, = 같은 특수문자가 데이터에 혼입

  4. 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]"
    )
)

패턴 설명:

  1. split(..., decodeUriComponent('%0A')): 줄바꿈으로 분할

  2. filter(..., item => contains(item, '이메일')): 특정 키워드 포함 라인 찾기

  3. first(...): 첫 번째 매칭 라인 선택

  4. split(..., ':'): 콜론으로 분할하여 값 추출

  5. last(...): 마지막 부분(실제 값) 선택

  6. replace(..., '주소 ', ''): 불필요한 텍스트 제거

  7. 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 설정 필수

아쉬운 점과 개선 과제

  • 변수명 불일치: 일부 변수명이 통일되지 않아 추후 리팩토링 필요

  • 오류 처리: 현재는 성공/실패 알림만 있고, 부분 성공 케이스에 대한 처리 부족

향후 계획

  1. 모니터링: Power BI 대시보드 연동하여 채용 현황 실시간 시각화

  2. 예외 처리 강화: 필드 누락, 형식 오류 시 담당자에게 상세 알림

3
3개의 답글

뉴스레터 무료 구독

👉 이 게시글도 읽어보세요