Python 복습 004 | 파이썬으로 이메일 발송하기 (SMTP, Gmail)

Yunny.Log ·2022년 1월 19일
0

Python

목록 보기
4/7
post-thumbnail

파이썬으로 이메일 발송하기 (SMTP, Gmail)

0) 사전준비 :

0-1) Google 보안 수준 변경

  • 보안 수준이 낮은 앱'의 액세스 권한을 허용
    => Python 프로그램이 우리 메일 서버에 접근 가능

0-2 Google IMAP 상태값 변경

=> 모든 설정 보기로 들어가서

=> imap 허용

1) SMTP / IMAP / POP3

SMTP, POP3, IMAP 정리 블로그 글

  • 메일 보내는 사람은, 자신이 사는 동네의 우체국인 SMTP 서버에 메일 저장
  • SMTP서버에서 다시 받을 사람의 POP3 서버로 보낸다, POP3는 SMTP의 이메일 데려오는 받는이의 우체국 역할
  • STMP : Simple Mail Transfer Protocol (간단히 메일 보내기 위한 약속)
  • 메일을 보낼 시에 상대의 컴퓨터로 바로 보내는 것이 아닌 , 중간에 메일 서버라는 곳을 거친다. 이것이 거쳐지고 거쳐져 결국 목표대상인 END-USER에게 전해지는 것
  • SMTP 는 메일 서버 간의 전송 규약 (서버 간 보내는 것 관련)
  • POP3/ IMAP : 유저가 메일 서버에서 메일 받기 위한 프로토콜
  • POP3는 받는 메일
  • IMAP3 도 받는 메일
    => 차이는 서버에 이메일 저장, 삭제 여부가 다르다는 것, POP3는 삭제, IMAP은 보관해둠

2) SMTP 서버 활용 이메일 전송하기

2-1 : SMTP 메일 서버를 연결한다
2-2 : SMTP 메일 서버에 로그인한다
2-3 : SMTP 메일 서버로 메일을 보낸다

2-1 ) SMTP 메일 서버 연결

  • 이전의 크롤링과 번역기 사용 시에는 우리가 서버에 요청을 보내면 요청을 받아오는 형태였음

  • 이번에는 응답 받는게 아니라 서버를 직접 연결해서 서버로 메일 보내준 다음, 원하는 서버로 메일 전해달라~ 라고 하는 것이 목표다앙

  • smtplib 는 설치 필요 없음

import smtplib
  • 서버 연결 위해 필요한 두가지 요소
    (1) 서버 주소
    (2) 서버 포트 번호
import smtplib
SMTP_SERVER = "smtp.gmail.com"
#gmail을 사용할 것이기 때문에 gmail stmp 주소
SMTP_PORT = 465
# gmail이 우리의 문을 열고 들어오기 위해선 꼭 465번 문을 열고 들어와야 함
smtpp = smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT)
print(smtpp)
#서버 찾기 위해선 서버주소, 포트번호 필요

=> stmp.STMP을 통해 서버를 연결해주면 RETURN 되는 것은 연결정보

  • 이 연결정보를 smtp 라는 변수에 담아와보자꾸나
    stmp = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)

=> 그런데 이 smtp 안에 담긴 응답 변수를 출력해보면

  • 연결에 실패했다는 응답이 돌아온다..!

  • 이는 보안문제가 원인

  • SMTP는 보안을 위해서 SSL 을 이용한다. 따라서 서버 연결시 이와 같은 암호화 방식을 포함해서 서버를 연결해야 하는 것, 즉 지금은 SSL 처리 함수가 존재하지 않아서 연결이 불발된 것이고 따라서 이를 처리해줄 SSL을 추가적으로 넣어줘야 한다.

  • SSL 처리 추가 위해서는 단순 SMTP가 아닌 SMTP_SSL 로 서버 연결

smtp = smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT)

=> smtp 출력해보면

성공적으로 연결되었음을 알 수 있음

2-2 ) SMTP서버 연결 후, 해당 서버에 로그인

  • 내 gmail 에 연결시켜야 하므로 login 이 요구됨
  • smtp.login("gmail주소" , "비밀번호") 를 통해 본인의 계정으로 로그인 진행
    => gmail에 서버 연결 & 계정 정보 주어서 로그인 하게 되는 것

로그인 값을 출력해보면 잘 연결됨 알 수 있음
비밀번호 틀리면 accepted 되지 않음

2-3 ) 메일을 보내기

smtp.send_message()

MIME : 이메일 구현을 위해 알아두어야 할 개념 , 전자 우편을 보낼 때 보내기로 한 형식 정해둔 것

  • 우리의 명령을 MIME 형식으로 변형하면 SMTP가 우리의 명령을 알아듣는 것이 가능
  • 이메일의 형태를 지정하는 방식
    1) 이메일을 만든다
    2) 이메일에 내용을 담기
    3) 이메일에 발신자, 수신자를 담기

2-3-1) 이메일을 만든다 (MIME 형태로 이메일 만들기)

우선 email.message 모듈 import
=> 여기서 EmailMessage 라는 def 만 사용할 것
from email.message import EmailMessage

message = EmailMessage()

  • EmailMessage 라는 아이가 메시지를 담을 통을 만들어둔다.
  • EmailMessage 를 통해 우리가 쓴 메시지를 MIME 형태로 변경해서 통에 담아줌
  • 우린 그 통을 message 라는 변수에 담아서 쓰고자 하는 것
import smtplib
from email.message import EmailMessage

SMTP_SERVER = "smtp.gmail.com"
SMTP_PORT = 465

message = EmailMessage()
#EmailMessage 라는 아이가 메시지를 담을 통을 만들어둔다.
#EmailMessage 를 통해 우리가 쓴 메시지를 mime 형태로 변경해서 통에 담아줌
#우린 그 통을 message 라는 객체에 담아서 쓰고자 하는 것

smtp = smtplib.SMTP_SSL(SMTP_SERVER,SMTP_PORT)
smtp.login("mail주소", "비번")
smtp.send_message()
smtp.quit()
  • 이때 send_message() 괄호 안에는 mime 통 객체 (내용,헤더내용 존재하는)넣어주고 보내주면 됨
  • 완료 후 서버에서 나가줘야 함!~!

2-3-2) 만들어둔 이메일 통에 이메일에 내용을 담는다

내용 담기 (객체.set_content("담을내용"))

message.set_content("담을 내용 작성해주기")

=> 다만 지금은 내용만 담는 중
=> 제목, 수신자, 발신자도 담아주어야 한다. (얘네는 헤더 영역에포함)

2-3-3 ) 제목, 발신자, 수신자 설정 (객체["키워드"])

  • MIME 에는 헤더가 존재, 이 헤더에는 아래와 같은 키워드들이 존재
  • 헤더(Header) : 아래의 나오는 각종 키워드로 시작하여 원하는 영역 지정
    객체["키워드"]
    • Return-Path: => 하나 이상의 SMTP 서버를 경유할 때 필요한 항목
    • Received: => 하나 이상의 SMTP 서버를 경유할 때 필요한 항목
    • From: => 발신자
    • To: => 수신자
    • Subject: => 제목
    • Date: => 날짜
message.set_content("나는 지금 배가 고파용")

message["Subject"] = "이건 제목입니당."

message["From"] = "00@gmail.com"

message["To"] = "00@naver.com"

2-3-4 ) 이메일 전송해보기

  • 라이브러리 불러오고
  • 이메일 입력받을 mime 통 & 객체 만들고
  • 통에다가 내용, 제목, 수신인, 발신인 넣어주고
  • 서버 연결하고 (서버 연결 시 서버주소 & 포트번호 필요, 그리고 ssl 보안 생각하면서 연결하기)
  • 서버에 로그인 (보낼 이메일(=로그인된 이메일), 비번) 하고
  • smtp.send_message(mime통 객체) , 이메일 보내기
  • smtp.quit() , 서버 연결 종료

  • 내가 지정한 내용, 헤더의 제목, 수신, 발신 다 맞게 잘 보내짐 ㅋ 성공이다

3) 메일에 사진 첨부

  • rb, wb, ab (read, write, append + binary)
    -binary : 컴퓨터가 읽기 편한 문자 (png, img => 인간이 이해하긴 쉽지만,컴터는 이걸 binary로 읽어야 함)
~윗 부분 생략~
message.set_content("나는 지금 배가 고파용") #내용
message["Subject"] = "이건 제목입니당." #제목
message["From"] = "~@gmail.com" #발신
message["To"] = "~@naver.com" #수신

f=open("붕어빵.PNG", "rb")
#png 파일은 binary로 읽어와줘야해서 rb
a=f.read()

print(a)의 결과는 아래와 같다


=> rb가 어떻게 읽히나 프린트해보면 위와 같이 출력된다 ㅋ ;;
=> binary 코드당 컴퓨터가 좋아하는!, 컴퓨터는 위와 같이 이미지 파일을 이해하고 있는중

  • 근데 위의 코드처럼 작성하면 꼭 close 수동으로 해줘야 하는데 귀찮ㅋ음
  • 그래서 아래와 같이 코드 refactoring 하자
with open("붕어빵.PNG", "rb") as image :
    image_file = image.read()

=> 자동으로 닫아주게 돼있음

  • 그리고 메일에 이미지 넣어주려면 필요한 함수

    add_attachment(..)

  • 일반 텍스트가 아니라면 multipart/mixed 타입
  • 메일에 텍스트 이외의 것이 들어오게 되면 plain 타입에서 mixed 타입이 됨

mixed type , 즉 add_attachment(..) 사용 위해 필요한 세가지
message.add_attachment(image_file,maintype='image',subtype='png')
1) image (이때 바로 png 파일로 넣는게 아니라, br로 읽어온아이로 넣어주깅)
2) maintype (내가 보내는 아이의 타입알려주면 됨, 여기선 image)
3) subtype (확장자, 근데 이게 확장자 바뀌면맨날 바꿔줘야 함.. 귀찮 ;; )
=> 3)에서 확장자 변경해줄 python 기능 imghdr.what 사용하즈앙 ㅋ
==> import imghdr : import 해와야 함
==> 얘는 파일명 & 파일의 원래 자료형 필요
=> image_type = imghdr.what('codelion',br로 읽어온거 담은 아이)

image_type = imghdr.what('파일명',image_file)
message.add_attachment(image_file,maintype='image',subtype='png')

=> 여기서 image_file 이라는 친구는 우리가 이미지를 br로 읽어온 친구임, 이미지 그 자체 아님

  • image_type = imghdr.what('codelion',br로 읽어온거 담은 아이)
    => 위의 아이를 이용해서 type 을 결정해주었고, 이를 프린트 해보면 현재 붕어빵이라는 파일명을 가진 아이가 png 의 subtype 이라는 확장자인 사실이 밝혀짐 ㅋ 두두둥

마지막으로 이 코드를 사용해서 이제 전송만 하면 끝!

import smtplib
from email.message import EmailMessage
import imghdr
SMTP_SERVER = "smtp.gmail.com"
SMTP_PORT = 465

message = EmailMessage()
#MIME 형태로 변환

message.set_content("나는 지금 배가 고파용") #내용
message["Subject"] = "이건 붕어빵 사진입니당." #제목
message["From"] = "00@gmail.com" #발신
message["To"] = "00@naver.com" #수신

with open("붕어빵.PNG", "rb") as image :
    image_file = image.read()

image_type = imghdr.what('붕어빵',image_file)
#message.add_attachment(image_file,maintype='image',subtype='image_type')
message.add_attachment(image_file,maintype='image',subtype='image_type', filename='붕어빵.png')
smtp = smtplib.SMTP_SSL(SMTP_SERVER,SMTP_PORT)
smtp.login("00@gmail.com", "004")
smtp.send_message(message)
smtp.quit()

1) message.add_attachment(image_file,maintype='image',subtype='image_type')
=> 이렇게만 하면 이미지 파일이 txt 형태로 첨부가 됨

2) message.add_attachment(image_file,maintype='image',subtype='image_type', filename='붕어빵.png')
=> 이렇게 filename 까지 명시해주어야 이미지 파일로 제대로 간다

4) 유효성 검사

  • 이메일을 보낼 때, 그 이메일 주소가 존재하는지 정확히 확인하기 위해서 유효성 검사가 필요

이메일 정규표현식

^[a-zA-Z0-9.+_-]+@[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$
  • 이는 정규표현식을 사용하면 된다
  • 시작 = ^
  • 끝 = $
  • [a-zA-Z0-9.+_-]+ : a부터 z까지 A부터Z까지, 0부터 9까지, . , +, - , -
    이 중에서 하나 가져오라는 것
  • 뒤에 붙은 +의 갯수 따라서 달라짐, 여기선 + 가 하나 붙었으니깐 a부터 z까지 A부터Z까지, 0부터 9까지, . , +, - , - 여기에 속하는 거 하나 고르고, 또 한번 더 하라는 것임
  • (EX) hungry.example 라는 아이가 있다고 하면, a-z 하나, . 하나를 골라와서 총 두번 선택한 것 따라서 ㅇㅋ
  • \. => 점 필수라는 것!
  • [a-zA-Z]{2,3} => a부터 z까지, A부터 Z까지가 최소 2회, 최대 3번 반복

4-1 ) 유효성 검사 위해서 re import

import re

4-2 ) 이메일의 경우 정규표현식 작성 & 정규표현식에 맞나 체크

이메일 정규표현식 : ^[a-zA-Z0-9.+_-]+@[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$"

#re.match(체크정규표현식, 체크할대상) 로 정규표현식에 부합한가 체크한다

reg = "^[a-zA-Z0-9.+_-]+@[a-zA-Z0-9]+.[a-zA-Z]{2,3}$"

  • . : 이거는 정규표현식에서의 점이 아니고 진짜 요구로 하는 점이라는 표시야 라는 걸 위해 . 앞에 **** 넣어주는 것
print("test1 : " , re.match(reg, "j@a.com"))
print("test2 : " , re.match(reg, "ja.com"))

위의 코드를 프린트해본다면

이렇게 프린트 됨, match 되면 match 된다, 아니면 None

4-3 ) 메일 주소 잘못 보내면 전송 거부, 잘될 때만 전송 ㅇ 구현

def sendMail(addr) :
    reg = "^[a-zA-Z0-9.+_-]+@[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$"
    if bool(re.match(reg, "j@a.com")) :
        smtp.send_message(message)
        print("정상적으로 메일 발송")
    else :
        print("유효하지 않은 메일 주소입니다!")

=> 위와 같은 코드 써주고

원래
smtp.send_message() 있던 자리에
함수로 대체해서 넣어줌
sendMail(message["To"])

아래와 같이 메일주소가 정규표현식에 맞을 때만 전송 가능해짐 ㅇ

4-3 ) 정규표현식에 맞지 않으면 에러 출력하기

  • 에러 출력할 함수를 새롭게 정의하기

5) 에러

5-1 )

  • 우연인지.. 아닌지.. 내가 기막히게도 자꾸만 파일명을 이미 존재하는 모듈명으로 해서 동일한 에러가 두번이나 났다! 파이썬 안에 있는 모듈 이름으로 파일을 설정하면 발생하는 에러인 AttributeError: partially initialized module 'smtplib' has no attribute 'SMTP' (most likely due to a circular import)

=> 파일명 수정해주면 OK

참고 오버플로우

5-2 ) socket.gaierror: [Errno 11001] getaddrinfo failed

  • 링크 주소가 오타났을 경우에 , 내가 연결하고자 하는 IP 주소, 파이썬의 IP 주소가 맞지 않을 때 일어나는 에러

"stmp.gmail.com"

=> smtp를 stmp로 적어서 나타난 에러 ㅋ... 오타 조심하자!

참고 블로그

5-3 )

  • WINDOW 비밀번호를 새로
  • 컴퓨터용 앱을 만들었어야 한다.
  • 날씨 API 호출, kakaomap api
  • (+) 카카오맵 api 강의자료 추가적으로 만들기
  • (+) 동적크롤링, 강의자료 추가적으로 만들기
  • (+) api key, 이메일 환경변수로 빼서 관리하는 법

0개의 댓글

관련 채용 정보