[Python] SMTP, email, pandas 라이브러리 활용한 이메일 자동화 (RPA) (한글 깨짐 해결)

2023. 2. 6. 18:38·Python/Python Distilled

개발 환경

 - 이메일 서버/포트/2차 인증 사용 여부

  • SERVER: smart.whoismail.net
  • PORT: 587
  • 2차 인증 사용 여부: 사용안함

RPA 프로세스

  1. pymysql connect 로 세션 생성
  2. pandas 라이브러리로 DB를 조회하여 DataFrame 으로 저장 (DB에 발송할 이메일 주소를 함께 가져오지만, 보안상 간단히 DataFrame 내 'A' Column 으로 가정)
  3. 발송할 메일 내용이 될 HTML 템플릿을 만든다.
  4. for loop을 돌면서 HTML 템플릿 내부에 df.to_html() 메서드로 만든 df 테이블을 삽입한다.
  5. 메일 발송

 코드 전문

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로 전달한다.

잠시 메일 작성할 때 화면을 보면,

outlook 메일 화면

위 이미지 처럼 [받는 사람] 과 [참조] 가 있는데, 이는 각각 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] 대입 연산자 (:=)  (2) 2022.10.04
[python] 이스케이프 표현식(escaped expression)  (0) 2022.09.12
'Python/Python Distilled' 카테고리의 다른 글
  • [python] pymysql로 INSERT 할 때, 마지막 PK 값에서 1씩 증가시키는 방법
  • [python] 대입 연산자 (:=)
  • [python] 이스케이프 표현식(escaped expression)
옐리yelly
옐리yelly
  • 옐리yelly
    개발 갤러리
    옐리yelly
  • 전체
    오늘
    어제
    • 모든 글 보기 (85)
      • Project (22)
      • Java (4)
      • Spring (8)
      • Kubernetes (6)
      • Docker (2)
      • JPA (3)
      • Querydsl (2)
      • MySQL (9)
      • ElasticSearch (7)
      • DevOps (4)
      • Message Broker (3)
      • Git & GitHub (2)
      • Svelte (1)
      • Python (8)
        • Python Distilled (4)
        • Anaconda (1)
        • Django (0)
        • pandas (3)
      • Algorithm (1)
      • Computer Science (0)
      • 내 생각 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    리팩토링
    argocd
    Project
    비사이드
    Message Broker
    mybatis
    pymysql
    svelte
    pandas
    gitops
    devops
    Spring
    Python
    OOP
    JPA
    ncloud
    예약 시스템
    elasticsearch
    데드락
    nks
    MySQL
    성능 테스트
    포텐데이
    프로젝트
    docker
    k8s
    RabbitMQ
    querydsl
    blue-green 배포
    커넥션 풀
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
옐리yelly
[Python] SMTP, email, pandas 라이브러리 활용한 이메일 자동화 (RPA) (한글 깨짐 해결)
상단으로

티스토리툴바