파이썬 이메일 자동화 (이메일 대량 발송)

회사에서 업무를 하다 보면 여러명에게 각각 다른 제목과 내용으로 (심지어 각각 다른 첨부파일을 포함해서) 이메일을 보내야 할 때가 있다. 손으로 보내긴 귀찮으니 파이썬으로 자동화하는 코드를 최대한 간단히 짜보았다.


1. 이메일을 보낼 때 알아야 할 기본적인 개념

1) MIME

이메일은 일단 MIME(Multipurpose Internet Mail Extensions)이라는 ‘전자우편을 위한 표준적인 형식’으로 작성되어야 한다. 사실 나도 그 이상은 모른다.

이건 파이썬에 내장된 기본 라이브러리 email을 통해 가볍게(?) 작성할 수 있다.

2. SMTP

SMTP(Simple Mail Transfer Protocol)는 이메일을 주고 받는 표준적인 약속 체계나 규약을 의미한다. 역시 나도 이 이상은 모른다.

어쨌든 이메일을 보내려면 메일 발송하기 위한 SMTP 서버 주소와 그 때 사용할 포트(경로)만 알면 된다는 뜻이다.

자신이 사용하는 이메일 서버와 포트는 간단히 찾을 수 있다. Gmail(지메일)의 경우 SMPT 서버는 smtp.gmail.com, 포트는 465를 사용한다. 대부분의 상용 이메일 서비스들은 이 정보가 다 공개되어 있고, 회사 이메일도 관련 부서에 문의해서 알아보면 쉽게 알 수 있을 거다. 어쨌든~

SMTP 서버와 포트를 설정해서 이메일 발송하는 과정은 파이썬에서 기본적으로 제공하는 smtplib라는 라이브러리를 통해 손쉽게 구현할 수 있다.


결국 SMTP 서버, 포트, 이메일 계정과 비밀번호만 준비하면 끝이다. SMTP 서버에게 “이메일을 이런 형태로 보내줘”라고 MIME 뭐시기 형태로 얘기해주는 게 우리가 할 일이다.

그리고 별도의 라이브러리를 설치할 필요도 없다. 파이썬 기본 라이브러리로 해결할 거다. (자세한 내용은 레퍼런스 참고. 귀찮으면 넘어가도 된다.)


2. 이메일 발송 파이썬 코드 예제

여기서는 지메일을 기준으로 작성했다.

그리고 정규표현식을 통해 실제로 보내야 하는 이메일 주소가 유효한 것인지 확인하는 is_valid라는 이름의 함수를 포함시켜보았다. (참고로 정규표현식은 문자열을 다루는 방법인데, 깊이 들어가면 너무 어렵기 때문에 그냥 다른 사람이 잘 작성해놓은 코드를 필요에 따라 가져다가 쓰자.)

그리고 텍스트로만 구성된 형식으로 메일을 보낼 건지 아니면 파일이나 이미지를 첨부할 것인지에 따라 MIME 형식이 달라져야 한다. 만약 텍스트로만 메일을 보내려면 Text라는 형식으로 작성해도 되지만, 만약 첨부파일이 있는 경우 이메일 포맷을 Mulipart로 구성해서 "mixed"라는 형식으로 바꿔주어야 한다.

어쨌든 아래 코드에서는 만약 첨부파일이 있는 경우 그 파일을 포함해서 메일을 날리는 것으로 send_mail(addr, subj_layout, cont_layout, attachment=None)이라는 함수를 작성해보았다. 이 함수는 ‘받는 사람 이메일 주소’, ‘제목’, ‘내용’, 그리고 ‘첨부파일이 있는 경우 해당 파일 경로’를 인자로 받는다.

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

# SMTP 접속을 위한 서버, 계정 설정
SMTP_SERVER = "smtp.gmail.com"
SMTP_PORT = 465

# 보내는 메일 계정
SMTP_USER = "보내는 사람 메일 주소"
SMTP_PASSWORD = "비밀번호"

# 이메일 유효성 검사 함수
def is_valid(addr):
    import re
    if re.match('(^[a-zA-Z-0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)', addr):
        return True
    else:
        return False

# 이메일 보내기 함수
def send_mail(addr, subj_layout, cont_layout, attachment=None):
    if not is_valid(addr):
        print("Wrong email: " + addr)
        return
    
    # 텍스트 파일
    msg = MIMEMultipart("alternative")
    # 첨부파일이 있는 경우 mixed로 multipart 생성
    if attachment:
        msg = MIMEMultipart('mixed')

    msg["From"] = SMTP_USER
    msg["To"] = addr
    msg["Subject"] = subj_layout

    contents = cont_layout
    text = MIMEText(_text = contents, _charset = "utf-8")
    msg.attach(text)

    # 첨부파일이 있으면
    if attachment:
        from email.mime.base import MIMEBase
        from email import encoders

        file_data = MIMEBase("application", "octect-stream")
        file_data.set_payload(open(attachment, "rb").read())
        encoders.encode_base64(file_data)

        import os
        filename = os.path.basename(attachment)
        file_data.add_header("Content-Disposition", 'attachment', filename=('UTF-8', '', filename))
        msg.attach(file_data)

    # smtp로 접속할 서버 정보를 가진 클래스변수 생성
    smtp = smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT)

    # 해당 서버로 로그인
    smtp.login(SMTP_USER, SMTP_PASSWORD)

    # 메일 발송
    smtp.sendmail(SMTP_USER, addr, msg.as_string())

    # 닫기
    smtp.close()

3. (활용) 엑셀에서 명단을 불러와 대량으로 이메일 보내기

보통 이메일을 대량으로 보내기 전에 엑셀로 명단을 주고 받고, 준비하는 경우가 많다. 귀찮으니 그냥 이메일 주소뿐만 아니라 제목이랑 내용, 첨부할 파일의 경로까지 다 엑셀 파일에 정리해놓고 한 번에 이메일을 날려보자.

파이썬으로 엑셀 파일을 다루려면 라이브러리를 하나 설치해야 한다. 여기서는 내가 애용하는 가볍고 쉬운 엑셀 라이브러리 openpyxl를 사용하겠다.

참고로 openpyxl은 이전에도 이런 포스팅을 통해 소개한 바 있다.

이렇게 ex.xlsx라는 엑셀 파일에 받는 사람 주소, 제목, 내용, 첨부파일명(경로)를 작성해놓자. (파일을 첨부할 필요가 없다면 D열은 비워둬도 된다.)

그리고 위에 소개된 코드와 아래의 코드를 연결해서 돌리면 끝이다.

from openpyxl import load_workbook

wb = load_workbook('ex.xlsx')
ws = wb.active

for row in ws.iter_rows():
    addr = row[0].value
    subj_layout = row[1].value
    cont_layout = row[2].value
    attachment = row[3].value

    send_mail(addr, subj_layout, cont_layout, attachment)

끝.

대량으로 이메일 뿌리기?
생각보다 쉽다.

반복적인 작업은 무조건 자동화 하고
우린 그 시간에 놀자. 더 의미 있는 일을 하자.

추천 글

“파이썬 이메일 자동화 (이메일 대량 발송)”의 15개의 댓글

  1. 안녕하세요 선생님 다름이아니라 선생님의 파이썬 코드를 이용해보려하는데 첨부파일을 여러개 첨부할순 없는건가요?

    1. 피드백 감사합니다.

      질문 해주신 덕분에 저도 오랜만에 다시 들여다봤네요…!

      send_mail() 함수에서
      for 반복문을 돌며 파일을 하나씩 첨부하는 것으로 짜보았습니다. 🙂
      애초에 첨부파일을 list로 준비하면 함수가 작동합니다.

      ————————————

      # 이메일 보내기 함수 (첨부파일을 list 형태로 받음)
      def send_mail(addr, subj_layout, cont_layout, attachments=None):

          # 텍스트 파일 (첨부파일이 있는 경우 mixed로 생성)
          msg = MIMEMultipart(“alternative”)
          if attachments:
              msg = MIMEMultipart(‘mixed’)

          msg[“From”] = SMTP_USER
          msg[“To”] = addr
          msg[“Subject”] = subj_layout
          contents = cont_layout
          text = MIMEText(_text = contents, _charset = “utf-8”)
          msg.attach(text)

          # 첨부파일이 있으면
          if attachments:
              from email.mime.base import MIMEBase
              from email import encoders

              for attachment in attachments:
                  if attachment:
                      file_data = MIMEBase(“application”, “octect-stream”)
                      file_data.set_payload(open(attachment, “rb”).read())
                      encoders.encode_base64(file_data)
                      filename = os.path.basename(attachment)
                      file_data.add_header(“Content-Disposition”, ‘attachment’, filename=(‘UTF-8’, ”, filename))
                      msg.attach(file_data)

          # smtp로 접속할 서버 정보를 가진 클래스변수 생성
          smtp = smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT)
          # 해당 서버로 로그인
          smtp.login(SMTP_USER, SMTP_PASSWORD)
          # 메일 발송
          smtp.sendmail(SMTP_USER, addr, msg.as_string())
          # 닫기
          smtp.close()

  2. 선생님 저 코드가 실행이 잘됬었는데 갑자기 ImportError: cannot import name ‘Chartsheet’ from ‘openpyxl.chartsheet’ (unknown location)라고 뜹니다 왜그럴까요

    1. 글쎄요… 일단 메시지만 보면
      openpyxl 라이브러리를 불러올 때 문제가 있는 것 같습니다.
      이것만 따로 떼어서 불러오고 실행하는 테스트를 한 번 해보시는 게 좋을 거 같아요.

      원래 잘 실행이 됐다면 파이썬이나 라이브러리 버전을 한 번 확인해보시는 게 좋을 거 같아요.
      중간에 업데이트를 했다거나 다른 라이브러리를 설치하면서 이것저것 꼬였을 수도 있겠네요.

  3. python3.7로 실행했을 때 여러 에러가 발생하긴 하지만 이메일은 보내지네요. 제목과 내용은 빠진채로요ㅠㅠㅠ 혹시 python2.7버전으로 작성한 코드이신가요? 그리고 엑셀 크롤링하는 두번째 코딩은 첫번째 코딩의 어느 부분에 붙여야 하나요?

    1. python 3.6 사용할 때 작성했던 것 같아요.
      제가 python 2는 사용해본 적이 없네요.

      제목과 내용은 send_mail()의 인수 subj_layout, cont_layout에 각각 문자열로 넣어주셨는지 잘 보시고요.
      첫 번째 코드는 함수라서 아래 코드로 실제 send_mail() 함수를 실행하게 됩니다.

      부디 잘 성공하시길 _()_

  4. 안녕하세요 선생님 코드 잘보았습니다.
    제가 아두이노와 파이썬을 연동하여 아두이노에서 진동을 감지하면 파이썬을 통해서 이메일로 알림을 주려는 코딩을 하고 있습니다.
    아두이노와 파이썬은 연동 성공하였는데, 파이썬에서 메일을 보내는데 자꾸 오류? 또는 진동 감지 시 메일을 보내고 싶은데 한 번 밖에 메일이 안보내집니다.. 혹 여기에 코딩한 내용을 올리면 좀 도와주실 수 있으실까요?

    1. 제가 아두이노를 몰라서 답은 못 드리겠지만ㅜㅜ
      파이썬 스크립트가 실행되는 조건을 정의하는 과정에서
      혹시나 놓친 부분이 있지 않을까요…?

  5. # -*- coding: utf-8 -*-

    import time
    import serial
    import smtplib
    from email.mime.text import MIMEText

    serial = serial.Serial(‘COM7’, 9600)

    def send_mail():

    s = smtplib.SMTP(“smtp.gmail.com:587”)
    s.starttls()
    s.login(‘******@gmail.com’, ‘**************’)
    print(“메일을 보내는 중입니다. 잠시 기다려 주세요.”)

    msg = MIMEText(“움직임이 감지 되었습니다. 확인 바랍니다.”, _charset=’utf-8′)
    msg[‘Subject’] = “움직임 감지 알림!”

    s.sendmail(“*****@gmail.com”, “********@naver.com”, msg.as_string())
    s.quit()
    print(“메일을 발송했습니다.”)

    while True:
    message = serial.readline()
    print(message[0])
    if message[0] == ‘M’: # 첫번째 글자가 M이면… 그런데 값을 인지 하지 못하네??? 임시방편으로 처리함!
    send_mail()
    time.sleep(0.5)

    이렇게 작성했는데… 선생님께서 작성하신대로 다시 짜면 볼까요…?

  6. 안녕하세요, 해당 코드를 잘사용하고 있는데요,혹시 메일 내용을 HTML 형식으로 폰트 사이즈나, bodl처리가 가능하게 html형식으로도 코드 짜는게 가능할까요?

  7. 안녕하세요! 따라하다가 막히는게 있어서 여쭤봅니다

    msg[‘From’] = addr
    msg[‘To’] = subj_layout
    msg[‘Subject’] = cont_layout
    contents = cont_layout

    1. 이 부분에서 addr subj_layout subject cont layout 에는 제가 직접 기입해야하는 건가요?
    ‘aaa@gmail.com, ‘bbb@gmail.com’, ‘Title’, cont_layout(이건 뭔지 잘 모르겠어서요..)

    2. 저렇게 작성하고 send_mail(addr, subj_layout, cont_layout, attachment)을 입력하면
    name ‘addr’ is not defined 이런 에러가 뜨는데 어떻게 해결해야 할까요?

    1. 위에 댓글 남겼는데 위에 문제는 해결했습니다!..(답변은 안주셔도 됩니다!)

      import smtplib
      from email.mime.text import MIMEText
      from email.mime.image import MIMEImage
      from email.mime.base import MIMEBase
      from email.mime.multipart import MIMEMultipart
      from email import encoders

      def send_mail(addr, subj_layout, cont_layout, attachments=None):

      # 텍스트 파일 (첨부파일이 있는 경우 mixed로 생성)
      msg = MIMEMultipart(‘alternative’)
      if attachments:
      msg = MIMEMultipart(‘mixed’)

      msg[‘From’] = addr
      msg[‘To’] = subj_layout
      msg[‘Subject’] = cont_layout
      contents = cont_layout
      text = MIMEText(_text = contents, _charset = ‘utf-8’)
      msg.attach(text)

      # 첨부파일이 있으면
      if attachments:
      from email.mime.base import MIMEBase
      from email import encoders

      for attachment in attachments:
      if attachment:
      file_data = MIMEBase(‘application’, ‘octect-stream’)
      file_data.set_payload(open(attachment, ‘rb’).read())
      encoders.encode_base64(file_data)
      filename = os.path.basename(attachment)
      file_data.add_header(‘Content-Disposition’, ‘attachment’, attachment=(‘UTF-8’, ”, attachment))
      msg.attach(file_data)

      # smtp로 접속할 서버 정보를 가진 클래스변수 생성
      smtp = smtplib.SMTP(‘smtp.gmail.com’, 587)
      smtp.ehlo()
      smtp.starttls()
      smtp.ehlo()
      smtp.login(‘aaa@gmail.com’, ‘비밀번호’)
      # 메일 발송
      smtp.sendmail(‘aaa@gmail.com’, ‘bbb@gmail.com’, msg.as_string())
      # 닫기
      smtp.close()

      send_mail(‘aaa@gmail.com’, ‘bbb@gmail.com’,’Title’, attachment)

      이렇게 했는데 메일은 오는데 파일이 첨부가 안됩니다.. 어떤부분이 문제일까요?..

댓글 남기기