Go + GmailAPIでサービスアカウントからメールを送信する

## はじめに
GOでGmailAPIからメールを送ろうとした時、
サービスアカウントを利用しようとすると一気に情報がなくなって苦戦したのでメモ

## やりたいこと
- GSuiteのドメイン内のユーザが送信するメールの自動化
- ユーザが認証をせず、ユーザのアドレスから送信したように見せかけたい

## いいたいこと
- サービスアカウントにドメインの権限を委任しよう
- サービスアカウントクライアントにGSuiteの権限を委任しよう
- JWTを利用した認証をしよう

今回のサンプルリポジトリ
https://github.com/siro33950/go-send-gmail-sample

## サービスアカウントとは
ユーザが操作をしなくても様々な権限を付与できるアカウント
https://cloud.google.com/iam/docs/service-accounts?hl=ja
> サービス アカウントは、個々のエンドユーザーではなく、アプリケーションや仮想マシン（VM）に属している特別な Google アカウントです。
> アプリケーションはサービス アカウントを使用して、ユーザーの関与を必要とせずに Google のサービス API を呼び出すことができます。

サービスアカウントを利用することで以下のメリットがある

1. ユーザがOAuth認証をしなくても権限付与できる
2. Token切れ等を心配しなくていい

## サービスアカウント作成

1. GCPで IAMと管理 -> サービスアカウント-> サービスアカウントを作成 と移動する
  ![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3131383134372f64666666623636322d616535652d623632642d303439382d6433336432303332633334362e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118147/7cb16f5f-8a38-4d6a-9c12-f385e5baf1aa.png)


2. サービスアカウント名だけ入力し、他オプションはデフォルトで作成
  ![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3131383134372f31323563303266382d326137312d643332312d356130322d6133393231303939303837652e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118147/9bb31966-ccd2-ec3c-675f-3a4c7c7a0e8e.png)


3. 今作ったサービスアカウントの詳細画面に移動
  ![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3131383134372f63313037646566302d376536392d303864322d653430612d6430346531363665383463352e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118147/b8c4972a-0e30-fcda-9513-f9817f75d3dc.png)


4. 「G Suite ドメイン全体の委任を有効にする」にチェック
  ![スクリーンショット 2019-12-10 19.05.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118147/876efc7b-ad90-5788-0a6a-9b8ab59fae7a.png)

5. 「キーを作成」からJOSNキーを作成
  ![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3131383134372f65316432353437622d366236342d393533632d653035302d3533653166383561666533662e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118147/af117996-1ad2-8418-a182-abe47089664a.png)



6. キーが作成されたのを確認したら設定を保存
  ![スクリーンショット 2019-12-10 19.08.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118147/5dcf2370-3081-8ca8-ff20-7ed6f83f317d.png)

## サービスアカウントに権限を委任

1. GSuiteで セキュリティ -> 設定 -> 詳細設定 -> APIクライアントアクセスを管理する に移動
  ![スクリーンショット 2019-12-10 19.31.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118147/b050a481-d5e3-b5b6-0b07-b690ff885d23.png)

2. クライアントIDにサービスアカウントのメールアドレス、スコープに利用したいAPIのスコープを記載して承認  
  スコープは[ここ](https://developers.google.com/gmail/api/auth/scopes)に一覧がある
  ![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3131383134372f30633539323362622d396531362d623664362d616665652d6265653438303430343066372e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118147/092ba86d-3a9d-a682-529c-71bd3b2b584c.png)

## キーを利用して認証

先ほど作成したJSONを利用して認証する
[GmailAPIのパッケージ](https://godoc.org/google.golang.org/api/gmail/v1)では4種類ほど認証方法が記載されているが、
今回やりたいことは***どの方法も上手くいかない***ので、JWTを利用した認証をする

```go
import (
	"context"
	"golang.org/x/oauth2/google"
	"google.golang.org/api/gmail/v1"
	"google.golang.org/api/option"
	"io/ioutil"
	"log"
)

func createService() (service *gmail.Service, err error) {
	// サービスアカウント作成時にダウンロードしたJSONを読み込む
	json, err := ioutil.ReadFile("./credential.json")
	if err != nil {
		log.Printf("[ERROR] Failed to process read file: %s", err)
		return nil, err
	}

	// スコープはGSuiteで指定した物をそのまま記述する
	config, err := google.JWTConfigFromJSON(json, gmail.MailGoogleComScope)
	if err != nil {
		log.Printf("[ERROR] Failed to process get jwt config: %s", err)
		return nil, err
	}
	// 送信元のアドレスを指定
	// 別のアドレスを指定すると実行時エラーになる
	config.Subject = "hoge@gmal.com"

	ctx := context.Background()
	tokenSource := config.TokenSource(ctx)
	return gmail.NewService(ctx, option.WithTokenSource(tokenSource))
}
```

## メールを送信

```go
import (
	"encoding/base64"
	"google.golang.org/api/gmail/v1"
	"log"
	"strings"
)

func sendMail() {
	// サービス作成
	service, err := createService()
	if err != nil {
		log.Printf("[ERROR] Failed to process create service: %s", err)
		return
	}

  // 本文を作成
  // FROMはSubjectに指定したアドレスか、そのアドレスが利用できるaliasを指定する
	temp := []byte("From: hoge@gmal.com\r\n" +
		"To: fuga@gmail.com\r\n" +
		"Subject: testSubject\r\n" +
		"\r\ntestBody")
	var message gmail.Message
	message.Raw = base64.StdEncoding.EncodeToString(temp)
	message.Raw = strings.Replace(message.Raw, "/", "_", -1)
	message.Raw = strings.Replace(message.Raw, "+", "-", -1)
	message.Raw = strings.Replace(message.Raw, "=", "", -1)
	
  // 送信
	_, err = service.Users.Messages.Send("me", &message).Do()
	if err != nil {
		log.Printf("[ERROR] Failed to process send message: %s", err)
		return
	}
}
```

## 参考
https://qiita.com/tnagao3000/items/3d210582bc7f1ca218cc
https://godoc.org/google.golang.org/api/gmail/v1