I recently worked on a project to automate email workflows for an acquaintance’s company. The goal was to extract information from invoice PDF attachments and upload it to Google Sheets using Google Apps Script (GAS).

As part of this workflow, I needed a post-processing step to move processed emails to an archive/completed folder. Initially, I hoped to use the official NaverWorks API to handle this, but discovered that the NaverWorks email API does not support a “Move” action.

Here is a summary of the trial and error I went through and how I ultimately resolved the issue using IMAP.


1. First Attempt: Dynamically Modifying Mail Filters (and Its Limitations)

Since NaverWorks lacked a direct “Move” API, I looked for workarounds. My first idea was to dynamically manipulate the automated email classification (filter) rules:

  • Workflow: Add filter rule -> Fetch filter rules -> Delete filter rule

However, NaverWorks mail filters do not support filtering by a specific email ID (Mail ID). Since it was impossible to target and move a single, specific email this way, I had to discard this approach.

Ultimately, I decided to connect directly to the mail server using the IMAP protocol to move the emails.


2. Identifying Emails: Matching Sent Times down to the Second

When using IMAP, I needed to identify the exact email that had just been processed by the GAS batch job. However, I couldn’t pass a unique identifier (like a Mail ID) directly.

This is because the mail key retrieved via the NaverWorks Web API does not map to the IMAP standard message UID (it returned as undefined). As a workaround, I decided to match emails using their metadata.

I first queried the inbox by sender and subject, and then compared the sent times down to the second. Allowing a small margin of error (1 to 2 seconds) to account for processing latency, I was able to reliably pinpoint the target email.


3. Second Attempt: Handling Non-ASCII Mailbox Names (Encoding Error)

When I attempted to access a folder named in Korean using Python’s built-in imaplib library, I ran into an error.

import imaplib

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

# Attempting to select non-ASCII folder name
mail.select("발주서_처리대기")

The above code produces the following error:

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

Root Cause

By default, imaplib attempts to encode commands using the ASCII codec before sending them over the socket. Passing a string with non-ASCII characters directly to mail.select() triggers a Python encoding error.


4. Third Attempt: Sending UTF-8 Encoded Bytes (Mailbox Not Found)

To bypass the Python-level ASCII encoding error, I tried encoding the non-ASCII string directly into UTF-8 bytes.

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

This prevents the local encoding error, but the mail server returns a “mailbox not found” error:

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

Root Cause

According to the IMAP protocol (RFC 3501) specification, mailbox names containing non-ASCII characters cannot be processed as raw UTF-8 byte streams unless a UTF8=ACCEPT extension handshake is negotiated during connection. Because this negotiation wasn’t set up, the NaverWorks IMAP server failed to recognize the UTF-8 bytes and reported that the mailbox did not exist.


5. The Solution: Encoding with Modified UTF-7

According to the IMAP standard, internationalized (non-ASCII) mailbox names must be encoded using Modified UTF-7 before transmission.

Modified UTF-7 converts non-ASCII character blocks into a modified version of Base64. These blocks are prefixed with & and suffixed with -. Additionally, the standard Base64 slash character (/) is replaced with a comma (,).

To implement this in Python, I wrote the following helper function to encode strings to Modified UTF-7:

import binascii

def encode_modified_utf7(s: str) -> bytes:
    """
    Converts a normal string into an IMAP Modified UTF-7 byte string
    """
    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')

Using this helper function, I wrote the final implementation to move emails between folders with non-ASCII names:

# Convert non-ASCII folder names
src_folder = encode_modified_utf7("발주서_처리대기")
dest_folder = encode_modified_utf7("발주서_처리완료")

# 1. Select the source folder
mail.select(src_folder)

# 2. Search for the target email using conditions like the sent time match
status, data = mail.search(None, "ALL")
mail_ids = data[0].split()

if mail_ids:
    target_id = mail_ids[-1]
    
    # 3. Copy the email to the destination folder and flag the original email for deletion
    result, apply_data = mail.copy(target_id, dest_folder)
    
    if result == 'OK':
        mail.store(target_id, '+FLAGS', '\\Deleted')
        mail.expunge()
        print("Email move completed")

By implementing Modified UTF-7 encoding, I overcame the limitations of the NaverWorks API and successfully automated the email movement process alongside the GAS batch job.

(Though in hindsight, simply renaming the folders to English would have saved a lot of time…)