SendGrid v2 APIからv3 APIへの移行で押さえておくポイント

# SendGrid API v3

この記事は[SendGrid Advent Calendar 2016](http://qiita.com/advent-calendar/2016/sendgrid)の23日目の記事です。

今年１番のSendGrid界隈のニュースといえば[2016年6月にリリースされたSendGrid v3のリリース](https://sendgrid.com/blog/introducing-v3mailsend-sendgrids-new-mail-endpoint/)ではないでしょうか。よりRESTFulになり、細やかなオプション制御にも対応しつつ便利になりました。[クラウドワークス](https://crowdworks.co.jp)でも社内で利用しているサービスをv3 APIに対応したのでその際の課題や対応するためのワークアラウンドを共有します。

## v2とv3の違い

v2 APIとv3 APIで筆者が感じる大きな違いは、APIに渡すのに必要なデータの構造が整理されて取り扱いしやすくなった点です。メール送信のapiを例にあげると、v2 APIでは下図のようにメール送信に必要な情報として宛先と埋め込みたいテキストを別々の配列として渡していました。正しく動いている時は問題ないですが、開発中であるとかバグで配列の数や位置がずれたときに意図しない相手に意図していない埋め込みテキストのメールを送信してしまいかねない構造になっています。

![v2 API Mail send](https://qiita-image-store.s3.amazonaws.com/0/75104/cdba8553-a440-a556-5af0-7a72d8194ae6.png)

一方v3 APIでは１通のメール送信に必要なデータが `SendGrid::Personalization` としてまとめられているため、構造的に送信先と埋め込みデータがズレるといったことが起きにくくなっています。

![image](https://qiita-image-store.s3.amazonaws.com/0/75104/7387d666-9294-521f-39e1-b4b55ef78026.png)

開発しているシステムの堅牢性を向上する意味でもv3 APIへ移行する意義は十分ありそうです。他にも送信時にリンクのトラッキングの有無を設定できるようになったり細やかな要求に耐えられるようになりました。

## v3 APIの日本語エンコーディングの問題

メール送信の日本語処理について[公式にもいくつかの不具合がある](https://support.sendgrid.kke.co.jp/hc/ja/articles/206231901-Web-API-v2%E3%81%A8v3%E3%81%A9%E3%81%A1%E3%82%89%E3%82%92%E5%88%A9%E7%94%A8%E3%81%99%E3%81%B9%E3%81%8D%E3%81%A7%E3%81%97%E3%82%87%E3%81%86%E3%81%8B-)とアナウンスされています。こちらの利用環境でも、とある不具合に遭遇しました。

具体的には`Substitution`を使ってメール本文の埋め込みテキストを置換してメール送信すると`Subject`が生のUTF-8のまま送信されてしまう問題がありました。Gmailではそのまま表示されるので気が付きにくいです[^1]。

回避策としては以下のように`Subject`にも空の`Substitution`を渡すことでBASE64エンコードしてくれるようになります[^2]。

```ruby:send_v3_api.rb
sendgrid = SendGrid::API.new(api_key: SENDGRID_API_KEY)
mail = SendGrid::Mail.new
from_name, from_addr = from.split(' ')
mail.from = SendGrid::Email.new(email: from_addr, name: from_name)
# 件名の後ろに置換用のダミーを追加します
mail.subject = subject + "_dummy_"
to.size.times do |i|
  personalization = SendGrid::Personalization.new
  personalization.to = SendGrid::Email.new(email: to[i], name: names[i])
  personalization.substitutions = SendGrid::Substitution.new(key: '_fullname_', value: names[i])
  # 件名をエンコードさせるためダミーを空文字で置換します
  personalization.substitutions = SendGrid::Substitution.new(key: '_dummy_', value: "")
  mail.personalizations = personalization
end
sendgrid.client.mail._('send').post(request_body: mail.to_json)
```

と、これを書いている2016年12月現在、この問題は修正されたとの連絡をいただきました！

現在では本文のみ置換していても`Subject`を問題なくエンコード文字列にしてくれるようです(まだ試せていない)。

[^1]: ガラケーで確認しました
[^2]: v2 APIでは`quoted-printable`エンコードされます

# 認証とapi_key

v3 APIは基本的に`api_key`での利用が前提になります。v2 APIでID/Passで認証で呼び出していた場合はAPIライブラリの初期化まわりを書き換える必要があります。また、`api_key`の利用目的によって実行できる操作を許可したり制限したりすることができます。使いたい機能に絞った権限を付与して安全に利用できます。サブユーザーの切り替えについても直接ID/passで認証するのではなくそれぞれのサブユーザーで生成した`api_key`を利用します。

## 権限の付与

`api_key`はWebのSendGridダッシュボードで生成できます。しかし、残念なことにWebからは割り当てられない権限がたくさんあります。以下のAPIリファレンスに載っている細かい権限については権限を与奪するAPIを`curl`などで叩いて設定します。

* https://sendgrid.com/docs/API_Reference/Web_API_v3/API_Keys/api_key_permissions_list.html

権限の付与には、より強い権限をもった認証をしないとできないのでマスターアカウントのパスワードで認証してAPIを叩いて設定します。たとえば以下はサブユーザーのStatsを参照するための権限を付与しするためのリクエストになります。

```shell-session
curl -u "user:password" --request PUT --http1.1 --header 'Content-Type: application/json' "https://api.sendgrid.com/v3/api_keys/API_KEY" --data '{"name": "melma_stats", "scopes": ["subusers.create","subusers.read","subusers.stats.read","subusers.stats.monthly.read","subusers.stats.sums.read", "subusers.credits.read","subusers.monitor.read","subusers.reputations.read","email_activity.read","stats.read","stats.global.read","browsers.stats.read","devices.stats.read","geo.stats.read","mailbox_providers.stats.read","clients.desktop.stats.read","clients.phone.stats.read","clients.stats.read","clients.tablet.stats.read","clients.webmail.stats.read","categories.stats.read","user.webhooks.parse.stats.read","categories.stats.sums.read"]}'
```

# まとめ

`api_key`の取り扱いとより構造化されたメッセージの組み立てを意識してリファレンスを読めばとくに迷うことなく移行できると思います。

[curlですぐ試せる例付きの日本語リファレンス](https://sendgrid.kke.co.jp/docs/API_Reference/Web_API_v3/index.html)が役に立ちます。いつもお世話になってます。ありがとうございます。