[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
  • 전체
    오늘
    어제
    • 모든 글 보기 (84)
      • Project (22)
      • Java (4)
      • Spring (8)
      • Kubernetes (6)
      • Docker (2)
      • JPA (2)
      • 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)
  • 블로그 메뉴

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

  • 공지사항

  • 인기 글

  • 태그

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

  • 최근 글

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

티스토리툴바