Ruby on RailsでSendgrid(Web API)メール送信を実装


業務でSendgridのSMTPを用いてメールをユーザー宛に送信していたが、送信されるメール毎にカテゴリーを付与し、コンバージョン率を調査したいとの社内マーケからの要望を受けた。

そのため、SMTPでカテゴリーを付与しようとしたが、カテゴリー設定がSMTPではできないことが判明し、Web APIにて実装しようと思ったところ、意外と参考文献が少なかったので備忘録も兼ねて記事とした🚬
ちなむと今回は、Web APIを用いてメール送信するところが難所なのではなく、カテゴリー付与するところで詰まりました。

また、今回APIの呼び出しに使用したのは`sendgrid-ruby`という`gem`である。

https://github.com/sendgrid/sendgrid-ruby

# SendgridのAPI取得

APIの取得に関しては公式ドキュメントを参照して取得して欲しい。
メール送信など単純な使用目的で取得する場合は`full access`を選択してAPI生成すれば問題なし✌️

https://sendgrid.kke.co.jp/docs/Tutorials/A_Transaction_Mail/manage_api_key.html


# コード実装

ここからの実装の流れは以下2つの記事を参照した。

https://techracho.bpsinc.jp/yamada/2018_12_22/67222

https://qiita.com/sheepland/items/0bbf5c6f9fba08caf0a6


## 最終的なコード
RailsはデフォでAction Mailer(smtp)を使用するので、新規でdelivery methodを追加してあげる。

今回実装したファイルは以下。


```ruby
# config/initializers/sendgrid.rb

ActionMailer::Base.add_delivery_method :sendgrid, Mail::SendGrid,
  api_key: Settings.sendgrid_api #settings.local.ymlの中にあるAPIキーを呼び出す。
```

```ruby
# app/mailers/application_mailer.rb

class ApplicationMailer < ActionMailer::Base
  def set_sendgrid_settings(category)
    ActionMailer::Base.delivery_method = :sendgrid
    headers['category'] = category
  end
end
```

```ruby
# lib/mail/send_grid.rb

class Mail::SendGrid
  def initialize(settings)
    @settings = settings
  end

  def deliver!(mail)
    sg_mail = set_mail_information(mail)
    add_content_and_category(sg_mail, mail)
    sg = SendGrid::API.new(api_key: @settings[:api_key])
    sg.client.mail._('send').post(request_body: sg_mail.to_json)
  end

  private

    def set_mail_information(mail)
      from = SendGrid::Email.new(email: mail.from.first)
      to = SendGrid::Email.new(email: mail.to.first)
      subject = mail.subject
      content = SendGrid::Content.new(type: 'text/plain', value: mail.body.parts[0].body.raw_source)
      SendGrid::Mail.new(from, subject, to, content)
    end

    def add_content_and_category(sg_mail, mail)
      sg_mail.add_content(SendGrid::Content.new(type: 'text/html', value: mail.body.parts[1].body.raw_source))
      sg_mail.add_category(SendGrid::Category.new(name: mail[:category].value))
    end
end
```

# メールが送られるまでの流れ

- `app/mailers/application_mailer.rb`で定義したメソッド`set_sendgrid_settings('カテゴリー名')`をmailerファイルの中で呼び出す。


#### 例 ）
以下test_mailer.rbのアクション内で定義した`set_sendgrid_settings('カテゴリー名')`を呼び出すことで、「test_mailerメソッドのdelivery_methodはsendgridのWeb APIを通して送信しますよ。」と指示する。
※引数でカテゴリー名を渡すことで、sendgridへカテゴリー名も付与することができる。

```ruby
# app/mailers/test_mailer.rb
class TestMailer < ApplicationMailer
  def test_mail
    set_sendgrid_settings('This is test category')
    mail(from: 'from@example.com', to: 'to@example.com', subject: 'テストメール')     
  end
end
```

```
# app/views/test_mailer/test_mail.text.erb
SendGrid送信テスト
```


*** 

- `set_sendgrid_settings('カテゴリー名')`によって、`lib/mail/send_grid.rb`で定義した`deliver!`メソッドが使用できる。

以下の`initialize`によって、`config/initializers/sendgrid.rb`が呼び出されAPIを使用することができ、`@settings`にAPIキーが代入される。

```ruby
# lib/mail/send_grid.rb
class Mail::SendGrid

  def initialize(settings)
    @settings = settings
  end
~
```
*** 

- mailerから渡ってきた`mail`変数を`sendgrid-ruby`の所定のフォーマットに則って代入する。

以下の`set_mail_information(mail)`プライベートメソッドにてgemのフォーマットに則って代入する。
※`SendGrid::Email.new()`はgem特有のもの。詳しくは公式まで。

```ruby
# lib/mail/send_grid.rb
class Mail::SendGrid

~

  private

    def set_mail_information(mail)
      from = SendGrid::Email.new(email: mail.from.first)
      to = SendGrid::Email.new(email: mail.to.first)
      subject = mail.subject
      content = SendGrid::Content.new(type: 'text/plain', value: mail.body.parts[0].body.raw_source)
      SendGrid::Mail.new(from, subject, to, content)
    end

~
```

以上の流れでメールは送信できる。

***

# 詰まった問題点

そもそも業務でsendgridを使用しているので、`text/plain`形式だけではなく、`html`形式のメールも送信するマルチパートメールを採用している。（というかどこの企業もそうだと思うが…）

上記までだと、`text/plain`形式のメールだけしか送れず、なんならhtml.erbファイルもある場合はエラーが出て送信できない。

あと、カテゴリーに関してもaction mailerのMail::Messageに含まれる引数しか渡せないため、独自に変数を渡すことは不可能だったので、別の方法で実装するしかなかった。


- マルチパートメールどうやって送るの？
- カテゴリー名どうやって付与するねん…


# 解決方法

結論、gemの特性なのか知らないが、先に`text/plain`形式のメールを指定しなければならないらしい。なので、`html`形式は`add_category`というgemのメソッドを使用して追加することに成功した。

カテゴリーは、mailerのactionごとに指定したい場合には、`header[:category]`として含める必要があることがわかった。
※カテゴリー名はUS-ASCII文字セットのみ対応（日本語不可）ということにも注意。

> 該当箇所

html形式のメールは`mail.body.parts`の2番目に含まれているので、`mail.body.parts[1]`で指定してあげる。（1番目に格納されているのは`text/plain`形式。)

```ruby
# lib/mail/send_grid.rb

class Mail::SendGrid

~

    def add_content_and_category(sg_mail, mail)
      sg_mail.add_content(SendGrid::Content.new(type: 'text/html', value: mail.body.parts[1].body.raw_source))
      sg_mail.add_category(SendGrid::Category.new(name: mail[:category].value))
    end
end
```

以上でmailerのアクション毎にカテゴリー名を付与した上で、Web APIを経由しメール送信することが可能になった。

Sengridのダッシュボード上でもカテゴリー分け毎のコンバージョン率も確認できて、Webマーケからも感謝されました。めでたしめでたし。