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

- 작성자: [주말러](https://community.clauders.ai/member/imported-member-B8X4Y1GWIK)
- 공간: [에이전트 / 노코드 자동화](https://community.clauders.ai/automation)
- 게시: 2025-11-26
- 연재: [AI 에이전트 빌더 활용](https://community.clauders.ai/series/openai-agent-builder) 1/1편
- 태그: #에이전트빌더
- 주소: https://community.clauders.ai/automation/post/%EC%9D%B8%ED%84%B4-%EC%B1%84%EC%9A%A9-%EB%A9%94%EC%9D%BC-%EC%9E%90%EB%8F%99%ED%99%94-%EC%82%AC%EB%A1%80-power-automate-%ED%99%9C%EC%9A%A9%EA%B8%B0-f894cd1c-e89a-804f-9487-ce09efee2f02

---

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

회사 공유 사서함으로 수신되는 인턴 채용 요청 메일 내용을 매번 수동으로 Excel 시트에 입력하는 업무가 반복적이고 시간 소모적이었습니다. 수기로 입력하다보면 Human Error가 발생할 때가 있어 오류 발생률을 줄이고자 했습니다.

**목표는 명확했습니다:**

- 채용 요청 메일이 도착하면 자동으로 파싱하여 Excel 시트에 입력
- 후보자 정보(성명, 이메일, 핸드폰번호, 소속 등) 15개 이상 필드 자동 매핑
- 발신인 정보를 통한 후보자 소속 자동 매칭
- 계정 발급 여부 등 추가 데이터 처리

### **사용 도구 및 기술 스택**

- **Power Automate **: 메일 트리거 및 자동화 플로우 구축
- **Office 365 Outlook 커넥터**: 공유 사서함 메일 수신
- **Excel Online (Business) 커넥터**: SharePoint Excel 테이블 읽기/쓰기
- **정규식 기반 파싱**: HTML 태그 제거 및 필드 추출

### **핵심 프로세스**

**1. 트리거 설정**

```
트리거 유형: "새 이메일이 공유 사서함에 도착하는 경우(V2)"
- 공유 사서함 주소: abcd@company.com
- 특정 발신자 필터링 (인턴 채용 담당자 8명)
- 실행 주기: 1분 (트래픽에 따라 3분 권장)
- splitOn 설정으로 메일마다 별도 플로우 실행
```

### **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, '이메일')
            )),
            ':'
        )),
        '주소 ', ''    // "주소 asdf@naver.com" → "asdf@naver.com"
    )
)
```

**패턴 설명**:

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. **예외 처리 강화**: 필드 누락, 형식 오류 시 담당자에게 상세 알림
