[파이썬/RPA] 이메일 다루기

seulzzang·2022년 9월 15일
0

파이썬/RPA

목록 보기
3/6

📍이메일 자동화

1. SMTP 서버 정보 변수 할당

  • SMTP : 메일 발송을 위해 메일 서비스 제공 회사와 사용자 간에 약속된 규약 (Simple Mail Transfer Protocol)
  • IMAP : 전자 메일에 엑세스 (Internet Messaging Access Protocol)
  • POP : 사용자 기기에 이메일 다운로드 후 삭제 (Post Office Protocol)
    • 다른 이메일 제공 업체 서버
      {p}.gmail.com
      {p}.gmail.com
      {p}.gmail.com
    • 포트
      465 : SMTP
      993 : IMAP
      995 : POP
SMTP_SERVER = 'smtp.naver.com' # 서버 주소
SMTP_PORT = 465	# 포트번호
SMTP_USER = 'email_addrs' # 유저네임
SMTP_PASSWORD = '*********' # 패스워드

SMTP_PASSWORD같은 경우는 실제 비밀번호를 사용하기 때문에 email_config처럼 따로 파일을 두고 관리를 해도 된다.

SMTP_PASSWORD = open('./email_config', 'r').read()

그럴경우 이렇게 파일 읽어오면 됨.

2. SMTP 메일 발송하기

from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

필요한 라이브러리들을 import 해준다.

1) 편지봉투 만들기

편지를 보내려면 편지봉투가 필요하잔아요?
누구한테, 누가 보내는지, 내용에 뭐가 있는지를 알려주는 파트임당.

# 편지봉투 만들기
msg = MIMEMultipart('alternative')

# 편지 내용
msg['From'] = SMTP_USER
msg['To'] = SMTP_USER
msg['Subject'] = '제목'
contents = "테스트"

# 편지지를 편지봉투에 담아준다
text = MIMEText(contents)
msg.attach(text)

만약에 첨부파일이 있다면

if attachment:
        msg = MIMEMultipart('mixed')

alternative가 아닌 mixed를 써주면 됩니다!

2) 메일 발송하기

smtplibimport해주고 다음과 같이 try-except-finally구문으로 메일 발송 부분을 작성할 수 있다.

import smtplib

try:
    # 이메일 전송 서버에 접속
    smtp = smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT)
    print('메일 서버 접속 성공')
    smtp.login(SMTP_USER, SMTP_PASSWORD)
    print('로그인 성공!')
    smtp.sendmail(from_addrs, to_addrs, msg.as_string())
    print('이메일 발송 성공!')
except Exception as e:
    # 에러났을 때 실행할 코드
    print('#### 에러 발생 ####')
    print(e)
finally:
    # 에러 여부 상관없이 무조건 실행할 코드
    # smtp 연결 해제
    smtp.close()
    print('Finally')

print구문들은 제대로 작동하고 있는지 확인하기 위함이다!
만약에 엑세스가 거부된다면

네이버 메일 환경설정에서 IMAP/SMTP 설정 에서 IMAP/SMTP 사용사용함으로 바꿔주면 된다.
그 외 오류가 난다면 본인 비밀번호가 틀린 문제거나, 네이버 로그인 2단계 인증을 사용하시는 분일텐데 👉네이버 외부 메일, 2단계 인증 사용자 적용 방법 요기 보시면 도움이 되실듯..
나는 2단계 인증 사용 안해서 메일 설정만 바꿔주니 정상적으로 보내졌다!

3) 첨부파일 추가하기

from email.mime.base import MIMEBase #'base64'라는 데이터타입을 사용
from email import encoders
from os.path import basename

마찬가지로 필요한 라이브러리들을 import 해주고~

# 파일 담을 공간
email_file = MIMEBase('application', 'octet-stream')
# 파일 읽어오기
with open('실습3/example3.xlsx', 'rb') as f:
    file_data = f.read()

# .set_payload로 file_data를 첨부파일에 담아준다.
email_file.set_payload(file_data)

# base64인코딩형식으로 인코딩
encoders.encode_base64(email_file)

file_name = basename('실습3/example3.xlsx')
email_file.add_header('Content-Disposition', 'attachment', filename=file_name)

msg.attach(email_file)

basename은 파일명만 따오기 위한 것이다. 경로를 적어줘도 example3.xlsx만 가져옴!
이렇게 작성해주면 된다. 강사님도 이 부분은 못외웠다고 하심. 그러니까 필요할때마다 찾아 쓰면 됨!
이 코드를 실행하고 메일발송 코드를 실행시키면 첨부파일이 예쁘게 담겨져서 온다.

📍 실습 4

  • 앞에서 배운 메일 발송 로직을 하나의 함수로 만들기. 함수명은 자유롭게 해주세요.

💻나의 코드

위 코드들을 하나로 합쳐주면 된다.

from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import smtplib

SMTP_SERVER = 'smtp.naver.com'
SMTP_PORT = 465
SMTP_USER = 'email_addrs'
SMTP_PASSWORD = open('./email_config', 'r').read()

def send_mail(addrs, title, content, attachment = False):
    msg = MIMEMultipart('alternative')
    
    if attachment:
        msg = MIMEMultipart('mixed')

    msg['From'] = SMTP_USER
    msg['To'] = addrs
    msg['Subject'] = title
    contents = content
    text = MIMEText(contents)
    msg.attach(text)

    # 첨부파일이 있다면
    if attachment:
        from email.mime.base import MIMEBase
        from email import encoders
        from os.path import basename

        email_file = MIMEBase('appliacation', 'octet-stream')
        with open(attachment, 'rb') as f:
            file_data = f.read()
        email_file.set_payload(file_data)
        encoders.encode_base64(email_file)
        file_name = basename(attachment)
        email_file.add_header('Content-Disposition', 'attachment', filename=file_name)
        msg.attach(email_file)

    # 메일 발송
    try:
        smtp = smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT)
        smtp.login(SMTP_USER, SMTP_PASSWORD)
        smtp.sendmail(SMTP_USER, addrs, msg.as_string())
    except Exception as e:
        print('#### 에러 발생 ####')
        print(e)
    finally:
        # smtp 연결 해제
        smtp.close()

별거 없죵?
나는 나의 네이버 메일에서 지메일로 보내주기로 했다. 해당 함수를 실행하면

send_mail(my_gmail_addrs, '실습4', '실습4 test')


잘 온다. 첨부파일이 없기때문에 첨부파일은 안왔음.
만약에 첨부파일을 같이 보낸다면

send_mail(my_gmail_addrs, '실습4', '실습4 test', '실습3/example3.xlsx')

attachment에 파일경로 넣어주면 됨.

역시 잘 왔당 ㅎㅎ

💻모범 답안


from email.mime.text import MIMEText

from email.mime.base import MIMEBase
from email import encoders
from os.path import basename

import smtplib

# 이메일 발송을 위한 상수변수들 설정
SMTP_SERVER = 'smtp.naver.com'
SMTP_PORT = 465
SMTP_USER = email_addres
SMTP_PASSWORD = open('./email_config', 'r').read().rstrip()

# 이메일 발송 함수
def send_mail(from_user:str, to_users:list, subject:str, content:str, attachments:list=[], cc_targets=[]) -> bool:
    '''
    메일을 발송하는 함수입니다.

    **필수값**
    from_user: 보내는 사람의 이메일 주소
    to_users: 받는 사람의 이메일 주소들 (list)
    subject: 메일 제목
    content: 메일 내용

    **선택**
    attachments: 첨부 파일들 (파일경로 리스트)
    cc_targets: 참조 이메일 주소들 (list)
    '''
    try:
        # 이메일 전송 서버에 접속
        smtp = smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT)
        print('메일 전송 서버 접속 성공')
        smtp.login(SMTP_USER, SMTP_PASSWORD)
        print('로그인 성공 !')

        # 편지봉투 만들기
        msg = MIMEMultipart('alternative')

        # 첨부파일여부
        # True : 1, ['result.xlsx'], 'a'
        # False : 0, [], ''
        if attachments:
            msg = MIMEMultipart('mixed')

            for attachment in attachments:
                # 파일 담을 공간 (첨부 파일)
                email_file = MIMEBase('application', 'octet-stream')

                # 파일 읽어오기
                with open(attachment, 'rb') as f:
                    file_data = f.read()

                # 파일 데이터를 첨부파일에 담아준다.
                email_file.set_payload(file_data)
                # base64 인코딩형식으로 인코딩
                encoders.encode_base64(email_file)

                file_name = basename(attachment)
                email_file.add_header('Content-Disposition', 'attachment',
                    filename=file_name)

                msg.attach(email_file)

        msg['From'] = from_user
        msg['To'] = ','.join(to_users)
        if cc_targets:
            msg['CC'] = ','.join(cc_targets)
        msg['Subject'] = subject

        # 편지지를 편지봉투에 담아준다.
        text = MIMEText(content)
        msg.attach(text)
        
        # sendmail('보내는 사람', '받는 사람', msg.as_string())
        smtp.sendmail(from_user, ','.join(to_users+cc_targets), msg.as_string())
        print('이메일 발송 성공 !')

        return True

    except Exception as e:
        # 에러났을 때 실행할 코드
        print('#### 에러 발생 ####')
        print(e)

    finally:
        # 에러 여부 상관없이 무조건 실행할 코드
        # smtp 연결 해제
        smtp.close()
    
    return False

라이브러리들을 import하는 경우 함수 밖에 빼주는게 좋다고 하심. 메모✍
강사님은 받는 사람이 여러명일 수도 있으니 그 대상들을 list로 받고, 첨부파일도 여러개일 수 있으니 list로 받아주셨다. 또 참조가 있을 수도 있으니 cc_targetsparameters에 넣어주심.
함수 설명에 나와있다시피 첨부파일과 참조이메일 주소들은 필수항목이 아니다.
만약에 이메일 전송 서버에 접속 실패할 수도 있으니 크게 try-except-finally로 묶어주셨다.
진짜 이메일들이 이런식으로 작동하는 것 아닐까!

✍수정사항

  • smtp.sendmail(from_user, ','.join(to_users+cc_targets), msg.as_string())
    이부분에서 받는사람 부분은 저렇게 join해주면 맨 앞에 있는 이메일에게만 메일이 전송된다고 한다.
    • smtp.sendmail(from_user, set(to_users+cc_targets), msg.as_string())
      이렇게 iterable한 객체로 넣어줘야함!!

📍실습 5

  • 엑셀 파일을 활용해 결제완료한 수강생들에게 결제 확인 및 수강 안내 커리큘럼 이메일 발송하기

💻나의 코드

import openpyxl
# def send_mail(addrs, title, content, attachment = False):

xlsx = openpyxl.load_workbook('./실습5/수강생_결제정보.xlsx', read_only = True)
sheet = xlsx.active

for row in sheet.iter_rows():
    D_row = row[3].value
    if D_row == '결제완료':
        send_mail(addrs, '실습5', '실습5 test', './실습5/커리큘럼.xlsx')

openpyxl로 엑셀 파일 .load_workbook해주고 위에서 작성한 함수로 메일을 보내주기만 하면 끝
얘는 내 네이버 메일로 전송했다.
결제완료된 수강생이 10명이므로 메일도 10개 다 잘 온 것을 확인할 수 있다!
만약에 해당 엑셀에서 이메일정보까지 불러온다면 엑셀 시트에서 B열이 이메일 정보니까 이 역시 for문 안에 넣어줘서 B_row = row[1].value 이렇게 해주면 될듯욤?
실습자료의 이메일이 강사님 이메일이라 나는 그냥 내 이메일 주소 넣고 했음.

💻모범 답안

import openpyxl
from email_manager import send_mail

workbook = openpyxl.load_workbook('./실습5/수강생_결제정보.xlsx', read_only = True)
sheet = workbook.active

target_users = []
for row in sheet.iter_rows(min_row=2):
    if row[3].value == '결제완료':
        email_address = row[1].value
        target_users.append(email_address)

is_send = send_mail(email_addrs, [email_address], 
            subject='결제완료 안내 메일', 
            content = '결제완료 되었습니다.',
            attachments=['RPA/실습5/커리큘럼.xlsx'])

if is_send:
    print('발송완료')

위에 올린 엑셀파일 캡처를 보면 알겠지만 1행은 헤더부분이다. (수강생이름, 이메일, 수강신청일, 결제여부, 결제일) 그래서 2행부터 읽어주면 되는데 이를 sheet.iter_rows(min_row=2)이렇게 나타내주면 된다. min_row라는 인자가 있음을 확인하기!
그리고 send_mailfor문 안에 있어도 상관 없지만(받는 사람 메일을 row[1].value로 두고 계속 갱신해도 됨) 어차피 그 외에는 다 똑같은 내용들이니까 반복문 밖으로 빼주고, 결제완료인 사람들의 이메일을 target_userslist로 받아서 처리해주는 방법이다.
사실 그렇게 막 로직이 다르고 .. 크게 다를바 없는 코드지만 sheet.iter_rows(min_row=2) 같은 섬세한 부분이 더 좋은 코드를 만들어 내는 것 같다. 뭐.. 결제완료라는 단어가 헤더에 들어가있진 않지만 혹시 헤더부분과 엑셀시트의 내용이 겹치는 경우가 있으면 곤란해지니까?


오늘 수업은 여기까지였다. 이메일을 처음 다뤄봤는데 너무 신기했음.. 이렇게 메일을 보낼 수 있는 거구나😜 이제 프로젝트..하자..

profile
중요한 것은 꺾이지 않는 마음

0개의 댓글