0Pricing
AI Agents · 강의

프로그래밍으로 이메일 읽기 및 보내기

메시지 목록 조회, 메시지 본문 가져오기, MIME 이메일 보내기를 알아봅니다.

프로그래밍으로 이메일 읽기 및 보내기은(는) CoddyKit의 무료 AI Agents 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 AI Agents 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. AI Agents 강의에는 총 4개의 강의가 포함되어 있습니다.

Gmail API로 메시지 목록 조회

이메일을 읽는 첫 단계는 기준에 맞는 메시지를 목록으로 조회하는 것입니다. service.users().messages().list()는 전체 내용이 아니라 메시지 ID와 스레드 ID를 반환합니다. 그런 다음 각 메시지를 개별적으로 가져옵니다. 이 두 단계 패턴을 사용하면 목록 조회 호출을 빠르게 유지할 수 있습니다.

def list_messages(service, user_id='me', query='', max_results=10):
    results = service.users().messages().list(
        userId=user_id,
        q=query,          # Gmail search query
        maxResults=max_results
    ).execute()

    messages = results.get('messages', [])
    print(f'Found {len(messages)} messages')
    return messages

# Examples of Gmail search queries:
# 'is:unread' — unread messages
# 'from:boss@company.com is:unread' — unread from boss
# 'subject:invoice label:inbox' — invoices in inbox
# 'after:2026/05/01 has:attachment' — recent with attachments
messages = list_messages(gmail_service, query='is:unread label:inbox')

전체 메시지 가져오기

service.users().messages().get()을 사용하여 전체 메시지 내용을 가져옵니다. format 매개변수는 반환되는 데이터의 양을 제어합니다. 'full'은 헤더와 본문을 포함하고, 'metadata'는 헤더만 반환하며, 'minimal'은 ID와 라벨만 반환합니다.

def get_message(service, message_id, user_id='me'):
    message = service.users().messages().get(
        userId=user_id,
        id=message_id,
        format='full'  # 'full', 'metadata', or 'minimal'
    ).execute()
    return message

# Fetch the first unread message
messages = list_messages(gmail_service, query='is:unread', max_results=1)
if messages:
    msg = get_message(gmail_service, messages[0]['id'])
    print('Thread ID:', msg['threadId'])
    print('Labels:', msg['labelIds'])
    print('Snippet:', msg['snippet'][:100])

이메일 헤더 추출

헤더(보낸 사람, 받는 사람, 제목, 날짜)는 message['payload']['headers']에 {'name': ..., 'value': ...} 형식의 딕셔너리 목록으로 저장됩니다. 이름으로 헤더를 추출하는 도우미 함수를 작성하십시오. 이 함수를 계속 사용하게 될 것입니다.

def get_header(message, name):
    headers = message.get('payload', {}).get('headers', [])
    for h in headers:
        if h['name'].lower() == name.lower():
            return h['value']
    return ''

def extract_email_meta(message):
    return {
        'id': message['id'],
        'from': get_header(message, 'From'),
        'to': get_header(message, 'To'),
        'subject': get_header(message, 'Subject'),
        'date': get_header(message, 'Date'),
        'snippet': message.get('snippet', '')
    }

meta = extract_email_meta(msg)
print(f'From: {meta["from"]}')
print(f'Subject: {meta["subject"]}')
print(f'Date: {meta["date"]}')

이메일 본문 디코딩(base64)

Gmail API의 이메일 본문은 base64url로 인코딩되어 있습니다. 이는 +가 -로, /가 _로 바뀌는 URL 안전형 base64입니다. 디코딩하려면 base64.urlsafe_b64decode()를 사용하십시오. 단일 부분 이메일과 다중 부분 이메일을 모두 처리해야 합니다.

import base64

def decode_body(data):
    if not data:
        return ''
    decoded_bytes = base64.urlsafe_b64decode(data + '==')
    return decoded_bytes.decode('utf-8', errors='replace')

def get_email_body(message):
    payload = message.get('payload', {})
    mime_type = payload.get('mimeType', '')

    # Simple (non-multipart) email
    if 'body' in payload and payload['body'].get('data'):
        return decode_body(payload['body']['data'])

    # Multipart email: find the text/plain or text/html part
    parts = payload.get('parts', [])
    for part in parts:
        if part.get('mimeType') == 'text/plain':
            return decode_body(part['body'].get('data', ''))

    # Fallback: try text/html
    for part in parts:
        if part.get('mimeType') == 'text/html':
            return decode_body(part['body'].get('data', ''))

    return message.get('snippet', '')

# --- demo ---
encoded = base64.urlsafe_b64encode(b'Hello from the agent!').decode().rstrip('=')
print('Decoded body:', decode_body(encoded))

message = {
    'payload': {
        'mimeType': 'multipart/alternative',
        'parts': [
            {'mimeType': 'text/plain', 'body': {'data': encoded}}
        ]
    }
}
print('Email body:', get_email_body(message))

다중 부분 이메일을 재귀적으로 처리하기

첨부 파일, 인라인 이미지 또는 혼합 콘텐츠가 포함된 복잡한 이메일은 중첩된 다중 부분 구조로 이루어집니다. 본문 부분은 임의의 깊이로 중첩될 수 있습니다. 부분 트리를 순회하는 재귀 함수 하나로 모든 경우를 처리할 수 있습니다.

import base64

def extract_parts(payload, target_mime='text/plain'):
    parts_text = []
    mime_type = payload.get('mimeType', '')

    if mime_type == target_mime:
        data = payload.get('body', {}).get('data', '')
        if data:
            decoded = base64.urlsafe_b64decode(data + '==').decode('utf-8', errors='replace')
            parts_text.append(decoded)

    # Recurse into sub-parts
    for part in payload.get('parts', []):
        parts_text.extend(extract_parts(part, target_mime))

    return parts_text

def get_plain_text(message):
    payload = message.get('payload', {})
    texts = extract_parts(payload, 'text/plain')
    return '\n\n'.join(texts) if texts else message.get('snippet', '')

body_text = get_plain_text(msg)
print(f'Body ({len(body_text)} chars):', body_text[:200])

메시지를 읽음으로 표시하기

이메일을 처리한 후 에이전트는 UNREAD 라벨을 제거하여 읽음으로 표시해야 합니다. removeLabelIds=['UNREAD']와 함께 service.users().messages().modify()를 사용하십시오. 에이전트가 처리한 이메일을 추적하기 위해 PROCESSED와 같은 라벨을 추가할 수도 있습니다.

def mark_as_read(service, message_id, user_id='me'):
    service.users().messages().modify(
        userId=user_id,
        id=message_id,
        body={'removeLabelIds': ['UNREAD']}
    ).execute()
    print(f'Marked {message_id} as read')

def add_label(service, message_id, label_id, user_id='me'):
    service.users().messages().modify(
        userId=user_id,
        id=message_id,
        body={'addLabelIds': [label_id]}
    ).execute()

# Get label ID by name
def get_label_id(service, label_name, user_id='me'):
    labels = service.users().labels().list(userId=user_id).execute()
    for label in labels.get('labels', []):
        if label['name'].lower() == label_name.lower():
            return label['id']
    return None

# --- demo: minimal stand-in for the Gmail API's service object ---
class _Exec:
    def __init__(self, result):
        self._result = result
    def execute(self):
        return self._result

class _FakeUsers:
    def messages(self):
        return self
    def modify(self, **kwargs):
        print(f'[gmail api] messages.modify({kwargs})')
        return _Exec({'id': kwargs.get('id')})
    def labels(self):
        return self
    def list(self, **kwargs):
        return _Exec({'labels': [{'id': 'Label_1', 'name': 'Processed'}]})

class _FakeService:
    def users(self):
        return _FakeUsers()

service = _FakeService()
mark_as_read(service, 'msg_42')
add_label(service, 'msg_42', 'Label_1')
print('Label id for "Processed":', get_label_id(service, 'Processed'))

MIMEText로 이메일 작성하기

이메일을 보내려면 먼저 Python 표준 email 라이브러리를 사용하여 MIME 메시지로 작성합니다. 그런 다음 원시 바이트를 base64url로 인코딩하고 Gmail API에 POST합니다. MIMEText가 메시지 본문의 올바른 인코딩을 처리합니다.

import base64
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

def create_message(sender, to, subject, body_text, body_html=None):
    if body_html:
        msg = MIMEMultipart('alternative')
        msg.attach(MIMEText(body_text, 'plain', 'utf-8'))
        msg.attach(MIMEText(body_html, 'html', 'utf-8'))
    else:
        msg = MIMEText(body_text, 'plain', 'utf-8')

    msg['From'] = sender
    msg['To'] = to
    msg['Subject'] = subject

    # Encode as base64url
    raw = base64.urlsafe_b64encode(msg.as_bytes()).decode('utf-8')
    return {'raw': raw}

message = create_message(
    sender='agent@yourcompany.com',
    to='recipient@example.com',
    subject='Weekly Summary',
    body_text='Hello,\n\nHere is your summary.\n\nBest,\nAgent'
)

# --- demo ---
print('Message keys:', list(message.keys()))
print('Base64 length:', len(message['raw']))

Gmail API로 이메일 보내기

service.users().messages().send()를 사용하여 작성한 메시지를 보냅니다. userId='me' 매개변수는 인증된 사용자를 가리킵니다. API는 ID와 스레드 ID가 포함된 전송된 메시지를 반환합니다.

from googleapiclient.errors import HttpError

def send_message(service, message, user_id='me'):
    try:
        sent = service.users().messages().send(
            userId=user_id,
            body=message
        ).execute()
        print(f'Message sent! ID: {sent["id"]}')
        return sent
    except HttpError as e:
        import json
        body = json.loads(e.content.decode())
        print(f'Send failed ({e.resp.status}): {body.get("error", {}).get("message")}')
        return None

# Send the message
message = create_message(
    sender='me',
    to='team@company.com',
    subject='Agent Report',
    body_text='Processing complete. 42 tasks handled.'
)
send_message(gmail_service, message)

이메일 초안 작성 및 보내기

에이전트는 즉시 전송하는 대신 사람이 검토할 수 있도록 drafts를 만들 수 있습니다. service.users().drafts().create()를 사용하십시오. 그러면 사람이 Gmail 사용자 인터페이스에서 초안을 검토하고 보낼 수 있습니다. 사람의 승인이 필요한 모든 이메일에 권장되는 패턴입니다.

def create_draft(service, message, user_id='me'):
    draft = service.users().drafts().create(
        userId=user_id,
        body={'message': message}
    ).execute()
    print(f'Draft created: {draft["id"]}')
    return draft

def send_draft(service, draft_id, user_id='me'):
    sent = service.users().drafts().send(
        userId=user_id,
        body={'id': draft_id}
    ).execute()
    print(f'Draft sent as message: {sent["id"]}')
    return sent

# Create a draft for review
message = create_message(
    sender='me',
    to='client@example.com',
    subject='Proposal Follow-up',
    body_text='Dear Client,\n\nFollowing up on our proposal...'
)
draft = create_draft(gmail_service, message)
# Human reviews in Gmail, then agent sends:
# send_draft(gmail_service, draft['id'])

여러 이메일 일괄 처리

많은 이메일을 처리할 때는 빠른 반복문에서 하나씩 가져오지 마십시오. 할당량 제한에 도달하게 됩니다. 짧은 지연을 포함한 제어된 반복문을 사용하거나 Gmail API의 일괄 요청 기능으로 여러 작업을 하나의 HTTP 호출로 묶으십시오.

import time

def process_unread_emails(service, max_emails=20):
    messages = list_messages(
        service,
        query='is:unread label:inbox',
        max_results=max_emails
    )

    processed = []
    for i, msg_ref in enumerate(messages):
        # Rate-limit: process max 5 per second
        if i > 0 and i % 5 == 0:
            time.sleep(1)

        msg = get_message(service, msg_ref['id'])
        meta = extract_email_meta(msg)
        body = get_plain_text(msg)

        result = {
            'id': msg['id'],
            'from': meta['from'],
            'subject': meta['subject'],
            'body_preview': body[:200]
        }
        processed.append(result)
        mark_as_read(service, msg['id'])

    return processed

이메일에 답장하기(스레드 내)

스레드 내에서 답장하려면 원본 메시지의 Message-ID 헤더를 사용하여 In-Reply-To 및 References 헤더를 설정하고, 전송 호출에 threadId를 전달하십시오. 이렇게 하면 답장이 동일한 Gmail 대화 스레드에 유지됩니다.

import base64
from email.mime.text import MIMEText

def create_reply(original_message, reply_text, sender='me'):
    original_msg_id = get_header(original_message, 'Message-ID')
    to = get_header(original_message, 'From')
    subject = get_header(original_message, 'Subject')
    if not subject.startswith('Re:'):
        subject = 'Re: ' + subject

    msg = MIMEText(reply_text, 'plain', 'utf-8')
    msg['From'] = sender
    msg['To'] = to
    msg['Subject'] = subject
    msg['In-Reply-To'] = original_msg_id
    msg['References'] = original_msg_id

    raw = base64.urlsafe_b64encode(msg.as_bytes()).decode('utf-8')
    return {
        'raw': raw,
        'threadId': original_message['threadId']  # keeps it in thread
    }

빠른 확인: base64 이메일 본문

Gmail API 메시지 처리에 대한 이해도를 확인해 보십시오.

이메일 읽기 및 보내기 요약

이제 에이전트가 프로그래밍 방식으로 이메일을 읽고 보낼 수 있습니다:

  • 목록 조회: messages().list(q='is:unread')는 ID를 반환하며 Gmail 검색 구문을 사용
  • 가져오기: messages().get(id=..., format='full')은 전체 메시지를 반환
  • 헤더 분석: payload.headers에서 보낸 사람/제목/날짜 추출
  • 본문 디코딩: 텍스트에는 base64.urlsafe_b64decode(data)를 사용하고 다중 부분은 재귀적으로 처리
  • 보내기: MIMEText로 작성하고 base64url로 인코딩한 뒤 messages().send()를 통해 POST
  • 스레드에서 답장: In-Reply-To 헤더와 threadId를 전송 본문에 설정

자주 묻는 질문

“프로그래밍으로 이메일 읽기 및 보내기” 강의는 무료인가요?

네 — “프로그래밍으로 이메일 읽기 및 보내기” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 AI Agents 강의 전체를 잠금 해제할 수 있습니다. AI Agents 강의에는 총 4개의 강의가 포함되어 있습니다.

“프로그래밍으로 이메일 읽기 및 보내기”에서 뭘 배우나요?

메시지 목록 조회, 메시지 본문 가져오기, MIME 이메일 보내기를 알아봅니다. 브라우저에서 직접 실행하는 실습 코드로 AI Agents을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

AI Agents을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 AI Agents은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.

“프로그래밍으로 이메일 읽기 및 보내기” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 AI Agents 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 AI Agents 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. API로 Gmail 연결하기
  2. 프로그래밍으로 이메일 읽기 및 보내기
  3. 캘린더 일정 생성 및 조회
  4. 간단한 이메일 도우미 에이전트 만들기
← AI Agents(으)로 돌아가기