파이썬으로 구현하는 이메일 발송 자동화 완벽 가이드

매달 거래처나 고객들에게 안내 메일을 보내야 하는데, 수신자가 50명이 넘어가니 일일이 보내는 게 여간 번거로운 일이 아니었어요. 복사 붙여넣기만 해도 30분은 훌쩍 지나가고, 가끔은 실수로 이름을 잘못 적거나 누락되는 경우도 생겼죠. 그래서 이참에 파이썬으로 이메일 자동화 스크립트를 직접 만들어보기로 했어요.


사무실에서 컴퓨터로 이메일을 하나씩 보내는 직장인의 모습


기본 이메일 발송 스크립트 만들기


먼저 파이썬에 기본으로 들어있는 smtplib를 사용했어요. Gmail 계정으로 테스트했는데, 보안 설정에서 앱 비밀번호를 발급받아야 했어요. Gmail 설정에서 2단계 인증을 켜고, 앱 비밀번호를 새로 만들면 돼요.


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

# 계정 정보 설정
sender_email = "your_email@gmail.com"
sender_password = "your_app_password"
smtp_server = "smtp.gmail.com"
smtp_port = 587

# 수신자 정보
receiver_email = "receiver@example.com"
subject = "자동화 테스트 메일"
body = "안녕하세요! 이 메일은 Python으로 자동 발송된 메일입니다."

# 이메일 구성
msg = MIMEText(body, "plain", "utf-8")
msg["From"] = sender_email
msg["To"] = receiver_email
msg["Subject"] = subject

# 서버 연결 및 발송
try:
    server = smtplib.SMTP(smtp_server, smtp_port)
    server.starttls()
    server.login(sender_email, sender_password)
    server.sendmail(sender_email, receiver_email, msg.as_string())
    print("메일이 성공적으로 전송되었습니다!")
except Exception as e:
    print(f"오류 발생: {e}")
finally:
    server.quit()


네이버 메일을 쓰신다면 smtp.naver.com으로 바꾸고, 환경설정에서 POP3/SMTP 사용을 허용하면 돼요. Outlook은 smtp.office365.com에 포트 587을 사용하면 됩니다.


Jinja2로 개인화된 HTML 이메일 만들기


단순한 텍스트 메일보다는 예쁜 HTML 메일을 보내고 싶었어요. Jinja2 템플릿 엔진을 사용하면 각 수신자별로 맞춤형 내용을 쉽게 만들 수 있어요.


먼저 HTML 템플릿 파일을 만들어요:


<!DOCTYPE html>
<html>
<body>
    <p>안녕하세요, {{ name }}님!</p>
    <p>이번 달 특별 혜택을 확인하세요: <b>{{ offer }}</b></p>
    <p>감사합니다.<br/>마케팅팀 드림</p>
</body>
</html>

그리고 Python 코드에서 이렇게 사용해요:


import pandas as pd
from jinja2 import Template
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

# 수신자 정보 읽기
df = pd.read_csv("contacts.csv")

# 템플릿 읽기
with open("template.html", "r", encoding="utf-8") as f:
    html_template = f.read()
template = Template(html_template)

# SMTP 서버 연결
server = smtplib.SMTP("smtp.gmail.com", 587)
server.starttls()
server.login(from_addr, password)

# 반복문으로 개인화 메일 발송
for _, row in df.iterrows():
    html_content = template.render(name=row['이름'], offer=row['offer'])
    
    msg = MIMEMultipart()
    msg["From"] = from_addr
    msg["To"] = row['이메일']
    msg["Subject"] = "개인화 혜택 안내"
    msg.attach(MIMEText(html_content, "html"))
    
    server.sendmail(from_addr, row['이메일'], msg.as_string())

server.quit()


이미지와 첨부파일 추가하기


청구서나 카탈로그를 첨부하거나, 본문에 이미지를 넣어야 할 때가 많았어요. MIMEMultipart를 활용하면 여러 파트를 조합해서 보낼 수 있어요.


from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email import encoders

# 메일 객체 생성
msg = MIMEMultipart()
msg["From"] = "보내는사람@example.com"
msg["To"] = "받는사람@example.com"
msg["Subject"] = "파일 첨부 및 이미지를 포함한 이메일"

# 텍스트 본문 추가
body = "이메일 본문 텍스트"
msg.attach(MIMEText(body, "plain"))

# 이미지를 본문에 삽입
with open("image.png", "rb") as f:
    img = MIMEImage(f.read())
    img.add_header('Content-ID', '<my_image>')
    msg.attach(img)

# HTML 본문에 이미지 표시
html = '<html><body>이미지: <img src="cid:my_image"></body></html>'
msg.attach(MIMEText(html, "html"))

# PDF 첨부파일 추가
filename = "첨부파일.pdf"
with open(filename, "rb") as attachment:
    part = MIMEBase("application", "octet-stream")
    part.set_payload(attachment.read())
    encoders.encode_base64(part)
    part.add_header("Content-Disposition", 
                    f'attachment; filename="{filename}"')
    msg.attach(part)


대량 발송 시 실패 관리와 재시도


수백 명에게 보내다 보면 네트워크 오류나 잘못된 이메일 주소 때문에 실패하는 경우가 생겨요. 그래서 실패 건을 따로 관리하고 재시도하는 로직을 추가했어요.


import time
import logging
from datetime import datetime

# 로그 설정
logging.basicConfig(filename='email_log.txt', level=logging.INFO)

def send_email_with_retry(recipient, subject, body, max_retries=3):
    for attempt in range(max_retries):
        try:
            msg = MIMEText(body, "plain", "utf-8")
            msg["To"] = recipient
            msg["Subject"] = subject
            
            server.sendmail(sender_email, recipient, msg.as_string())
            logging.info(f"[{datetime.now()}] SUCCESS: {recipient}")
            return True
            
        except Exception as e:
            logging.error(f"[{datetime.now()}] FAILURE: {recipient} - {e}")
            if attempt < max_retries - 1:
                time.sleep(2)  # 2초 대기 후 재시도
            else:
                return False

# 대량 발송 실행
failed_list = []
for email in email_list:
    if not send_email_with_retry(email, subject, body):
        failed_list.append(email)

# 실패 목록 저장
with open("failed_emails.txt", "w") as f:
    f.write("\n".join(failed_list))


정해진 시간에 자동 실행하기


스크립트는 완성했는데, 매번 수동으로 실행하는 것도 귀찮더라고요. 운영체제별로 스케줄러를 설정하는 방법이 달라요.


리눅스에서는 crontab을 사용해요:


# crontab -e 명령으로 편집
# 매일 자정에 실행
0 0 * * * /usr/bin/python3 /home/user/email_script.py

# 매주 월요일 오전 9시에 실행
0 9 * * 1 /usr/bin/python3 /home/user/email_script.py


윈도우는 작업 스케줄러에서 GUI로 설정하면 돼요. 작업 스케줄러를 열고, 새 작업 만들기를 선택한 다음, 트리거에서 원하는 시간을 설정하고, 동작에서 파이썬 실행 파일과 스크립트 경로를 지정하면 끝이에요.


발송 결과 DB 저장과 슬랙 알림


이메일 발송 결과를 체계적으로 관리하고 싶어서 데이터베이스에 저장하고, 실패가 발생하면 슬랙으로 알림을 받도록 했어요.


import sqlite3
import requests
from datetime import datetime

# DB 연결 및 테이블 생성
conn = sqlite3.connect('email_log.db')
cursor = conn.cursor()
cursor.execute('''
    CREATE TABLE IF NOT EXISTS email_log (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        recipient TEXT,
        subject TEXT,
        status TEXT,
        error_msg TEXT,
        sent_at TIMESTAMP
    )
''')

def log_email_result(recipient, subject, status, error_msg=None):
    cursor.execute('''
        INSERT INTO email_log (recipient, subject, status, error_msg, sent_at)
        VALUES (?, ?, ?, ?, ?)
    ''', (recipient, subject, status, error_msg, datetime.now()))
    conn.commit()

def send_slack_notification(message):
    webhook_url = "https://hooks.slack.com/services/xxx/yyy/zzz"
    data = {"text": message}
    requests.post(webhook_url, json=data)

# 이메일 발송 후 결과 기록
try:
    # 발송 코드
    log_email_result(recipient, subject, "SUCCESS")
except Exception as e:
    log_email_result(recipient, subject, "FAILURE", str(e))
    send_slack_notification(f"이메일 발송 실패: {recipient} - {str(e)}")


발송 속도 조절과 큐 시스템


Gmail이나 네이버 같은 메일 서비스는 시간당 발송 제한이 있어요. Gmail은 일반 계정 기준으로 하루 500건, 시간당 20건 정도의 제한이 있죠. 그래서 발송 속도를 조절하는 코드를 추가했어요.


import time
from collections import deque

class EmailQueue:
    def __init__(self, rate_limit=20, time_window=3600):
        self.queue = deque()
        self.rate_limit = rate_limit
        self.time_window = time_window
        self.sent_times = deque()
    
    def can_send(self):
        now = time.time()
        # 시간 창을 벗어난 기록 제거
        while self.sent_times and self.sent_times[0] < now - self.time_window:
            self.sent_times.popleft()
        
        return len(self.sent_times) < self.rate_limit
    
    def send_email(self, email_data):
        while not self.can_send():
            time.sleep(60)  # 1분 대기
        
        # 실제 발송 코드
        # ...
        
        self.sent_times.append(time.time())

# 사용 예시
email_queue = EmailQueue(rate_limit=20, time_window=3600)
for email in email_list:
    email_queue.send_email(email)


다른 언어로도 구현 가능해요


파이썬 말고 Node.js를 쓰신다면 Nodemailer를 사용하면 돼요:


const nodemailer = require('nodemailer');
const fs = require('fs');

const transporter = nodemailer.createTransporter({
    service: 'gmail',
    auth: {
        user: 'your@gmail.com',
        pass: 'yourpassword'
    }
});

// 발송 및 로그 기록
transporter.sendMail(mailOptions, function(error, info){
    const log = `[${new Date().toISOString()}] to="${mailOptions.to}" result=${error ? 'FAILURE' : 'SUCCESS'}\n`;
    fs.appendFileSync('email_log.txt', log);
});


AWS SES나 SendGrid 같은 전문 서비스 활용


대량 발송이 자주 필요하다면 AWS SES나 SendGrid 같은 전문 서비스를 쓰는 것도 좋아요. API로 쉽게 연동할 수 있고, 발송 통계나 반송 처리도 자동으로 해줘요.


# SendGrid 예시
import sendgrid
from sendgrid.helpers.mail import Mail

sg = sendgrid.SendGridAPIClient(api_key='your_api_key')
message = Mail(
    from_email='from@example.com',
    to_emails='to@example.com',
    subject='Sending with SendGrid',
    html_content='<strong>Hello, World!</strong>'
)
response = sg.send(message)


노트북에서 여러 이메일이 자동으로 빠르게 발송되는 모습


이렇게 직접 스크립트를 만들어 쓰니까 정말 편해졌어요. 처음엔 코드 짜는 게 번거로울 것 같았는데, 한 번 만들어두니 매달 30분씩 걸리던 작업이 클릭 한 번으로 끝나더라고요. 실수로 누락되는 일도 없고, 새벽이든 주말이든 예약 발송도 가능해서 시간 활용도 자유로워졌어요.


특히 발송 결과를 데이터베이스에 저장해두니까 나중에 통계 내기도 쉽고, 어떤 메일이 잘 읽히는지 분석할 수도 있었어요. 슬랙 알림까지 연동하니까 실패 건도 바로바로 확인할 수 있고요.


이메일 자동화가 필요하신 분들은 간단한 스크립트부터 시작해보시는 것도 좋을 것 같아요. 생각보다 어렵지 않고, 한 번 익혀두면 여러 곳에 응용할 수 있거든요.


파이썬 openpyxl로 엑셀 파일 자동 저장하는 방법