파이썬 smtplib·email 자동화 가이드: 첨부파일과 대량 발송

파이썬 smtplib과 EmailMessage로 첨부파일을 포함한 이메일을 보내고, 여러 수신자에게 안전하게 발송하는 방법과 TLS·오류 처리·재시도 원칙을 설명합니다.

본문 상단 광고 구역 (승인 후 자동 노출됩니다)
파이썬 smtplib email 첨부파일 대량 발송 자동화 대표 이미지
파이썬 smtplib과 email 모듈을 이용한 첨부파일 이메일 자동 발송

파이썬에서 첨부파일이 포함된 업무 메일을 자동 발송하려면 email.message.EmailMessage로 제목·본문·첨부파일을 구성하고, smtplib로 SMTP 서버에 암호화 연결한 뒤 send_message()를 호출하면 됩니다. 다만 여러 사람에게 보낼 때는 받는 사람을 한꺼번에 노출하지 말고, 발송 간격·재시도·결과 기록을 넣어야 안전하게 운영할 수 있습니다.

파이썬 공식 이메일 예제 확인하기

핵심 요약

  • 메일 작성은 EmailMessage, 전송은 smtplib가 담당합니다.
  • 첨부파일은 바이너리로 읽고 MIME 유형과 파일명을 지정해 추가합니다.
  • 비밀번호는 코드에 직접 쓰지 말고 환경 변수나 비밀 저장소에서 불러옵니다.
  • 대량 발송은 수신자별 개별 전송, 속도 제한, 실패 로그와 재시도가 필수입니다.
  • 메일 사업자의 SMTP 주소·포트·앱 비밀번호 정책은 발송 전에 반드시 확인해야 합니다.

smtplib과 email 모듈의 역할

smtplib는 SMTP 또는 ESMTP 서버와 연결해 메일 데이터를 실제로 전달하는 표준 라이브러리입니다. 반면 email 패키지는 제목, 발신자, 수신자, 텍스트·HTML 본문, 첨부파일을 인터넷 메일 형식에 맞게 조립합니다. 즉, email이 택배 상자를 만들고 smtplib가 배송한다고 이해하면 쉽습니다.

구성 요소 주요 역할 대표 사용법
EmailMessage 헤더·본문·첨부파일 구성 set_content(), add_attachment()
SMTP 일반 연결 후 STARTTLS 전환 starttls(), login()
SMTP_SSL 연결 시작부터 SSL 적용 SMTP_SSL(host, port)
send_message EmailMessage 객체 전송 smtp.send_message(msg)

문자열을 직접 이어 붙여 MIME 메시지를 만드는 방식보다 EmailMessage를 쓰는 편이 한글 제목, 여러 본문 형식, 첨부파일 처리를 안정적으로 수행하기 좋습니다. 파이썬 공식 예제 역시 이 구조를 사용합니다.

SMTP 발송 전 준비사항

코드를 작성하기 전에 이용 중인 메일 서비스에서 SMTP 사용 가능 여부를 확인해야 합니다. 서비스마다 호스트 주소, 포트, SSL 또는 STARTTLS 방식, 인증 방법이 다릅니다. 2단계 인증을 사용하는 계정은 일반 로그인 비밀번호 대신 앱 비밀번호나 별도의 인증 토큰을 요구할 수 있습니다.

  1. 메일 서비스의 공식 도움말에서 SMTP 호스트와 포트를 확인합니다.
  2. SSL 연결인지 STARTTLS 연결인지 구분합니다.
  3. SMTP 인증이 허용된 계정인지 확인합니다.
  4. 앱 비밀번호가 필요하면 계정 보안 설정에서 별도로 발급합니다.
  5. 테스트 수신자 한 명에게 먼저 발송해 연결과 인코딩을 확인합니다.

계정 정보는 소스 코드에 직접 기록하지 않는 것이 중요합니다. 저장소에 올린 코드에 비밀번호가 포함되면 비공개 저장소라도 노출 위험이 있습니다. 운영 환경에서는 SMTP_USER, SMTP_PASSWORD 같은 환경 변수로 분리하고, 오류 로그에도 비밀번호가 출력되지 않도록 해야 합니다.

EmailMessage로 텍스트 메일 보내기

다음 예시는 환경 변수에서 인증 정보를 불러오고, 연결 시작부터 SSL을 사용하는 기본 구조입니다. 호스트와 포트는 이용 중인 메일 사업자의 공식 설정값으로 바꿔야 합니다.

import os
import ssl
import smtplib
from email.message import EmailMessage

smtp_host = os.environ["SMTP_HOST"]
smtp_port = int(os.getenv("SMTP_PORT", "465"))
smtp_user = os.environ["SMTP_USER"]
smtp_password = os.environ["SMTP_PASSWORD"]

msg = EmailMessage()
msg["Subject"] = "주간 업무 보고서"
msg["From"] = smtp_user
msg["To"] = "recipient@example.com"
msg.set_content("이번 주 업무 보고서를 전달드립니다.")

context = ssl.create_default_context()
with smtplib.SMTP_SSL(
    smtp_host, smtp_port, context=context, timeout=30
) as smtp:
    smtp.login(smtp_user, smtp_password)
    smtp.send_message(msg)

with 문을 사용하면 정상 종료뿐 아니라 예외 발생 시에도 연결 정리가 쉬워집니다. 서버가 STARTTLS 방식을 요구한다면 SMTP()로 연결하고 starttls(context=context)를 호출한 다음 로그인해야 합니다. 두 방식을 임의로 섞지 말고 서비스 공식 안내에 맞추십시오.

HTML 본문도 함께 제공하는 방법

HTML 메일만 보내면 일부 메일 환경에서 내용을 제대로 읽지 못할 수 있습니다. 먼저 일반 텍스트 본문을 넣고 add_alternative()로 HTML 버전을 추가하면 수신 프로그램이 지원하는 형식을 선택할 수 있습니다.

msg.set_content("보고서 확인을 부탁드립니다.")
msg.add_alternative("""
<html><body>
  <h2>주간 업무 보고</h2>
  <p>보고서 확인을 부탁드립니다.</p>
</body></html>
""", subtype="html")

첨부파일을 안전하게 추가하는 방법

첨부파일은 바이너리 모드로 읽고 add_attachment()에 데이터, 주 MIME 유형, 하위 유형, 실제 파일명을 전달합니다. 파일 확장자만 믿기보다 mimetypes로 유형을 추정하고, 알 수 없는 형식은 application/octet-stream으로 처리하는 방식이 실용적입니다.

import mimetypes
from pathlib import Path

def attach_file(msg, file_path):
    path = Path(file_path)
    mime_type, encoding = mimetypes.guess_type(path.name)

    if mime_type is None or encoding is not None:
        mime_type = "application/octet-stream"

    maintype, subtype = mime_type.split("/", 1)
    with path.open("rb") as fp:
        msg.add_attachment(
            fp.read(),
            maintype=maintype,
            subtype=subtype,
            filename=path.name,
        )

attach_file(msg, "weekly-report.xlsx")

발송 전에 파일 존재 여부와 크기를 검사하십시오. 메일 서비스의 첨부 용량 제한을 넘으면 서버가 거절하거나 전달 과정에서 실패할 수 있습니다. 중요한 원본은 그대로 첨부하지 말고, 발송용 복사본을 만든 뒤 개인정보나 불필요한 시트가 남아 있지 않은지도 확인하는 편이 안전합니다.

여러 사람에게 안전하게 발송하기

대량 발송은 단순히 수신자 목록을 To에 넣는 작업이 아닙니다. 그렇게 하면 모든 수신자의 주소가 서로에게 노출될 수 있습니다. 개인화된 업무 알림이라면 수신자별로 새 EmailMessage를 만들고 개별 전송하는 것이 기본입니다.

import time

recipients = [
    {"email": "a@example.com", "name": "김담당"},
    {"email": "b@example.com", "name": "이담당"},
]

for person in recipients:
    msg = EmailMessage()
    msg["Subject"] = "업무 알림"
    msg["From"] = smtp_user
    msg["To"] = person["email"]
    msg.set_content(f"{person['name']}님, 확인 부탁드립니다.")
    smtp.send_message(msg)
    time.sleep(1)

실제 발송 간격과 하루 허용량은 메일 사업자의 정책에 맞춰야 합니다. 수백·수천 건의 홍보성 메일은 개인 SMTP 계정과 표준 라이브러리만으로 운영하기보다, 수신 동의·수신 거부·반송 관리 기능이 있는 전문 발송 서비스를 검토해야 합니다. 이 글의 방식은 승인된 사내 알림이나 소규모 업무 자동화에 적합합니다.

항목 권장 방식 피해야 할 방식
수신자 주소 수신자별 개별 메시지 전체 주소를 To에 노출
발송 속도 정책에 맞춘 간격과 제한 제한 없는 연속 발송
인증 정보 환경 변수·비밀 저장소 소스 코드에 비밀번호 작성
실패 처리 주소별 결과와 원인 기록 전체 성공으로 간주
재시도 일시 오류만 제한적으로 인증 오류도 무한 반복

SMTP 오류 처리와 재시도 설계

메일 전송 실패는 모두 같은 원인이 아닙니다. 인증 실패, 수신자 거부, 서버 연결 종료, 메시지 데이터 거부를 구분해야 합니다. 파이썬은 SMTPAuthenticationError, SMTPRecipientsRefused, SMTPServerDisconnected, SMTPDataError 등의 예외를 제공합니다.

import smtplib
import time

def send_with_retry(smtp, msg, max_attempts=3):
    for attempt in range(1, max_attempts + 1):
        try:
            smtp.send_message(msg)
            return True
        except smtplib.SMTPRecipientsRefused:
            return False  # 주소 수정 전에는 재시도 의미가 적음
        except (smtplib.SMTPServerDisconnected, TimeoutError):
            if attempt == max_attempts:
                raise
            time.sleep(2 ** attempt)
    return False

위 코드는 원리를 보여주는 단순 예시입니다. 서버 연결이 끊어졌다면 같은 객체로 바로 재시도하지 말고 연결을 새로 생성하는 구조가 필요합니다. 성공·실패 여부, 수신자, 시도 횟수, 오류 종류, 처리 시간을 로그나 데이터베이스에 기록하면 중복 발송을 방지하고 실패 건만 다시 처리할 수 있습니다.

실수 방지 체크리스트

  • SMTP 호스트·포트·암호화 방식을 공식 안내와 대조했는지 확인합니다.
  • 비밀번호와 앱 비밀번호가 코드·로그·Git 기록에 포함되지 않았는지 확인합니다.
  • 테스트 수신자 한 명으로 한글 제목과 본문을 먼저 검증합니다.
  • 첨부파일 경로, 파일명, MIME 유형, 용량을 검사합니다.
  • 수신자별 개별 메시지를 생성해 주소 노출과 잘못된 개인화를 막습니다.
  • 발송 제한과 간격을 메일 서비스 정책에 맞춥니다.
  • 영구 오류와 일시 오류를 구분하고 무한 재시도를 금지합니다.
  • 작업 고유 번호를 기록해 같은 메일이 중복 발송되지 않게 합니다.
  • 홍보성 메일은 수신 동의와 수신 거부 등 관련 법규·서비스 정책을 별도로 확인합니다.

자주 묻는 질문

1. smtplib만으로 첨부파일을 보낼 수 있나요?

전송 자체는 smtplib가 담당하지만, 첨부파일이 포함된 MIME 메시지를 안정적으로 구성하려면 email 패키지의 EmailMessage를 함께 사용하는 것이 좋습니다.

2. 465번 포트와 587번 포트 중 무엇을 써야 하나요?

일반적으로 465는 처음부터 SSL 연결, 587은 연결 후 STARTTLS 전환에 사용되지만 서비스 설정이 우선입니다. 이용 중인 메일 사업자의 공식 안내값을 그대로 적용하십시오.

3. 로그인 비밀번호를 사용하면 되나요?

서비스에 따라 일반 비밀번호 로그인을 차단하고 앱 비밀번호나 별도 인증을 요구합니다. 계정 보안 설정과 SMTP 인증 정책을 확인해야 합니다.

4. 한글 제목이나 파일명이 깨지는 이유는 무엇인가요?

EmailMessage가 아닌 문자열 조립 방식, 잘못된 MIME 헤더, 오래된 예제 코드가 원인일 수 있습니다. 최신 EmailMessage API로 작성하고 실제 수신 환경에서 시험하십시오.

5. 여러 수신자를 Bcc에 넣으면 충분한가요?

단순 공지에는 주소 노출을 줄일 수 있지만 개인화, 개별 실패 기록, 재발송 관리가 어렵습니다. 업무 자동화에서는 수신자별 개별 메시지가 더 안전합니다.

6. 첨부파일 용량 제한은 파이썬에서 정해지나요?

주된 제한은 발신·수신 메일 서비스 정책에 따라 결정됩니다. 파일의 인코딩 과정에서 실제 전송 크기가 늘 수 있으므로 서비스 제한보다 여유 있게 관리하십시오.

7. 발송 성공이면 상대방이 받은 것인가요?

SMTP 서버가 메시지를 받아들였다는 뜻이지 최종 받은편지함 도착을 보장하지는 않습니다. 이후 반송, 스팸 분류, 수신 서버 거절이 발생할 수 있습니다.

8. 실패한 주소만 다시 보내려면 어떻게 하나요?

수신자별 상태와 오류 종류를 저장하고, 성공 건은 제외한 뒤 일시 오류에 대해서만 제한된 횟수로 재시도해야 합니다.

9. HTML 메일에는 텍스트 본문이 필요 없나요?

텍스트 버전을 먼저 넣고 HTML 대체 본문을 추가하는 방식이 호환성과 접근성 측면에서 좋습니다.

10. 마케팅 이메일도 이 코드로 대량 발송해도 되나요?

기술적으로 발송 가능하더라도 수신 동의, 수신 거부, 발송량 제한, 반송 처리와 관련 법규를 충족해야 합니다. 대규모 홍보 발송은 전문 서비스를 사용하는 편이 적합합니다.

공식 출처

공식 문서 확인일: 2026년 8월 30일. 실제 SMTP 주소, 포트, 인증 및 발송 제한은 이용 중인 메일 서비스의 최신 공식 안내를 추가로 확인하시기 바랍니다.

본문 하단 광고 구역