blastengineを使ってGo言語でメールを送信する

[blastengine](https://blastengine.jp/)はシンプルに使える開発者向けメールサービスを提供しています。何かと面倒なメール配信をシンプルに、かつ確実に送信先に届くようになります。

今回はblastengineを使って、Go言語でメール送信を行うまでの流れを解説します。

## ユーザ登録する

blastengineにユーザ登録します。管理画面に入るためのユーザID、パスワードが手に入るので、ログインします（ユーザIDは後で使います）。

![getting-started-4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/5c6c8cb0-9ad1-0a6d-88fe-e24bc3ee7b17.jpeg)


## 送信元ドメインのSPFを設定する

送信元として利用するドメイン（自分で持っているもの）の設定をします。これは任意のドメイン管理サービスで設定できますが、TXTレコードに以下のSPFを追加します。

```
txt @ v=spf1 include:spf.besender.jp ~all
```

### APIキーを取得する

ログイン後、管理画面の右上にある設定メニューに移動します。

![getting-started-6.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/78a4eeb3-4649-149f-8246-44f83d03a0cf.jpeg)


そして設定の中で、APIキーを取得します。

![getting-started-5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/d21fc4e3-36f0-6f5c-23e3-8c675e16f538.png)

### SMTP用のパスワードを設定する

同じくSMTPリレーの設定で、SMTP用のパスワードを設定できます。また、IPアドレスベースの認証も可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/e615c636-8bf6-0937-6ed6-d06e933b84c7.png)


## プロジェクトを作成する

プロジェクトを作成します。

```bash
mkdir blastengine-go
cd blastengine-go
```

初期化します。

```bash
go mod init mail
```

## SMTP経由で配信する

まずはSMTPリレーを使ってメールを送信する流れを紹介します。利用したライブラリは以下の通りです。

- [wneessen/go-mail](https://github.com/wneessen/go-mail)

### ライブラリをインストールする

ライブラリをインストールします。

```bash
go get github.com/wneessen/go-mail
```

### コードの記述

`mail.go` というファイルを作成します。内容は次の通りです。

```go
package main

import (
	"log"
	"mime"
	"github.com/wneessen/go-mail"
)

func main() {
	// この中に処理を記述します
}
```

#### 必要な情報

メール送信時に利用する情報を設定します。パスワードはSMTP用のパスワードを設定します。SMTPホストは `smtp.engn.jp` で、ポート番号は25/587/2525より選択できます。

```go
username := "YOUR_SMTP_USER"
password := "YOUR_SMTP_PASSWORD"
toAddress := "user@example.jp"
fromAddress := "admin@example.com"
smtpHost := "smtp.engn.jp"
smtpPort := 587
```

#### メールクライアントの作成

設定した情報に基づいて、メールクライアントを作成します。

```go
c, err := mail.NewClient(smtpHost, mail.WithPort(smtpPort), mail.WithSMTPAuth(mail.SMTPAuthPlain),
	mail.WithUsername(username), mail.WithPassword(password))
if err != nil {
	log.Fatalf("メールクライアントの生成に失敗: %s", err)
}
```

#### メッセージの作成

メッセージを作成します。日本語の件名の場合は `mime.BEncoding.Encode` を使ってエンコードします。

```go
m := mail.NewMsg()
m.Subject(mime.BEncoding.Encode("UTF-8", "こんにちは from blastengine"))
m.SetBodyString(mail.TypeTextPlain, "日本語の本文")
m.SetBodyString(mail.TypeTextHTML, "<html><body>日本語の本文</body></html>")
if err := m.From(fromAddress); err != nil {
	log.Fatalf("Fromアドレスの設定に失敗: %s", err)
}
if err := m.To(toAddress); err != nil {
	log.Fatalf("Toアドレスの設定に失敗: %s", err)
}
```

#### メール送信

メールを送信します。

```go
if err := c.DialAndSend(m); err != nil {
	log.Fatalf("送信失敗: %s", err)
}
log.Printf("メール送信しました: %t", m.IsDelivered())
c.Close()
```

#### 全体のコード

SMTP経由でメールを送信するコードは次の通りです。

```go
package main

import (
	"log"
	"mime"

	"github.com/wneessen/go-mail"
)

func main() {
	username := "YOUR_SMTP_USER"
	password := "YOUR_SMTP_PASSWORD"
	toAddress := "user@example.jp"
	fromAddress := "admin@example.com"
	smtpHost := "smtp.engn.jp"
	smtpPort := 587
	c, err := mail.NewClient(smtpHost, mail.WithPort(smtpPort), mail.WithSMTPAuth(mail.SMTPAuthPlain),
		mail.WithUsername(username), mail.WithPassword(password))
	if err != nil {
		log.Fatalf("メールクライアントの生成に失敗: %s", err)
	}
	m := mail.NewMsg()
	m.Subject(mime.BEncoding.Encode("UTF-8", "こんにちは from blastengine"))
	m.SetBodyString(mail.TypeTextPlain, "日本語の本文")
	m.SetBodyString(mail.TypeTextHTML, "<html><body>日本語の本文</body></html>")
	if err := m.From(fromAddress); err != nil {
		log.Fatalf("Fromアドレスの設定に失敗: %s", err)
	}
	if err := m.To(toAddress); err != nil {
		log.Fatalf("Toアドレスの設定に失敗: %s", err)
	}
	if err := c.DialAndSend(m); err != nil {
		log.Fatalf("送信失敗: %s", err)
	}
	log.Printf("メール送信しました: %t", m.IsDelivered())
	c.Close()
}
```

## API経由で配信する

次にAPIを使ってHTMLメールを配信する流れを紹介します。今回利用したライブラリは以下の通りです。すべて標準ライブラリのみです。

```go
import (
	"bytes"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
)
```

### コードの記述

先ほど同じく `mail.go` の中に記述していきます。

```go
package main

import (
	"bytes"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
)

func main() {
	// この中に処理を記述します
}
```

### 必要な情報を設定

API経由で送信する際に必要な情報は、APIエンドポイント・ユーザID・APIキーです。それぞれの情報を設定します。

```go
apiEndpoint := "https://app.engn.jp/api/v1/deliveries/transaction"
apiUser := "YOUR_API_USER"
apiKey := "YOUR_API_KEY"
```

### 構造体の定義

APIリクエストに利用する構造体を定義します。

```go
type MailAddress struct {
	Name    string `json:"name"`
	Address string `json:"email"`
}

type InsertCode struct {
	Key   string `json:"key"`
	Value string `json:"value"`
}

type ListUnsubscribe struct {
	MailTo string `json:"mailto"`
	Url    string `json:"url"`
}

type EMail struct {
	To              string           `json:"to"`
	From            MailAddress      `json:"from"`
	Cc              []*string        `json:"cc"`
	Bcc             []*string        `json:"bcc"`
	InsertCode      []*InsertCode    `json:"insert_code"`
	Subject         string           `json:"subject"`
	ListUnsubscribe *ListUnsubscribe `json:"list_unsubscribe"`
	Encode          string           `json:"encode"`
	TextPart        string           `json:"text_part"`
	HtmlPart        string           `json:"html_part"`
}

// APIレスポンス
type Response struct {
	DeliveryId int `json:"delivery_id"`
}
```

### トークンを生成する

APIリクエストするためのトークンを生成します。手順としては次のとおりです。

1. ユーザIDとAPIキーを連結する
2. SHA256のハッシュを生成する
3. ハッシュを全て小文字にする
4. 3の文字列をBASE64エンコードする

実際のコードで言うと、次のようになります。 `token` がトークンです。

```go
// トークンの生成
hash := sha256.Sum256([]byte(apiUser + apiKey))
token := base64.URLEncoding.EncodeToString([]byte(hex.EncodeToString(hash[:])))
```

### APIリクエストする

では実際にメールを送信します。APIのエンドポイントは `https://app.engn.jp/api/v1/deliveries/transaction` になります。生成したトークンは `Authorization` ヘッダーに適用します。fromのemailやtoなど、メールアドレスは利用されるものに書き換えてください。

```go
// メールの作成
email := EMail{
	To: "user@example.jp",
	From: MailAddress{
		Name:    "管理者",
		Address: "admin@example.com",
	},
	Subject:  "テストメール from API",
	Encode:   "UTF-8",
	TextPart: `こんにちは、これはテストメールです。`,
	HtmlPart: `<html><body><h1>こんにちは、これはテストメールです。</h1></body></html>`,
}
jsonBytes, err := json.Marshal(email)
if err != nil {
	panic(err)
}
fmt.Println(string(jsonBytes))
req, err := http.NewRequest("POST", apiEndpoint, bytes.NewBuffer(jsonBytes))
if err != nil {
	panic(err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
	panic(err)
}
defer resp.Body.Close()
```

### 結果

レスポンスは以下のようになっています。

```json
{
	"delivery_id": 9
}
```

これを `json.Unmarshal` でパースして利用します。

```go
var response Response
err = json.Unmarshal(body, &response)
if err != nil {
	panic(err)
}
fmt.Println(response.DeliveryId)
```

#### 全体のコード

API経由でメールを送信するコードは次の通りです。

```go
package main

import (
	"bytes"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
)

type MailAddress struct {
	Name    string `json:"name"`
	Address string `json:"email"`
}

type InsertCode struct {
	Key   string `json:"key"`
	Value string `json:"value"`
}

type ListUnsubscribe struct {
	MailTo string `json:"mailto"`
	Url    string `json:"url"`
}

type EMail struct {
	To              string           `json:"to"`
	From            MailAddress      `json:"from"`
	Cc              []*string        `json:"cc"`
	Bcc             []*string        `json:"bcc"`
	InsertCode      []*InsertCode    `json:"insert_code"`
	Subject         string           `json:"subject"`
	ListUnsubscribe *ListUnsubscribe `json:"list_unsubscribe"`
	Encode          string           `json:"encode"`
	TextPart        string           `json:"text_part"`
	HtmlPart        string           `json:"html_part"`
}

type Response struct {
	DeliveryId int `json:"delivery_id"`
}

func main() {
	apiEndpoint := "https://app.engn.jp/api/v1/deliveries/transaction"
	apiUser := "YOUR_API_USER"
	apiKey := "YOUR_API_KEY"
	// トークンの生成
	hash := sha256.Sum256([]byte(apiUser + apiKey))
	token := base64.URLEncoding.EncodeToString([]byte(hex.EncodeToString(hash[:])))
	// メールの作成
	email := EMail{
		To: "user@example.jp",
		From: MailAddress{
			Name:    "管理者",
			Address: "admin@example.com",
		},
		Subject:  "テストメール from API",
		Encode:   "UTF-8",
		TextPart: `こんにちは、これはテストメールです。`,
		HtmlPart: `<html><body><h1>こんにちは、これはテストメールです。</h1></body></html>`,
	}
	jsonBytes, err := json.Marshal(email)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(jsonBytes))
	req, err := http.NewRequest("POST", apiEndpoint, bytes.NewBuffer(jsonBytes))
	if err != nil {
		panic(err)
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+token)
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	body, _ := io.ReadAll(resp.Body)
	// Parse
	var response Response
	err = json.Unmarshal(body, &response)
	if err != nil {
		panic(err)
	}
	fmt.Println(response.DeliveryId)
}
```

## まとめ

クラウドサービスではSMTPポートが塞がれている場合があるので、そうした時にはAPI経由を利用してください。SMTPリレーを使えば、より信頼性高く、安定した配信が実現できるでしょう。

APIとSMTPリレー、それぞれの要件に合わせて最適な方を選択してください。

[エンジニア向けメール配信システム「ブラストエンジン」](https://blastengine.jp/)