본문 바로가기
SW 개발/REST API

Google gmail API 사용 방법 (3) - Sample code

by Kibua20 2022. 1. 9.

Gmail API 사용 방법에 대한 3번째 포스트입니다.  앞서 2개의 포스트에서는 1) Google API Console에서 프로젝트를 생성하고, Client ID와 sescret을 받는 과정과 2) 구글 인증 서버에 계정 로그인을 통해서 API scope에 대한 명시적 동의를 받아 서버 인증용 access token을 받는 과정을 설명하였다. 본 게시글은 access token을 사용해서 실제 Google REST API에 호출하는 방법과 샘플 코드를 제공한다.  

 

[모바일/REST API] - Google Gmail API 사용 방법 (1)

[모바일/REST API] - Google Gmail API 사용 방법 (2)

 

 

Google API 호출을 위한 전체 Flow (Web Server Application flow로 진행)

 

Gmail API 사용: curl 명령어 확인하기 

구글 API 사용하기 위해서는 API Reference 사이트에서 원하는 기능의 API를 찾아야 한다.  본 게시글에서는 Gmail 서버를 사용해서 email 전송 구현이 목표이며, 사용할 수 있는 API는 Users.Messages:send 이다.  API Reference에서는 API 호출을 위한 http Request를 위한 header paramter, Authorization scope, Response body, sample code 가 설명되어 있다. 

 

Users.Messages:sendhttps://www.googleapis.com/upload/gmail/v1/users/userId/messages/send URL로 http request의 Post함수를 사용한다. userID는 앞서 설정한 gmail 사용자 계정을 의미하고 string (e.g. kibua20@gmail.com)으로 입력해야 한다.  Request Body는 RFC 2822 양식으로 base64url encoding 값으로 raw 포맷으로 구성할 수 있고, API 에 대한 response로 성공인 경우 200 OK를 전달하고 message id를 리턴한다.  

 

(1) HTTP request
  POST https://www.googleapis.com/upload/gmail/v1/users/userId/messages/send

(2) Parameter
  userId string The user's email address. The special value me can be used to indicate the authenticated user

(3) Request body
   Property nameValueDescriptionNotesRequired Propertiesrawbytes. The entire email message in an RFC 2822 formatted and base64url encoded string. Returned in messages.get and drafts.get responses when the format=RAW parameter is supplied.

(4) Response
   If successful, this method returns a Users.messages resource in the response body.

Google API Reference - email 전송은 Users.Messages:send API를 사용한다

 

API를 Python code로 바로 변환하는 것 보다는 curl를 사용해서 http request 시 header, request body, response의 동작을 먼저 확인해서 검증 후에 code로 변환하는 것이 개발 시간을 줄이는데 도움이 된다. Google API 의 경우에는 API reference site에서 curl 명령어를 직접 사용할 수 있는 도구를 제공한다.  (API reference site의 "Try This API" )  만일 curl 이 동작이 잘된다면 curl 을 python code로 자동변환도 가능하다. (변환 사이트)

 

Google Try This API 에서는 아래와 같이 입력한다.

 

1) User ID 설정: kibua20@gmail.com

2) JSON 의 raw 내용: Email이 내용을 base64 url로 encoding 한 값이다. 아래 Python 코드에서 CreateMessage()의 return 값을 마우스로 copy & Paste로 입력한다.

3) 실행 함수를 누르면 계정 로그인 및 사용자 권한 동의 팝업이 표시되고, OK를 누르면 실제 email 이 전송된다.

4) Email 전송이 완료가 되면 구글 서버에 response 200 OK와 함께 message ID 값을 전달한다. 

 

Google Gmail API: curl 로 http request의 header, body 구성 방법을 확인할 수 있다.

 

결과적으로 curl 명령어는 아래와 같고,  Python code를 옮길 때에는 access token과 JSON의 raw 값을 채워야 한다. Access token을 사용하는 경우 API Key 설정은 필요 없다. 

 

curl --request POST \
'https://www.googleapis.com/gmail/v1/users/[USERID]/messages/send?key=[YOUR_API_KEY]' \
--header 'Authorization: Bearer [YOUR_ACCESS_TOKEN]' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data '{"raw":""}' \
--compressed

 

 

Gmail API 사용: Python code 작성하기

Gmail API 가 curl로 http request가 정상 동작하는 것을 확인하였으면, 다음 단계는 http request내용을 그대로 Python code를 옮기는 과정이다.  아래 Python sample code는 Git Hub에서 다운로드할 수 있다. 

 

Gmail 전송을 위한 Python code는 크게 2개의 CreateMessage()와 sendmail() 함수로 구성되어 있다. CreateMessage() 함수는Email  sender 주소, 받는 사람 주소, 제목, 메시지를 받아서 base64 encoding 스트링을 구성하는 함수이고, sendmail() 함수는 curl로 확인한 http request의 header와 body를 구성하여 실제 http 전송을 실행하는 함수이다. 

 

아래 code에서는 일반 text 파일도 html를 mime type으로 전송하고, message에 html tag (e.g. <br>, <h1>)를 사용할 수 있다. 첨부 파일은 이미지, 오디오, 텍스트 파일 등으로 첨부가 가능하다.   sendmail() 함수 중간에 payload 값을 콘솔에서 copy 해서 Google API reference 사이트에 입력하면 API의 동작을 확인할 수 있다.  아래 code에서 access token과 email address는 각자 등록한 계정에 맞는 값을 사용해야 한다. 

 

https://github.com/kibua20/devDocs/tree/master/gmail

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import base64
from email.mime.audio import MIMEAudio
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import mimetypes
import os
import requests
import json
import sys

# -----------------------------------------------------------------------------------------
def CreateMessage(sender, to, subject, message_text=None, attachment_path=None):
  """Create a message for an email.

  Args:
    sender: Email address of the sender.
    to: Email address of the receiver.
    subject: The subject of the email message.
    message_text: The text of the email message.
    attachment_path: Full path for attachment (dir+filename)


  Returns:
    An object containing a base64url encoded email object.
  """
  message = MIMEMultipart()
  message['to'] = to
  message['from'] = sender
  message['subject'] = subject
  #message['cc'] = cc
  #message['bcc'] = cc
  #msg.add_header('reply-to', return_addr)

  if message_text != None:
    msg = MIMEText(message_text, 'html')
    message.attach(msg)

  if attachment_path != None:
    content_type, encoding = mimetypes.guess_type(attachment_path)

    if content_type is None or encoding is not None:
        content_type = 'application/octet-stream'
    main_type, sub_type = content_type.split('/', 1)
    if main_type == 'text':
        fp = open(attachment_path, 'rb')
        msg = MIMEText(fp.read().decode('utf8'), _subtype=sub_type)
        fp.close()
    elif main_type == 'image':
        fp = open(attachment_path, 'rb')
        msg = MIMEImage(fp.read(), _subtype=sub_type)
        fp.close()
    elif main_type == 'audio':
        fp = open(attachment_path, 'rb')
        msg = MIMEAudio(fp.read(), _subtype=sub_type)
        fp.close()
    else:
        fp = open(attachment_path, 'rb')
        msg = MIMEBase(main_type, sub_type)
        msg.set_payload(fp.read())
        fp.close()

    msg.add_header('Content-Disposition', 'attachment', filename=os.path.basename(attachment_path))
    message.attach(msg)

  try:
    return {'raw': base64.urlsafe_b64encode(message.as_string())}
  except:
    return {'raw': base64.urlsafe_b64encode(message.as_bytes()).decode('utf8')}

# -----------------------------------------------------------------------------------------
def sendmail(to, subject, message_text_html, attachment=None):
  # Your access code 
  access_token = 'ya29.aaaaaaaa'
  user_id = 'your id@gmail.com'

  URL = 'https://www.googleapis.com/gmail/v1/users/'+user_id+'/messages/send'

  request_header = {
      "Content-Type": "application/json",
      "Authorization": "Bearer " + access_token,
      "X-GFE-SSL": "yes"
  }

  payload = CreateMessage(user_id, to, subject, message_text_html, attachment)

  # validate payload on https://developers.google.com/gmail/api/v1/reference/users/messages/send
  # print (payload)
  # exit(1)

  response = requests.post(URL, headers=request_header, data=json.dumps(payload))

  print ('*** Error *** : sendmail fail')
  print(response)
  print(response.text)

# -----------------------------------------------------------------------------------------
if __name__ == "__main__":
    sendmail('receiver@gmail.com', 'subject_test', 'message_test', None)

 

Reply 형식으로 메일 회신하기 

댓글 중에 공유가 필요한 내용이 있어 업데이트 합니다. 원문은 GitHub 링크에 있습니다. 아래 내용은 갓태희님이 작성하신 내용입니다. 요점은 message 내용에 threadId를 추가하면 된다고 합니다.  


블로그 댓글을 작성하면 코드가 잘 작성되지 않아 이렇게 깃헙이슈로 남기게 됬습니다. 해당 게시글에 더해 Reply를 보내는 방법을 찾다가 해답을 찾아서 이렇게 남기게 됬습니다. 선생님이 블로그에 작성해주신 코드 주소의 70~73번째 줄의

  try:
    return {'raw': base64.urlsafe_b64encode(message.as_string())}
  except:
    return {'raw': base64.urlsafe_b64encode(message.as_bytes()).decode('utf8')}

위에 해당하는 코드를

    try:
        return {'raw': base64.urlsafe_b64encode(message.as_string()), 'threadId': '스레드 ID(숫자)'}
    except:
        return {'raw': base64.urlsafe_b64encode(message.as_bytes()).decode('utf8'), 'threadId': '스레드 ID(숫자)'}

로 바꾸어 주었더니 해당 스레드에 Reply형식으로 메일이 보내졌습니다.

 

스레드라 함은 알고보니 그냥 한개의 메일을 Gmail에서는 스레드라고 하는것 같았습니다.

위의 사진처럼 스레드 ID는 단순히 스레드 ID를 알고싶은 메일에 들어가서 주소창을 보면 맨끝  FMfcgzGmtNdjmjGGmWGwgxVvzqQrXdfh와 같은 문자로 알수 있는데요.

이 문자는 스레드 ID가 맞긴 하지만 실제로 저희의 Python 코드에는 숫자형식의 스레드 ID가 들어가야했습니다. 그래서 해당 문자를 숫자형식의 스레드 ID로 바꾸는 방법은 여기에서 알수 있었습니다.

document.querySelector('[data-legacy-thread-id]').getAttribute('data-legacy-thread-id')

해당 JS를 확인하고싶은 메일에 들어가서 개발자도구를 열어 console창에 복붙하시게 되면 숫자 형식의 스레드 아이디를 확인하실수 있습니다. 이렇게 얻은 스레드 ID를 저희 파이썬 코드에 넣으면 정상적으로 Reply 메일이 보내집니다. 다시한번 훌륭한 글을 작성해주셔서 너무 감사드립니다!!


 

관련 글:

[모바일/REST API] - Google Gmail API 사용 방법 (1)

[모바일/REST API] - Google gmail API 사용 방법 (3)

[모바일/Python] - Python 표준 입출력(stdin/stdout) 활용 - 리눅스 프로그램과 연동

[모바일/Python] - Python JSON 사용 시 TypeError: Object of type bytes is not JSON serializable

[모바일/Python] - Python smtplib 사용한 email 발송 예제 (gmail)

[개발환경] - [Memo] 우분투에서 gmail 활용하여 command line으로 email 전송

[모바일/REST API] - 우분투 20.04에서 Web 서버 설치 방법 (apache2, tomcat9)

 




댓글