GoでGmail APIを使ってメールを送信する
はじめに
Go で Gmail を送信したいと思って色々苦労したので自分用のメモ
今回は OAuth2 認証をして使用するやり方を書いていこうと思います
Project 作成
GCP コンソールに移動して以下のページでプロジェクトを作成
API を有効化
検索欄で Gmail と検索して API を有効にするボタンをクリック
OAuth 同意
スコープはちゃんと設定しないと使用できないので注意
スコープを追加または削除をクリック後フィルタで gmail と検索https://mail.google.com/にチェックマークを入れ更新して保存して次へ
テストユーザのところは自分の使っている google アカウントを入力
これで OAtuh 同意画面は終了
OAuth クライアント ID を作成する
次に認証情報を作成をクリックして OAuth クライアント ID を作成する
画像のように承認済みの URI に
http://localhost
と入力して作成
作成すると次の画面が出てくるので json をダウンロードします
アクセストークンを取得
ディレクトリ作成
mkdir gmailSend
モジュールを初期化
go mod init gmailSend
先ほどダウンロードした json ファイル名を を credentials.json に変更して下のように配置
.
├── credentials.json
├── go.mod
├── go.sum
└── main.go
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"google.golang.org/api/gmail/v1"
"google.golang.org/api/option"
)
func getClient(config *oauth2.Config) *http.Client {
tokFile := "token.json"
tok, err := tokenFromFile(tokFile)
if err != nil {
tok = getTokenFromWeb(config)
saveToken(tokFile, tok)
}
return config.Client(context.Background(), tok)
}
func getTokenFromWeb(config *oauth2.Config) *oauth2.Token {
authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
fmt.Printf("Go to the following link in your browser then type the "+
"authorization code: \n%v\n", authURL)
var authCode string
if _, err := fmt.Scan(&authCode); err != nil {
log.Fatalf("Unable to read authorization code: %v", err)
}
tok, err := config.Exchange(context.TODO(), authCode)
if err != nil {
log.Fatalf("Unable to retrieve token from web: %v", err)
}
return tok
}
func tokenFromFile(file string) (*oauth2.Token, error) {
f, err := os.Open(file)
if err != nil {
return nil, err
}
defer f.Close()
tok := &oauth2.Token{}
err = json.NewDecoder(f).Decode(tok)
return tok, err
}
func saveToken(path string, token *oauth2.Token) {
fmt.Printf("Saving credential file to: %s\n", path)
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
log.Fatalf("Unable to cache oauth token: %v", err)
}
defer f.Close()
json.NewEncoder(f).Encode(token)
}
func main() {
ctx := context.Background()
b, err := os.ReadFile("credentials.json")
if err != nil {
log.Fatalf("Unable to read client secret file: %v", err)
}
config, err := google.ConfigFromJSON(b, gmail.MailGoogleComScope)
if err != nil {
log.Fatalf("Unable to parse client secret file to config: %v", err)
}
client := getClient(config)
srv, err := gmail.NewService(ctx, option.WithHTTPClient(client))
if err != nil {
log.Fatalf("Unable to retrieve Gmail client: %v", err)
}
fmt.Println("Created Gmail service", srv)
}
必要なモジュールをインストール
go mod tidy
実行
go run main.go
実行すると コンソールに URL が表示されるのでコピーしてブラウザで開く
テストユーザで入力した google アカウントを選択すると以下の画面になるので続行を選択
URL の code=〇〇〇〇の〇〇部分をコピーして main.go を実行しているコンソールに貼り付ける
Saving credential file to: token.json と表示されローカルに token.json が作成されれば OK
メールを送信
package main
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"strings"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"golang.org/x/text/encoding/japanese"
"golang.org/x/text/transform"
"google.golang.org/api/gmail/v1"
"google.golang.org/api/option"
)
func getClient(config *oauth2.Config) *http.Client {
tokFile := "token.json"
tok, err := tokenFromFile(tokFile)
if err != nil {
tok = getTokenFromWeb(config)
saveToken(tokFile, tok)
}
return config.Client(context.Background(), tok)
}
func getTokenFromWeb(config *oauth2.Config) *oauth2.Token {
authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
fmt.Printf("Go to the following link in your browser then type the "+
"authorization code: \n%v\n", authURL)
var authCode string
if _, err := fmt.Scan(&authCode); err != nil {
log.Fatalf("Unable to read authorization code: %v", err)
}
tok, err := config.Exchange(context.TODO(), authCode)
if err != nil {
log.Fatalf("Unable to retrieve token from web: %v", err)
}
return tok
}
func tokenFromFile(file string) (*oauth2.Token, error) {
f, err := os.Open(file)
if err != nil {
return nil, err
}
defer f.Close()
tok := &oauth2.Token{}
err = json.NewDecoder(f).Decode(tok)
return tok, err
}
func saveToken(path string, token *oauth2.Token) {
fmt.Printf("Saving credential file to: %s\n", path)
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
log.Fatalf("Unable to cache oauth token: %v", err)
}
defer f.Close()
json.NewEncoder(f).Encode(token)
}
func main() {
ctx := context.Background()
b, err := os.ReadFile("credentials.json")
if err != nil {
log.Fatalf("Unable to read client secret file: %v", err)
}
config, err := google.ConfigFromJSON(b, gmail.MailGoogleComScope)
if err != nil {
log.Fatalf("Unable to parse client secret file to config: %v", err)
}
client := getClient(config)
srv, err := gmail.NewService(ctx, option.WithHTTPClient(client))
if err != nil {
log.Fatalf("Unable to retrieve Gmail client: %v", err)
}
fmt.Println("Created Gmail service", srv)
//追記
msgStr := "From: 'me'\r\n" +
"reply-to: hoge@gmail.com\r\n" + //送信元
"To: hogehoge@gmail.com\r\n" + //送信先
"Subject:あいさつ\r\n" +
"\r\n" + "hogeですhogehogeさんお元気ですか"
reader := strings.NewReader(msgStr)
transformer := japanese.ISO2022JP.NewEncoder()
msgISO2022JP, err := ioutil.ReadAll(transform.NewReader(reader, transformer))
if err != nil {
log.Fatalf("Unable to convert to ISO2022JP: %v", err)
}
msg := []byte(msgISO2022JP)
message := gmail.Message{}
message.Raw = base64.StdEncoding.EncodeToString(msg)
_, err = srv.Users.Messages.Send("me", &message).Do()
if err != nil {
fmt.Printf("%v", err)
}
}
件名が文字化けするので変換処理をしている
reader := strings.NewReader(msgStr)
transformer := japanese.ISO2022JP.NewEncoder()
msgISO2022JP, err := ioutil.ReadAll(transform.NewReader(reader, transformer))
これで無事メールが届いていれば完了
お疲れ様でした
参考
Discussion