파이썬으로 첨부파일과 HTML 메일 보내기
매달 말이면 비슷한 일을 하시죠. 보고서 엑셀 만들고, 메일 새로 쓰고, 폴더에서 파일 찾아 첨부하고, 받는 사람 주소 다시 확인하고.
한 번에 5분이지만 매달이잖아요.
게다가 지난 글에서 파이썬으로 보낸 메일은 글자만 덩그러니 있어서 좀 허전했고요. 표도 없고, 굵은 글씨도 없고.
지난 글 코드에서 바뀌는 건 딱 한 군데
먼저 안심하고 시작하셔도 돼요. 오늘 새로 배울 게 거의 없거든요.
지난 글의 3단계 기억하시죠. 편지 쓰기 → 우체국 접속하고 로그인 → 부치기. 이 중에서 접속·로그인·발송은 오늘 한 글자도 안 바뀝니다. 바뀌는 건 편지 내용을 채우는 부분뿐이에요.
봉투 안에 서류를 같이 넣는 법(첨부), 그리고 편지지를 인쇄물처럼 꾸미는 법(HTML). 오늘은 이 두 덩어리입니다.
파이썬 첨부파일 붙이기 — add_attachment 한 줄
import smtplib
from email.message import EmailMessage
msg = EmailMessage()
msg["Subject"] = "7월 실적 보고서 보내드립니다"
msg["From"] = "mysender@gmail.com"
msg["To"] = "mysender@gmail.com"
msg.set_content("7월 실적 보고서를 첨부합니다. 확인 부탁드립니다.")
# 여기부터가 오늘의 새 부분
with open("보고서.xlsx", "rb") as f: # rb = 바이너리로 읽기
msg.add_attachment(
f.read(), # 파일 내용을 통째로
maintype="application", # 파일의 큰 분류
subtype="octet-stream", # 세부 분류 (모르면 이걸로)
filename="7월_실적보고서.xlsx" # 받는 분 화면에 보일 이름
)
with smtplib.SMTP("smtp.gmail.com", 587) as server:
server.starttls()
server.login("mysender@gmail.com", "앱비밀번호16자리") # 지난 글의 그 16자리
server.send_message(msg)
print("첨부 메일 발송 완료!")
# 실행 결과:
# 첨부 메일 발송 완료!
받은편지함을 열어보세요. 메일 옆에 클립 아이콘이 붙어 있으면 성공이에요.
여기서 꼭 짚고 갈 게 "rb"입니다. 앞에서 텍스트 파일 읽을 때는 open(파일, "r")을 썼잖아요. 그 습관 그대로 엑셀을 열면 이렇게 터집니다.
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x50 ...
엑셀·PDF·이미지는 글자로 된 파일이 아니라 0과 1 덩어리거든요. 파이썬이 그걸 한글로 읽어보려다 포기한 거예요.
글자 아닌 파일은 무조건 "rb". 이것만 외우시면 됩니다.
그리고 filename은 받는 분 화면에 보일 이름이라 원본과 달라도 돼요. 내부 파일명이 tmp_0731.xlsx여도 2026년 7월 보고서.xlsx로 보낼 수 있습니다. 은근히 실무에서 유용하더라고요.
maintype·subtype이 뭐죠 (python email mime 아주 짧게)
이 두 줄에서 겁먹고 멈추시는 분이 많은데요.
모르겠으면 그냥 application / octet-stream 쓰세요. 엑셀, PDF, 한글, zip 전부 이걸로 잘 갑니다. 저도 거의 이것만 씁니다.
이게 "이건 엑셀이에요, 저건 그림이에요"를 메일 프로그램에 알려주는 꼬리표인데, 메일에 파일을 실어 보내는 국제 규칙 이름이 MIME이에요. python email mime이라고 검색하면 나오는 게 이 얘기입니다. 이름만 알고 넘어가셔도 충분해요. 굳이 구분하고 싶으시면 이미지는 image/png, image/jpeg 정도만.
파일 여러 개 한 번에 첨부하기
add_attachment()를 여러 번 부르면 여러 개가 붙어요. 그러니 반복문이면 끝납니다.
from pathlib import Path # 앞에서 배운 경로 도구
첨부파일들 = ["보고서.xlsx", "요약.pdf", "그래프.png"]
for 파일 in 첨부파일들: # 파일 하나씩
경로 = Path(파일)
with open(경로, "rb") as f: # 여기도 당연히 rb
msg.add_attachment(
f.read(),
maintype="application",
subtype="octet-stream",
filename=경로.name # 폴더 경로 빼고 파일 이름만
)
print(f"{len(첨부파일들)}개 파일 첨부 완료")
# 실행 결과:
# 3개 파일 첨부 완료
경로.name이 포인트예요. C:/보고서/7월/실적.xlsx에서 실적.xlsx만 쏙 뽑아줍니다. 전체 경로를 그냥 넣으면 받는 분 화면에 C_Users_...실적.xlsx 같은 흉한 이름으로 떠요.
리스트 대신 Path("첨부폴더").glob("*.xlsx")를 넣으면 폴더 안 엑셀이 전부 자동으로 붙습니다.
한 가지만 주의하세요. 봉투가 너무 두꺼우면 우체국이 안 받아주듯, 지메일은 첨부 총합 25MB까지예요. 사진 폴더 통째로 첨부하시면 거의 100% 막힙니다. 큰 파일은 클라우드에 올리고 링크를 본문에 넣는 게 정석이에요.
HTML 이메일 보내기 — 표와 굵은 글씨
여기서부터 두 번째 덩어리입니다. 첨부는 잠깐 내려놓으셔도 돼요.
텍스트 메일이 급하게 쓴 손글씨 쪽지라면, HTML 메일은 제목이 크고 표가 들어간 인쇄물 안내문이에요. 내용은 같아도 받는 사람이 느끼는 무게가 다르거든요.
msg = EmailMessage()
msg["Subject"] = "[안내] 7월 정기 회의"
msg["From"] = "mysender@gmail.com"
msg["To"] = "mysender@gmail.com"
# 1) 글자 버전 먼저
msg.set_content("7월 정기 회의는 25일 오후 3시 대회의실입니다.")
# 2) HTML 버전 나중에
html = """
<html>
<body style="font-family:맑은 고딕; color:#333333;">
<h2 style="color:#2c5aa0;">7월 정기 회의 안내</h2>
<p>7월 정기 회의 일정을 아래와 같이 공유드립니다.<br>
참석 여부는 <b>23일까지</b> 회신 부탁드립니다.</p>
</body>
</html>
"""
msg.add_alternative(html, subtype="html") # subtype 빼면 태그가 글자로 보임!
왜 본문을 두 번 넣냐고요? 요즘 메일 앱은 HTML을 잘 보여주지만, 아주 옛날 프로그램이나 스마트워치 알림에서는 HTML이 안 보이거든요. 전시장에 그림을 걸면서 옆에 설명 팻말도 같이 붙여두는 것과 같아요. 보통은 그림(HTML)을 보지만, 안 보이는 환경이면 팻말(텍스트)을 읽는 거죠.
순서는 텍스트 먼저, HTML 나중. 이건 그냥 패턴으로 외우세요. 뒤집으면 밋밋한 텍스트가 먼저 보일 수 있어요.
태그는 다섯 개만 아시면 됩니다. <h2> 큰 제목, <p> 문단, <b> 굵게, <br> 줄바꿈, <table> 표. HTML을 처음부터 배우는 글이 아니라, 복붙해서 글자만 고쳐 쓰는 글이니까요.
제일 많이 막히는 부분: 이메일 HTML은 인라인 스타일만 먹습니다
오늘 글에서 딱 하나만 가져가신다면 이 부분이에요.
저도 처음엔 웹에서 하던 대로 <style> 태그에 스타일을 예쁘게 몰아 썼거든요. 그런데 메일을 열어보니 색도 표 테두리도 다 사라져 있더라고요. 코드는 분명 맞는데요.
# 메일에선 통째로 잘려나갑니다
"""
<style>
h2 { color: blue; }
</style>
<h2>제목</h2>
"""
# 이렇게 태그마다 직접 붙여야 살아남습니다
"""
<h2 style="color:blue;">제목</h2>
"""
수학여행 갈 때 "우리 반 옷은 전부 파란색"이라고 칠판에 써 놔도, 다른 학교 선생님은 그 칠판을 안 보잖아요. 옷 하나하나에 이름표를 붙여야 어디서든 알아보죠.
메일 앱도 똑같아요. 스타일은 태그마다 하나씩 직접 붙여주세요. 몰아 쓰면 사라집니다.
그래서 표를 만들면 이렇게 지저분해 보이는데, 이게 정상이자 정답입니다.
html = """
<html>
<body style="font-family:맑은 고딕;">
<h2 style="color:#2c5aa0;">7월 팀별 실적</h2>
<table style="border-collapse:collapse; width:400px;">
<tr style="background-color:#f0f0f0;"> <!-- tr = 한 줄, th = 제목칸 -->
<th style="border:1px solid #cccccc; padding:8px;">팀</th>
<th style="border:1px solid #cccccc; padding:8px;">실적</th>
</tr>
<tr>
<td style="border:1px solid #cccccc; padding:8px;">영업1팀</td> <!-- td = 내용칸 -->
<td style="border:1px solid #cccccc; padding:8px;">1,200만원</td>
</tr>
</table>
</body>
</html>
"""
msg.add_alternative(html, subtype="html") # 받은편지함에 표가 그려집니다
칸마다 style이 반복되는 게 답답하시죠. 근데 몰아 쓰면 메일에선 그게 사라져요. 요즘 웹에서 쓰는 flex나 grid도 메일에선 잘 깨지니 표 배치가 제일 안전합니다.
실전: 월간 보고서 엑셀 첨부 + HTML 요약 메일
이제 두 덩어리를 한 통에 담아볼게요. 순서만 고정하시면 됩니다. 텍스트 → HTML → 첨부 → 발송.
import smtplib
from pathlib import Path
from email.message import EmailMessage
from datetime import datetime
이번달 = datetime.now().strftime("%Y년 %m월") # 예: 2026년 07월
msg = EmailMessage()
msg["Subject"] = f"[월간보고] {이번달} 실적 보고서"
msg["From"] = "mysender@gmail.com"
msg["To"] = "boss@example.com"
# 1) 텍스트 본문 (예비용)
msg.set_content(f"{이번달} 실적 보고서를 첨부합니다.")
# 2) HTML 본문 (실제로 보이는 쪽)
msg.add_alternative(f"""
<html>
<body style="font-family:맑은 고딕; color:#333333;">
<h2 style="color:#2c5aa0;">{이번달} 실적 보고</h2>
<p>{이번달} 실적을 정리해 보내드립니다.<br>
자세한 내용은 <b>첨부된 엑셀 파일</b>을 확인해 주세요.</p>
</body>
</html>
""", subtype="html")
# 3) 첨부파일은 맨 마지막에
보고서 = Path("월간실적.xlsx")
with open(보고서, "rb") as f: # 잊지 마세요, rb
msg.add_attachment(f.read(),
maintype="application",
subtype="octet-stream",
filename=보고서.name)
with smtplib.SMTP("smtp.gmail.com", 587) as server:
server.starttls()
server.login("mysender@gmail.com", "앱비밀번호16자리")
server.send_message(msg)
print(f"{이번달} 보고 메일 발송 완료!")
# 실행 결과:
# 2026년 07월 보고 메일 발송 완료!
달 이름까지 자동으로 들어가니까, 매달 하던 5분이 실행 한 번으로 끝납니다.
자주 만나는 에러
FileNotFoundError: '보고서.xlsx' — 첨부 파일이 코드와 다른 폴더에 있거나 확장자 오타예요. 윈도우 경로는 Path("C:/작업/보고서.xlsx")처럼 슬래시를 쓰거나 r"C:\작업\보고서.xlsx"로 앞에 r을 붙이세요.
UnicodeDecodeError 또는 TypeError: expected bytes-like object — "r"로 열었다는 뜻입니다. "rb"로 바꾸세요.
<h2>제목</h2>가 화면에 글자로 보임 — subtype="html"을 빠뜨렸어요.
HTML을 넣었더니 앞의 본문이 사라짐 — set_content를 두 번 부르셨을 거예요. set은 새로 설정, add는 덧붙이기. 추가는 add_alternative입니다.
메일이 스팸함으로 감 — HTML에 링크가 잔뜩 있으면 스팸 점수가 올라갑니다. 텍스트 버전을 같이 넣는 게 도움이 돼요.
직접 해보세요
- 예제 1로 본인 주소에 아무 파일이나 하나 첨부해 보내고, 받은편지함에서 열어보기.
- 예제 3을 보내되 일부러
subtype="html"을 빼고 한 번 보내보세요. 태그가 그대로 글자로 보이는 걸 눈으로 확인한 다음 다시 넣어서 보내면, 이 실수는 평생 안 하게 됩니다. - (도전) 마지막 예제의 파일명을 본인 업무 파일로 바꿔서 실제 보고 메일 형태로 완성해보기.
힌트: 첨부 파일이 안 잡히면 코드와 같은 폴더에 파일을 옮겨두고 파일명만 적어보세요. 경로 문제가 대부분이거든요.
이제 파이썬으로 문서를 만들 줄 알고, 메일을 보낼 줄 알고, 파일을 첨부할 줄도 아시게 됐어요. 이 세 개를 붙이면 뭐가 될까요?
엑셀 명단을 읽어서 사람마다 이름이 들어간 공문을 자동으로 만들고, 각자 이메일로 첨부해서 보내는 것. 30명이면 30통이 실행 한 번에 나가는 거예요. 다음 글은 그걸 처음부터 끝까지 만들어보는 미니 프로젝트인데, 오늘 배운 add_attachment 덩어리가 반복문 안으로 들어갈 뿐이에요. 그게 전부입니다.
제 글이 도움이 되셨다면 댓글 & 공감 부탁드려요 😀
'Application > Python' 카테고리의 다른 글
| [Python] 16. 크롤링 기초 - requests와 BeautifulSoup으로 웹 데이터 자동 수집하기 (0) | 2026.07.24 |
|---|---|
| [Python] 15. 파이썬으로 양식 일괄 발송 시스템 만들기 (엑셀 명단 → 개인 문서 → 자동 메일) (0) | 2026.07.22 |
| [Python] 13. 파이썬 이메일 자동 발송: smtplib 기초 (0) | 2026.07.10 |
| [Python] 12. 파이썬으로 Word 문서 자동 생성하기 (python-docx) (0) | 2026.07.06 |
| [Python] 11. 파이썬으로 PDF 텍스트 추출하고 엑셀로 정리하기 (0) | 2026.07.03 |