email 자동화
❤️ Python3.7, AWS lambda, S3 사용 기준으로 작성하였습니다.
배경
회사에서 업무하다보면 타부서에서 반복적인 데이터 추출 요청이 들어오곤 했다.
그럴때마다 헉 .. 휴가때는 어떻게 하지 🤔 …. 당황하기도 하고 매일 같은 시간에 기계처럼 코드 돌려서 메일에 첨부드리는 것이 비효율적으로 느껴졌다. 살짝 귀찮기도
따라서, 이사님의 조언으로 AWS Lambda 그리고 S3 두 서비스를 이용하여 email 발송 자동화 코드를 만들게 되었다.
들어가기 전, 배경지식 🍦
SMTP
Simple Mail Transfer Protocol
인터넷 메일의 송신(보낼 때)에 사용되는 네트워크 프로토콜
포트는 주로 25번을 사용한다. Email 송신 시 데이터는 송신자의 SMTP 서버를 타고 수신자의 SMTP 서버로 보내진 뒤 메일 박스에 저장 된다.
메일박스란 메일 서버 안에 있는 개인용 폴더를 말하는데, 인터넷에 설치된 사서함과 같은 역할을 한다.
즉, 송신되어 온 메일은 받는 사람의 사서함에 저장되는데, 실제로 그 사용자가 수취할 때까지 저장되어 있다는 것이다.
<그림 한 장으로 보는 최신 네트워크 용어 해설 (정보문화사), P.202>
MIME
Multipurpose Internet Mail Extensions
인터넷 메일에서는 ASCII 문자밖에 다룰 수 없다. 이것을 확장하여 한국어와 같은 2바이트 문자 취급이나 전자메일에 파일 첨부가
가능하도록 하기 위한 규격이 MIME이다.
전자메일 본문을 여러 파트로 나눠서 파트마다 ‘Content-Type’과 같은 각종 헤더 정보를 추가하여 거기에 ASCII 문자로 변환한 바이너리 데이터를 저장.
데이터 -> ASCII 문자로 변환하는 데 주로 Base64 라는 방법이 쓰인다. 어떤 정보를 사용했는지는 MIME가 덧붙이는 헤더 정보에 기술되어 있으며
수신측에서는 이 헤더 정보를 바탕으로 데이터를 원래의 파일로 복원한다.
<그림 한 장으로 보는 최신 네트워크 용어 해설 (정보문화사), P.220 요약>
SSL
Secure Sockets Layer
Netscape Communications사가 개발한 암호화 프로토콜.
네트워크에서 서로를 인증함으로써 ‘가장’을 막고 통신 데이터를 암호화함으로써 데이터의 ‘도청’이나 ‘변조’를 막는다.
<그림 한 장으로 보는 최신 네트워크 용어 해설 (정보문화사), P.212 요약>
파이프라인
프로젝트 폴더
Project
|__init__.py
|runcode.py
|code
|__init__.py
|ExtractData.py
|SendMail.py
|helper
|__init__.py
|delete_customer.py
|ReadDB.py
본격적으로, 코딩 🔥
Import Library
- Pandas : DataFrame 다루기 위하여
- boto3 : S3 서비스를 사용하기 위함
- io : 문제점 > 첫째, S3에 Excel 형식으로 저장하기 를 참고
- email : Header, Mime 사용 (텍스트 -> byte 변환)
- smtplib : SMTP, SSL, TLS 사용 (SMTP 프로토콜 사용)
- os : 환경변수 load (.env)
SendMail.py 작성
이미 DB 에서 불러와 전처리를 마친 Excel 파일이 스냅샷으로 S3에 저장되있다고 가정.
Lambda의 환경변수 로드는 어렵지 않다. 구성 > 환경변수 섹션에서 편집을 눌러 설정할 수 있다.
로컬이 아닌 EC2 인스턴스와 같은 클라우드 서버를 사용하여 SMTP 서버를 세팅할 수 있다.
파일 첨부하지 않을 때
import smtplib
from email.mime.text import MIMEText
from email.header import Header
# 환경변수
driver = os.getenv('DRIVER')
host = os.getenv('HOST')
port = os.getenv('PORT')
user = os.getenv('USER')
pwd = os.getenv('PWD')
# SMTP 연결
s = smtplib.SMTP(
host=host,
port=port
)
# SSL 혹은 TLS 보안 설정 후 로그인
s.starttls()
s.login(user, pwd)
# 내용 작성 : MIME을 사용하여 ASCII로 변환
msg = MIMEText(_text='{본문내용}')
msg['Subject'] = Header(s='{제목}', charset='utf-8')
from_addr='{수신메일주소}'
to_addr='{발송메일주소}'
# SMTP에 내용 실어서 보내기 (msg 에는 이미 ASCII로 변환된 텍스트 문자가 메모리 상에 있을 것)
s.sendmail(
from_addr,
to_addr,
msg.as_string()
)
s.quit()
파일 첨부할 때
사용 라이브러리가 살짝 달라진다. 자세한 내용은 아래의 문제점 > 셋째, 파일첨부 를 참고
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
import boto3
# 환경변수
driver = os.getenv('DRIVER')
host = os.getenv('HOST')
port = os.getenv('PORT')
user = os.getenv('USER')
pwd = os.getenv('PWD')
# SMTP 연결
s = smtplib.SMTP(
host=host,
port=port
)
# SSL 혹은 TLS 보안 설정 후 로그인
s.starttls()
s.login(user, pwd)
# 아래 부분이 달라진다.
msg = MIMEMultipart()
msg['Subject'] = '메일 제목'
msg.attach(MIMEText('메일 본문'))
s3 = boto3.resource('s3')
bucket = 'your_bucket'
key = 'your_key/excel.xlsx'
obj = s3.Object(bucket, key)
attachment = obj.get()['Body'].read()
part = MIMEApplication(attachment, Name='메일 첨부파일에 표기될 파일명')
msg.attach(part)
# 달라짐 <끝>
# SMTP에 내용 실어서 보내기 (msg 에는 이미 ASCII로 변환된 텍스트 문자가 메모리 상에 있을 것)
s.sendmail(
from_addr,
to_addr,
msg.as_string()
)
s.quit()
문제점
첫째, S3에 Excel 형식으로 저장하기
메모리에 저장된 Pandas DataFrame 을 로컬에 특정한 저장 없이 S3에 저장하는 데 어려움을 겪었다 ..^^;
AWS Lambda에 코드 올릴 것이기 때문에 이러한 문제점을 해결해야했다!
🔎 해결책 io.BytesIO() 사용
1)io library를 사용하여 인 메모리 바이너 스트림을 Byte 객체로 변환 (🤔 더 공부 필요한 부분, io에 관하여 잘 모르겠다.)
2)pd.ExcelWriter 파일 경로 대신 io.BytesIO 지정
3)getvalue() : io library 로 버퍼의 전체 내용을 포함하는 bytes를 반환
4)DataFrame -> Excel -> output (세션 바깥에 열려 있으므로)
구글링하며 찾은 정보라 조금 더 공부가 필요한 부분임 ! (io 는 무엇인지? 스트림 객체란?)
# save in S3
import io
import boto3
import pandas as pd
bucket = {bucket_name}
with io.BytesIO as output:
with pd.ExcelWriter(output, engine='xlsxwriter') as writer:
{DF}.to_excel(writer)
data = output.getvalue()
s3_resource = boto3.resource('s3')
s3_resource.Object(bucket, '{bucket_key}/file_name.xlsx').put(Body=data)
Reference
둘째, AWS Lambda Library 추가
AWS의 Lambda 서비스는 기본 라이브러리만 제공하고 있다. boto3와 같은 자기네 서비스 라이브러리는 그냥 제공하는 듯하다.
위의 코드 그대로 돌리면 이미 세팅된 환경에 따라 다른데 아마 pandas와 xlsxwriter을 읽지 못할 것이다.
🔎 해결책 Layer 추가
AWS Lambda에 Layer를 추가한다. 아래 블로그를 참고해 레이어 추가를 하였다.
AWS Lambda Layer 추가
셋째, 파일 첨부
MIMEText 선언 후 bytes로 변환된 excel 파일을 attach 하면 Cannot attach additional subparts to non-multipart/*
에러를 리턴한다. 당연하다. 나 텍스트 뿐 아니라 Multipart 쓸거야 하고 전달해줘야한다. 역시 삽질해야 얻는 것이 많다
🔎 MIMEMultipart 사용
MIMEMultipart를 사용한 인스턴스에 attach 메서드를 사용하여 Excel(bytes) 와 Text를 입력한다.
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
import boto3
# 상세 코드 생략
msg = MIMEMultipart()
msg['Subject'] = '메일 제목'
msg.attach(MIMEText('메일 본문'))
# File Read
s3 = boto3.resource('s3')
bucket = 'your_bucket'
key = 'your_key/excel.xlsx'
obj = s3.Object(bucket, key)
attachment = obj.get()['Body'].read()
part = MIMEApplication(attachment, Name='메일 첨부파일에 표기될 파일명')
msg.attach(part)
# .... 이하 생략
상세 코드
당연 문제될 내용 모두 마스킹 한 깃허브 코드이다 :)