かまずにまるのみ。

文鳥とかビールとか

ActionMailerとメール配信APIを組み合わせて使う

Ruby on Rails を書いていて、SendGrid を使うことになって、文鳥が「ActionMailer 使いたいですね」って言って、組み合わせて使うことになった。
ActionMailer は SendmailSMTP 前提っぽい。

Action Mailer の基礎 - Railsガイド

しかし今回利用したい SendGrid は API の使用を推奨してる。
あれあれ困りました。
「無理でしょできないでしょ」という声もありました。
いやでも Rails なら大体何とかできるんでしょ、文鳥知ってるもんね。

環境構築

ここで sendgrid-ruby の Gem を利用したいと思います。
この Gem、実は2年間くらいメンテされていません(2025年9月現在)
えー、そんなに放置されている Gem 使うのだめじゃない?
いやちょっと待ってほしい。
仮にも SendGrid が使用を推奨しているライブラリだ。
何か理由があるに違いない。

参考に、他の言語のライブラリも見てみます。
ちょっと更新されている気配があります。
古いバージョンの言語が非対応になっているようです。
なので SendGrid さん、メンテ自体はちゃんと行なっている模様。
これはあれですね、Ruby後方互換があれだからメンテンナンスされていないだけ疑惑が出てきましたね。

よしじゃあ sendgrid-ruby のコードを読んでみよう!
依存している gem も SendGrid が作った ruby_http_client ひとつのみ。
ひえーこっちの実装はすごいシンプルだね、すごいね。
じゃあ大丈夫そうだね、気にしなくてもいいね。
それではいよいよ sendgrid-ruby の実装を見てみます。
こっちもピュアな Ruby のメソッドをシンプルに使っていそう。
これはやはりメンテする必要がないから対応されていないだけの可能性が高い。
Gem を落としてきて Ruby 3.4 でテストを実行します。問題なくパスしました。
よかったよかった。

ちなみに「workflow でテストが落ちてる」とか「Issue や PR が放置されている」という理由でも利用を渋られましたが、SendGrid さんはそういう仕草がデフォルトっぽいので気にしないことにします。あと sendgrid-ruby に関していうとテストが落ちてるのは主に JRuby のせいです。無視無視。
おそらくクリティカルなところだけ対応しているんでしょう。
お忙しだろうから仕方ない。お疲れ様です。

自前実装するのも大変らしいので今回はこれを使うことにします。

# Gemfile
gem 'sendgrid-ruby'

Gem 選ぶだけですごい時間かかってしまいました。
酔っ払いが文章を書くとこうなるんですね。
でも今回は導入にあたって異論も出ていたので、それを払拭するために丁寧に対応したので多少時間をかけたのも事実です。 事前の想定で「自前実装のよるコスト増や不具合発生のリスク」よりも「公式が利用を推奨していて安定して使われているというメリット」が大きく上回る可能性がありました。

ちなみに普段の文鳥は Gem の導入には慎重になる派です。
何も考えずにほいほい入れるとあとで泣いちゃうからね。

設定

で、ActionMailer で SendGrid を使いたいわけです。
しかし前出の Rails ガイドには SendmailSMTP の例しかありません。

Action Mailer の基礎 - Railsガイド

# Sendmail を使うとき
config.action_mailer.delivery_method = :sendmail

# SMTP を使うとき
config.action_mailer.delivery_method = :smtp

# LetterOpener を使うとき
config.action_mailer.delivery_method = :letter_opener

どうやらこいつがカギを握っているようだ…….
ここで :sendgrid みたいな感じで設定できるとよさそうです。

# config/environments/#{environment}.rb

config.action_mailer.delivery_method = :sendgrid

しかし :sendgrid という送信方法は存在しません。
どうするかと言うと、ActionMailer::Base.add_delivery_method ってやつを使って新しい送信方法を追加することができます。

ActionMailer::DeliveryMethods::ClassMethods

ってこのあとも一生懸命書こうと思ったんですが、みんな大好き TechRacho さんが何年も前に書いてくださっているのでぜひそちらをご参照ください。

techracho.bpsinc.jp

例として config/initializers/sendgrid.rbMail::SendGrid を参照していますが、ファイルの読み込み順によってはここで失敗するかもしれません。その場合は読み込み順を何とかするとか、require して何とかするとか、何かしら作業が必要になります。
文鳥は最初ふつーに require してたんですが、夫から Rails.application.reloader.to_prepare を教えてもらいました。
AI からは「それ無駄じゃない?開発環境だとオーバーヘッドになるよ?」って突っ込まれました。いやでもだって仕方ないじゃないですか先に読んでくれないんだもん!!
影響が開発環境のみなので気にしないことにしました。

# config/initializers/sendgrid.rb

Rails.application.reloader.to_prepare do
  ActionMailer::Base.add_delivery_method(
   :sendgrid,
   Mail::Sendgrid,
   api_key: SENDGRID_API_KEY
  )
end

TechRacho さんの例では content として 'text/plain' のみを追加していますが、'text/html' も一緒に追加するとマルチパートメールを送ることができます。
また、subject や content、 personalization などに関しては生成した SendGrid::Mailインスタンスに対して、あとから追加することも可能です。
その辺りは実際に Gem のコードを見てもらうとわかりやすいと思います。

sendgrid-ruby/lib/sendgrid/helpers/mail/mail.rb at main · sendgrid/sendgrid-ruby · GitHub

# lib/mail/send_grid.rb

sendgrid_mail = SendGrid::Mail.new
sendgrid_mail.from = SendGrid::Email.new(email: mail.from.first)
sendgrid_mail.subject = mail.subject
sendgrid_mail.add_content(SendGrid::Content.new(type: 'text/plain', value: mail.text_part.body.raw_source))
sendgrid_mail.add_content(SendGrid::Content.new(type: 'text/html', value: mail.html_part.body.raw_source))

これで SendGrid さんにリクエストを送れるようになりました(たぶん)
レスポンスでステータスコード 202 が返ってきたときだけ「成功」と見なしてよさそうです。4xx 系はこっちの問題で発生するエラーなので再送しても意味がないかもしれません。5xx 系のときや例外が発生したときはリトライを試みる価値はありそうです。
この辺りは SendGrid の API リファレンスでご確認くださいませ。

ステータスコードとエラー - ドキュメント | SendGrid

Rails ではガイドに書いていないことでもいろいろできたりしますし、Rails の機能を活用することで得られるメリットも多くあります。
Rails Way に乗るってやつですね。
「そんなの普通の方法ではできないよ」で片付けることはかんたんです。
でも、Ruby なら、Rails ならできるんじゃない?という期待を持って、方法を模索することも大切なんじゃないかな〜と思います。「できない」「使えない」と判断する場合も、ちゃんとその根拠を考えて周りに説明できるようにしたいです。

名探偵
名探偵はち
最後に、本件に関しては夫 id:hs_hachi の多大なる協力を得て実装できました。
夫は sendgrid-ruby だけではなく ActionMailer の実装まで読んでもらい、文鳥の書いた実装に問題がないことを証明してくれました。
夫にはいつも感謝しかないね!

おしまい!