Go言語・SendGrid・REST APIでお問い合わせメール送信API

## 概要
Go言語を使って、Jsonファイル形式で送られてきたデータをメールで送信する。
モバイル開発において、お問い合わせフォームの内容をWebを使って送信する必要があったので、モバイル側から送られてきたデータをWebを通してメール送信をするプログラムを作成した。
OSはWindows

### 参照した記事
https://sendgrid.kke.co.jp/blog/?p=1241

https://qiita.com/katekichi/items/d94e078b376151858ca4

## 事前準備
まずメール送信を肩代わりしてくれるサービスSendGridを登録する必要がある。
申請をして、早くて数時間、遅くても1営業日で審査は終わる。SendGridは月12,000通までのメール送信なら無料で使える。

https://app.sendgrid.com/

審査が通ればログインしてAPIキーを所得。
Settings → API Keys → Create API Key(青いボタン)を押す。Permission はとりあえず Full Access で大丈夫。
API Key はどこかに保存しておく。API Key は生成時以外は確認できないので、忘れたら Key を作り直すことになる。（キーを失ったら作り直せばいい）

## 準備するファイル

~~~:ファイル構造
go_sendmail/
  ├ handler/
  │    └PostEmail.go
  ├ .env
  └ main.go
~~~

以下コード内容

~~~go:PostEmail.go
package handler

import (
	"fmt"
	"log"
	"os"
	"strings"

	"github.com/sendgrid/sendgrid-go"
	"github.com/sendgrid/sendgrid-go/helpers/mail"
	"github.com/joho/godotenv"

	"github.com/ant0ine/go-json-rest/rest"
	"net/http"
	"sync"
)

//メールの構造体
type Mail struct {
	Name    string
	Subject string
	Text    string
	Email   string
}

var store = map[string]*Mail{}

var lock = sync.RWMutex{}

//送信する関数
func PostMail(w rest.ResponseWriter, r *rest.Request) {
	send := Mail{}
	err := r.DecodeJsonPayload(&send)//sendにpost値を入れる
	if err != nil {
			rest.Error(w, err.Error(), http.StatusInternalServerError)
			return
	}

	if send.Email == "" {
			rest.Error(w, "mail required", 400)
			return
	}
	
	lock.Lock()
	store[send.Name] = &send
	lock.Unlock()

	err_read := godotenv.Load()
	if err_read != nil {
		log.Fatalf("error: %v", err_read)
	}

	// .envから環境変数読み込み
	API_KEY := os.Getenv("API_KEY")
	TOS := strings.Split(os.Getenv("TOS"), ",")
	fr := send.Email//postした値に含めたメールアドレス（送信者のメアド）

	// メッセージの構築
	message := mail.NewV3Mail()
	// 送信元を設定
	from := mail.NewEmail("", fr)
	message.SetFrom(from)

	// 宛先と対応するSubstitutionタグを指定(宛先は複数指定可能)
	p := mail.NewPersonalization()
	to := mail.NewEmail("", TOS[0])
	p.AddTos(to)

	//残りのpostされた値を変数に格納
	name := send.Name
	subject := send.Subject
	text := send.Text

	p.SetSubstitution("%name%", name)
	p.SetSubstitution("%m_subject%", subject)
	p.SetSubstitution("%m_text%", text)
	message.AddPersonalizations(p)

	// 2つ目の宛先と、対応するSubstitutionタグを指定
	// p2 := mail.NewPersonalization()
	// to2 := mail.NewEmail("", TOS[1])
	// p2.AddTos(to2)
	// p2.SetSubstitution("%name%", name)
	// p2.SetSubstitution("%m_subject%", subject)
	// p2.SetSubstitution("%m_text%", text)
	// message.AddPersonalizations(p2)

	// 3つ目の宛先と、対応するSubstitutionタグを指定
	// p3 := mail.NewPersonalization()
	// to3 := mail.NewEmail("", TOS[2])
	// p3.AddTos(to3)
	// p3.SetSubstitution("%name%", name)
	// p3.SetSubstitution("%m_subject%", subject)
	// p3.SetSubstitution("%m_text%", text)
	// message.AddPersonalizations(p3)

	// 件名を設定
	message.Subject = "%m_subject%"
	// テキストパートを設定
	c := mail.NewContent("text/plain", "%m_text%\r\n")
	message.AddContent(c)
	// HTMLパートを設定
	//c = mail.NewContent("text/html", "<strong> %name% さんは何をしていますか？</strong><br>　文章ー－－。")
	// message.AddContent(c)

	// カテゴリ情報を付加
	message.AddCategories("category1")
	// カスタムヘッダを指定
	message.SetHeader("X-Sent-Using", "SendGrid-API")
	// 画像ファイルを添付
	// a := mail.NewAttachment()
	// file, _ := os.OpenFile("./gif.gif", os.O_RDONLY, 0600)
	// defer file.Close()
	// data, _ := ioutil.ReadAll(file)
	// data_enc := base64.StdEncoding.EncodeToString(data)
	// a.SetContent(data_enc)
	// a.SetType("image/gif")
	// a.SetFilename("owl.gif")
	// a.SetDisposition("attachment")
	// message.AddAttachment(a)

	// メール送信を行い、レスポンスを表示
	client := sendgrid.NewSendClient(API_KEY)
	response, err := client.Send(message)
	if err != nil {
		log.Println(err)
	} else {
		fmt.Println(response.StatusCode)
		fmt.Println(response.Body)
		fmt.Println(response.Headers)
	}
	w.WriteJson(&send)
}
~~~
~~~go:main.go
package main

import (
	"go_sendmail/handler"
	"log"
	"net/http"

	"github.com/ant0ine/go-json-rest/rest"
)

func main() {
	//REST APIでpost処理を実現
	api := rest.NewApi()
	api.Use(rest.DefaultDevStack...)
	router, err := rest.MakeRouter(
		rest.Post("/post", handler.PostEmail),
	)
	if err != nil {
		log.Fatal(err)
	}
	api.SetApp(router)
	log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}
~~~
~~~:.env
API_KEY=your_api_key #←ここにSendGridのAPIキーを入力
TOS=you@youremail.com,friend1@friendemail.com,friend2@friendemail.com
# TOSにはメールが届いてほしいメールアドレスを入れる。「,」で区切ることで複数指定できる。
# とりあえず「,」は使わずに、メールアドレスは一つでいい。
~~~

上記の.envファイルの1行目に、先ほど保存したSendGridのAPIキーを入れておく。
まだgoファイルのimportの部分でエラーが表示されている状態かと。

## 実行
まずGo言語お決まりのgo.modファイルを生成するために、カレントディレクトリがgo_sendmailの状態でコマンド

~~~
$ go mod init go_sendmail
~~~

を実行。
次にパッケージのインストール。以下のコマンドを1行ずつ実行。

~~~
$ go get "github.com/sendgrid/sendgrid-go"
$ go get "github.com/sendgrid/sendgrid-go/helpers/mail"
$ go get "github.com/joho/godotenv"
$ go get "github.com/ant0ine/go-json-rest/rest"
~~~

これができたら

~~~
$ go run main.go
~~~

で実行。もしlocalhostがアクセスの許可を聞いてきたら許可する。
そしてコマンドプロンプトを新たに開いて、カレントディレクトリはどこでもいいので

~~~
$ curl -i -H "Content-Type: application/json" -d "{\"Name\":\"taro\",\"Subject\":\"test\",\"Text\":\"hello\",\"Email\":\"example@email.com\"}" http://localhost:8080/post
~~~

を実行。
これはcurlコマンドでデータを、mail.goで立ち上げたサーバーにPostしている。最終的にはモバイル側がこの処理をすることを想定しているが、今はテスト段階なので手動でコマンドを使っているだけ。
example@email.comの部分はメール送信者（アプリユーザ）のメールアドレスだからなんでもいい。
「"（ダブルクォーテーション）」の前にいちいち「\（環境によっては¥）」を入れているのは、WindowsのコマンドプロンプトにおいてJson形式の中身の「"」を念のためエスケープしているため。
Windows以外を使ったりしていれば

~~~
$ curl -i -H "Content-Type: application/json" -d "{"Name":"taro","Subject":"test","Text":"hello","Email":"example@email.com"}" http://localhost:8080/post
~~~

で実行すればいいかも。
とにかくこれで、.envファイルのTOSの値に指定したメールアドレスにメールが届いていれば成功。
ちなみにコマンドプロンプトの実行後の画面は以下の通り。
![2022-02-16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2388259/7aa0a56c-38c7-ffc8-bbc1-20d46f32a6cd.png)