개발 환경
- 이메일 서버/포트/2차 인증 사용 여부
- SERVER: smart.whoismail.net
- PORT: 587
- 2차 인증 사용 여부: 사용안함
RPA 프로세스
- pymysql connect 로 세션 생성
- pandas 라이브러리로 DB를 조회하여 DataFrame 으로 저장 (DB에 발송할 이메일 주소를 함께 가져오지만, 보안상 간단히 DataFrame 내 'A' Column 으로 가정)
- 발송할 메일 내용이 될 HTML 템플릿을 만든다.
- for loop을 돌면서 HTML 템플릿 내부에 df.to_html() 메서드로 만든 df 테이블을 삽입한다.
- 메일 발송
코드 전문
import os
import datetime
import pymysql # MySQL Server connection
import smtplib
import pandas as pd
from dotenv import load_dotenv # 환경변수 관리 lib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
load_dotenv('.env') # .env 파일 경로
DB_HOST = os.getenv('DB_HOST')
DB_SUPER_USER = os.getenv('DB_SUPER_USER')
DB_SUPER_PW = os.getenv('DB_SUPER_PW')
DB_NAME = os.getenv('DB_NAME')
DB_PORT =os.getenv('DB_PORT')
mail_server:str = 'smart.whoismail.net' # 메일 서버
mail_port:int = 587 # 메일 서버 포트
login_id:str = 'test@test.com' # 발송할 계정의 아이디
login_pw:str = 'test1234' # 발송할 계정의 비밀번호
from_mail_addr:str = login_id # 보내는 사람 = 발송할 계정의 아이디
to_mail_addr:str = "target_email@test.com" # 받는 사람
cc_addr:str = "carbon_copy@test.com" # 참조되는 이메일 주소
session = smtplib.SMTP(host=mail_server, port=mail_port) # smtp 인스턴스(세션) 생성
session.login(user=login_id, password=login_pw) # 로그인
# pymysql 라이브러리로 MySQL 서버 접속
conn = pymysql.connect(host=DB_HOST, user=DB_SUPER_USER,
password=DB_SUPER_PW, port=DB_PORT,
db=DB_NAME)
curs = conn.cursor(pymysql.cursors.DictCursor) # pymysql 인스턴스로부터 cursor 생성
query = 'select A, B, C from TEST;' # select query
df:pd.DataFrame = pd.read_sql(query, conn) # pandas dataframe 생성 (메일에 첨부할 예정)
curs.close()
conn.close()
# 메일에 사용할 HTML 및 HTML head에 CSS 추가 (<body> 태그는 닫지 않음.)
base_html_1 = """
<html>
<head>
<style>
table {
width: 100%;
text-align: center;
line-height: 1.0;
margin: 1px 1px;
}
</style>
</head>
<body>
<p>안녕하세요
<br>메일 본문 테스트 중입니다.</p>"""
# 메일에 사용할 HTML 내 <body> 태그를 닫음.
base_html_2 = """
</body>
</html>"""
for item in df['A']: # df['A']은 발송할 이메일 주소
root_msg = MIMEMultipart('related') # root 메시지는 MultiPart 형식으로 지정
root_msg['Subject'] = 'TEST Mail' # 메일 제목
root_msg['From'] = from_mail_addr # 메일 보내는 사람
to_mail_addr = item
root_msg['To'] = to_mail_addr # 메일 받는 사람
root_msg['Cc'] = cc_addr # 참조할 사람
root_msg.preamble = 'TEST message 입니다.' # 메일 머릿말
alternative_message = MIMEMultipart('alternative')
root_msg.attach(alternative_message) # root 메시지 에 첨부
text_msg = MIMEText('TEST 중 입니다.') # plain text 메시지
alternative_message.attach(text_msg) # alternative_message 에 텍스트 메시지 첨부
# df_msg(DataFrame) --> html 로 변환한 뒤 HTML 중간에 삽입하여 for loop 마다 html 을 생성)
msg_contents = base_html_1 + base_html_client + df[df['B'] == 'Test'].to_html(index=False, justify='center', col_space=100) + base_html_2
# 발송할 메일 본문 내용
text_msg = MIMEText(msg_contents, 'html', _charset='utf-8') # _charset='utf-8' 은 한글 깨짐을 방지
alternative_message.attach(text_msg) # alternative_message(MIMEMultipart)에 HTML로 만든 메일 첨부
# 메일 발송
session.sendmail(
from_mail_addr, # 보내는 사람
[to_mail_addr, cc_addr], # 받는 사람 (참조받는 사람은 메일 내 '받는 사람'란이 아닌 '참조'란에 표시. root_msg['Cc']: 참조란에 이미 들어가있다.)
root_msg.as_string() # root 메시지
)
else: # for loop 종료 후 메일 세션 닫음.
session.quit()
자세히 볼 코드
1. 78번 line (DataFrame --> HTML 변환 시 중앙 정렬)
df[df['B'] == 'Test'].to_html(index=False, justify='center', col_space=100)
여기서 .to_html()의 파라미터들을 보면,
- index=False : DataFrame 내 row의 id에 해당하는 index를 표시하지 않는다.
- justify='center' : html로 변환할 때, DataFrame은 <table> 태그로 변환된다.
DataFrame 에서 하나의 row 는 변환된 <table> 태그 내에서 하나의 <tr> 태그가 되는데,
이 때 style 속성 값을 전달하여 글자를 '중앙 정렬' 한다. - col_space=100 : <tr> 태그의 간격을 html 내 style 속성 값으로 전달한다.
2. 82번 line (글자 깨짐 방지)
text_msg = MIMEText(msg_contents, 'html', _charset='utf-8')
MIMEText() 내 세 번째 파라미터인 _charset='utf-8' 은 html 내 한글이 깨지지 않도록 인코딩하기 위함이다.
3. 85번 line (보내는 사람/받는 사람/참조)
session.sendmail(
from_mail_addr, # 보내는 사람
[to_mail_addr, cc_addr], # 받는 사람 (참조받는 사람은 메일 내 '받는 사람'란이 아닌 '참조'란에 표시. root_msg['Cc']: 참조란에 이미 들어가있다.)
root_msg.as_string() # root 메시지
)
여기서 .sendmail() 의 2번째 파라미터(받는 사람)를 보면 dtype은 list로 전달한다.
잠시 메일 작성할 때 화면을 보면,
위 이미지 처럼 [받는 사람] 과 [참조] 가 있는데, 이는 각각 67, 68번 line의 코드로 생성을 했다.
root_msg['To'] = item # 메일 받는 사람
root_msg['Cc'] = cc_addr # 참조할 사람
이는 비유를 하자면, 편지지에 [받는 사람] 과 [참조]를 기입한 것에 불과하다.
편지지에 기입하는 것과 실제 편지를 발송하는 것은 다른 이야기다.
실제로 우체국에 편지를 보낼 때는 [받는 사람] 과 [참조]에 해당하는 사람 모두에게 편지를 보내야한다.
따라서, '메일을 받아야하는 사람'이 여러 명일 때는 .sendmail() 메서드의 2번째 파라미터에 dtype을 list로 전달한다.
팁 1.
[숨은 참조]는 이렇게 한다.
root_msg['Bcc'] = '숨은 참조에 들어갈 메일'
팁 2.
[받는 사람], [참조], [숨은 참조] 각각 여러 명일 때는 이렇게 한다.
# 콤마(,)를 사용한 str 타입으로 작성
root_msg['To'] = 'test_1@test.com, test_2@test.com, ...'
root_msg['Cc'] = 'cc_1@test.com, cc_2@test.com, ...'
root_msg['Bcc'] = 'bcc_1@test.com, bcc_2@test.com, ...'
# 또는 .join() 메서드를 사용
root_msg['To'] = ''.join('test_1@test.com','test_2@test.com')
root_msg['Cc'] = ''.join('cc_1@test.com','cc_2@test.com')
root_msg['Bcc'] = ''.join('bcc_1@test.com','bcc_2@test.com')
그리고 '메일을 받아야하는 사람'에 해당하는 .sendmail()의 2번째 파라미터에 list로 전달한다.
session.sendmail(
from_mail_addr, # 보내는 사람
['test_1@test.com', 'test_2@test.com',
'cc_1@test.com', 'cc_2@test.com',
'bcc_1@test.com', 'bcc_2@test.com'], # 메일을 받아야하는 사람
root_msg.as_string() # root 메시지
)
팁 3.
한글이 여전히 깨진다면 코드 전문의 82번 line에 '_charset' 인자를 'utf-8'에서 'cp-949' 또는 'euc-kr' 또는 'utf-8-sig' 등을 사용해본다.
'Python > Python Distilled' 카테고리의 다른 글
[python] pymysql로 INSERT 할 때, 마지막 PK 값에서 1씩 증가시키는 방법 (0) | 2023.05.03 |
---|---|
[python] 대입 연산자 (:=) (1) | 2022.10.04 |
[python] 이스케이프 표현식(escaped expression) (0) | 2022.09.12 |