외주 작업으로 아는 형님 회사의 이메일 작업을 자동화하는 작업을 맡았었다. 발주서 PDF 파일을 인식해서 구글 앱스크립트를 활용해서 스프레드시트에 올리는 자동화 작업을 진행했다.

이 과정에서 처리 완료된 메일을 다른 폴더로 이동시키는 후처리가 필요했는데, 처음에는 네이버웍스 공식 API를 활용하여 메일 이동 기능을 구현하고자 했으나, 네이버웍스 메일 API에는 메일 이동(Move) 기능이 없었다..

이에 따라 겪었던 시행착오와 IMAP을 통해 문제를 해결한 과정을 정리해봤다.


1. 첫 번째 시도: 자동분류(필터) API 활용과 한계

메일 이동 API가 없었기 때문에, 대안으로 메일 자동분류 기능을 동적으로 제어하는 방식을 구상했다.

  • 작업 흐름: 자동분류 추가 -> 자동분류 조회 -> 자동분류 삭제

하지만 자동분류 필터링 조건에는 특정 메일 ID(Mail ID)를 지정할 수 없다. 특정 메일 하나만 타겟팅하여 이동시키는게 불가능했기 때문에 이 방식으로는 할 수 없었다.

결국 IMAP 프로토콜을 통해 직접 메일 서버에 연결하여 메일을 이동시키는 방식을 선택했다.


2. 메일 식별 문제와 해결: 발송 시간 초단위 매칭

IMAP을 사용할 때 GAS 배치 프로세스가 처리한 바로 그 메일을 정확히 찾아내야 했는데 메일 key값을 직접 전달해주지 못했다.

왜냐면 네이버웍스 api를 사용해 메일을 조회했을 때 IMAP 표준에서 사용될 메일 key값이 undefined로 내려왔기 때문인데, 그래서 메일 발송 시간을 대조하는 방식을 사용했다.

발신자 & 제목으로 검색한 뒤 발송 시간을 초단위로 비교하되, 1~2초 내외의 오차를 허용 범위로 두고 검색하도록 구현하여 타겟 메일을 정확히 특정할 수 있었다.


3. 두 번째 에러: 메일함 한글 인식

파이썬 내장 라이브러리인 imaplib을 사용해 한글 폴더로 접근을 시도했을 때 에러가 발생했다.

import imaplib

mail = imaplib.IMAP4_SSL("imap.naverworks.com")
mail.login("your-email@domain.com", "your-password")

# 한글 폴더 선택 시도
mail.select("발주서_처리대기")

위 코드는 다음과 같은 에러를 발생시킨다.

UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-2: ordinal not in range(128)

원인 분석

imaplib은 소켓 통신을 진행할 때 기본적으로 아스키(ASCII) 코덱으로 인코딩을 시도한다. 따라서 아스키 범위를 벗어나는 한글 문자열이 전달되면 인코딩 에러가 발생한다.


4. 세 번째 에러: UTF-8 바이트 전송 실패

그래서 한글 문자열을 UTF-8 바이트로 직접 인코딩하여 전송해 보았다.

folder_name = "발주서_처리대기".encode("utf-8")
mail.select(folder_name)

이 경우에는 인코딩 에러는 발생하지 않지만, 메일 서버 측에서 폴더를 찾지 못하는 에러가 발생한다.

imaplib.IMAP4.error: select failed: [NONEXISTENT] Mailbox doesn't exist

원인

IMAP 프로토콜(RFC 3501) 규격상, 사전에 UTF8=ACCEPT 확장이 합의되지 않는 한 메일함 이름에 포함된 다국어 문자를 일반 UTF-8 바이트 스트림으로 처리할 수 없다. 네이버웍스 IMAP 서버는 전달된 UTF-8 바이트를 정상적인 폴더명으로 인식하지 못했다.


5. 해결책: Modified UTF-7 인코딩 적용

IMAP 표준 규격에 따라 다국어 메일함 이름은 Modified UTF-7 방식으로 인코딩하여 전송해야 한다.

Modified UTF-7은 다국어 문자 영역을 Modified Base64로 변환하고, 변환된 영역의 시작을 &, 끝을 - 문자로 표시하며, Base64 패딩 문자 중 /,로 대체하는 표준 규격이다.

파이썬 환경에서 이를 처리하기 위해 아래와 같이 인코딩 변환 함수를 작성했다.

import binascii

def encode_modified_utf7(s: str) -> bytes:
    """
    일반 문자열을 IMAP Modified UTF-7 바이트로 변환
    """
    res = []
    r = []
    
    for c in s:
        if 0x20 <= ord(c) <= 0x7E and c != '&':
            if r:
                res.append(f"&{binascii.b2a_base64(''.join(r).encode('utf-16be')).decode('ascii').rstrip('=\n').replace('/', ',')}-")
                r = []
            res.append(c)
        elif c == '&':
            if r:
                res.append(f"&{binascii.b2a_base64(''.join(r).encode('utf-16be')).decode('ascii').rstrip('=\n').replace('/', ',')}-")
                r = []
            res.append('&-')
        else:
            r.append(c)
            
    if r:
        res.append(f"&{binascii.b2a_base64(''.join(r).encode('utf-16be')).decode('ascii').rstrip('=\n').replace('/', ',')}-")
        
    return "".join(res).encode('ascii')

이 변환 함수를 사용하여 한글 폴더명 간에 메일을 이동시키는 최종 코드를 만들었다.

# 한글 폴더명 변환
src_folder = encode_modified_utf7("발주서_처리대기")
dest_folder = encode_modified_utf7("발주서_처리완료")

# 1. 원본 폴더 선택
mail.select(src_folder)

# 2. 발송 시간 매칭 조건 등을 활용해 대상 메일 검색
status, data = mail.search(None, "ALL")
mail_ids = data[0].split()

if mail_ids:
    target_id = mail_ids[-1]
    
    # 3. 폴더 복사 및 기존 메일 삭제 플래그 처리
    result, apply_data = mail.copy(target_id, dest_folder)
    
    if result == 'OK':
        mail.store(target_id, '+FLAGS', '\\Deleted')
        mail.expunge()
        print("메일 이동 완료")

위 방식으로 작업을 전환하여 네이버웍스 API의 한계를 극복하고, GAS 배치 처리와 연동된 메일 이동 자동화를 성공적으로 구축할 수 있었다.

(사실 그냥 메일함 이름을 영어로 바꾸면 간단했지만..)