【Python】Gmail API を使ってメールを送信する 

# Gmail API を使ってみる
[Gmail API のガイド](https://developers.google.com/gmail/api/guides?hl=ja)を参考に Gmail アカウントからメール送信を試した際に、躓いた点について備忘録としてまとめておこうと思います。

### サンプルコードをとりあえず実行してみる
[Gmail API ガイドのメール送信](https://developers.google.com/gmail/api/guides/sending?hl=ja)に記載されている「send_message.py」のコードを参考にメール送信を試してみました。
が、ガイドに記載されている以下のコードをコピペして実行してみても、そのままでは動きません。

```python:send_message.py
import base64
from email.message import EmailMessage

import google.auth
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError


def gmail_send_message():
  """Create and send an email message
  Print the returned  message id
  Returns: Message object, including message id

  Load pre-authorized user credentials from the environment.
  TODO(developer) - See https://developers.google.com/identity
  for guides on implementing OAuth2 for the application.
  """
  creds, _ = google.auth.default()

  try:
    service = build("gmail", "v1", credentials=creds)
    message = EmailMessage()

    message.set_content("This is automated draft mail")

    message["To"] = "gduser1@workspacesamples.dev"
    message["From"] = "gduser2@workspacesamples.dev"
    message["Subject"] = "Automated draft"

    # encoded message
    encoded_message = base64.urlsafe_b64encode(message.as_bytes()).decode()

    create_message = {"raw": encoded_message}
    # pylint: disable=E1101
    send_message = (
        service.users()
        .messages()
        .send(userId="me", body=create_message)
        .execute()
    )
    print(f'Message Id: {send_message["id"]}')
  except HttpError as error:
    print(f"An error occurred: {error}")
    send_message = None
  return send_message


if __name__ == "__main__":
  gmail_send_message()
```
### クイックスタートのサンプルコードを確認
なぜ動かないのかというと、認証に関する処理が記述されていない為です。
Gmail API を使うには、事前に oAuth を利用してユーザーの認証を行う必要があります。認証処理のコードは [クイックスタート](https://developers.google.com/gmail/api/quickstart/python?hl=ja) に記載されている「quickstart.py」が参考になります。
以下のコードのうち、main() の中にある認証処理の箇所(28~49行目辺り)を「send_message.py」に追加すると動きそうです。

```python:send_message.py
import os.path

from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError

# If modifying these scopes, delete the file token.json.
SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"]


def main():
  """Shows basic usage of the Gmail API.
  Lists the user's Gmail labels.
  """
  creds = None
  # The file token.json stores the user's access and refresh tokens, and is
  # created automatically when the authorization flow completes for the first
  # time.
  if os.path.exists("token.json"):
    creds = Credentials.from_authorized_user_file("token.json", SCOPES)
  # If there are no (valid) credentials available, let the user log in.
  if not creds or not creds.valid:
    if creds and creds.expired and creds.refresh_token:
      creds.refresh(Request())
    else:
      flow = InstalledAppFlow.from_client_secrets_file(
          "credentials.json", SCOPES
      )
      creds = flow.run_local_server(port=0)
    # Save the credentials for the next run
    with open("token.json", "w") as token:
      token.write(creds.to_json())

  try:
    # Call the Gmail API
    service = build("gmail", "v1", credentials=creds)
    results = service.users().labels().list(userId="me").execute()
    labels = results.get("labels", [])

    if not labels:
      print("No labels found.")
      return
    print("Labels:")
    for label in labels:
      print(label["name"])

  except HttpError as error:
    # TODO(developer) - Handle errors from gmail API.
    print(f"An error occurred: {error}")


if __name__ == "__main__":
  main()
```

### コードを組み合わせて実行してみる
認証処理を追加したソースコードは以下になります。
「quickstart.py」の認証処理を get_token() 関数としてまとめて、関数の戻り値として creds オブジェクトをリターンするようにしました。
最後に main() 関数の中で認証処理とメール送信の関数を呼び出して、メールを送信しています。

```python:send_message_withAuth.py
import base64
from email.message import EmailMessage

import google.auth
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError

import os.path

from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow


SCOPES = ['https://mail.google.com/']

def get_token():
    creds = None
    if os.path.exists('token.json'):
        creds = Credentials.from_authorized_user_file('token.json', SCOPES)
    # If there are no (valid) credentials available, let the user log in.
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                'credentials.json', SCOPES)
            creds = flow.run_local_server(port=0)

        with open('token.json', 'w') as token:
            token.write(creds.to_json())
    
    return creds


def gmail_send_message(creds):
    try:
       service = build("gmail", "v1", credentials=creds)
       message = EmailMessage()

       message.set_content("This is automated draft mail")
       message["To"] = "XXXXXXXX@XXXXXXXX.jp"
       message["From"] = "gduser2@workspacesamples.dev"
       message["Subject"] = "Automated draft"

       # encoded message
       encoded_message = base64.urlsafe_b64encode(message.as_bytes()).decode()

       create_message = {"raw": encoded_message}
       # pylint: disable=E1101
       send_message = service.users().messages().send(userId="me", body=create_message).execute()
       print(f'Message Id: {send_message["id"]}')
    except HttpError as error:
      print(f"An error occurred: {error}")
      send_message = None
    return send_message


if __name__ == "__main__":
    creds = get_token()
    gmail_send_message(creds)
```

こちらを実行することで、無事にメールの送信を行うことが出来ました。