読者です 読者をやめる 読者になる 読者になる

Sendgridでメールサーバのお守りから解放され、かんたんに受信メールを取り扱えるようになる話

こんにちは、@Dominion525 です。 好きな大長編は海底鬼岩城(旧)です。

メールサーバの管理は面倒

さて、Webサービスを行う際にわりと面倒なことが起こりがちなのがメールの配信です。 各種メッセージツールが充実している昨今とはいえ、基盤的なコモディティとしてのメールは無視することができません。

とはいえいろいろ運用は難しく、気を抜くとspam判定されて届かなかったり、大量に送った際には時間がかかりすぎたり、不達が多すぎてRBLなどのブラックリストに入れられて解除申請するはめになったり…、といろいろつらい思い出が蘇る感じがあります。

で、かなりの運用コストがかかるので、素の状態のMTA(Message Transfer Agent / メールサーバ)を自力で運用するのはそろそろやめたい気持ちでいっぱいです。だってもう2015年なんですよ?

Sendgrid

そこでMisoca では、Sendgrid を利用しています。

Sendgridは主にWebサービス事業者にターゲットしたクラウド型のメール配信プロバイダで、大規模配信バックエンドとして広く使われています。 2013年からは、構造計画研究所さんが国内正規代理店となっており日本語でのサポートも行っています(料金は同じなのに対応は迅速で情報量も多いので、代理店経由がお勧めです)

シンプルなメール配信

最もシンプルな利用法としては従来のMTAの代替です。 これは、 - アプリケーションが指すSMTPサーバのアドレスを変更する - 既存MTA のリレー先に指定する というだけで利用開始できるので最もお手軽です。

メール配信サービスとしては Amazon が提供する Simple Email Service(Amazon SES)も有力なのですが、利用していくことによって利用枠が拡大されていくSES と比べて、Sendgridはいきなり上限まで利用できるので、すでに大規模な利用がある場合でも移行が可能です。

また、不達となるようなメールアドレスに送信してしまっても、当該アドレスがBounce リストに登録され以後はSendgridレベルで送信を停止してくれます。ので、あまり細かいことを考えずに送ってしまってもよそのサーバに迷惑をかけにくくなっています。Bounce リストはマネジメントコンソールやAPIから管理できます。

また、SPFDKIMDomainKeysなどもかなり簡単に設定できるようになっていますので、ちゃんとしたメールであるかぎりはかなり到達性が上がるものと思われます。(DNS設定との連携は必要です)

充実したAPI

素朴に使うだけであれば、上記のように既存のMTAを置き換える SMTP APIを使えば良いだけなのですが、Sendgridの魅力として豊富なAPI提供があります。

  • SMTP API
    • このAPIはメールヘッダに組み込んで使用します。メールテンプレートの使用や、トラッキング・統計分析のための情報埋め込みや、短時間で大量のメールを送信するために活用することができます。
  • Web API -マイページにログインしなくても設定情報や統計データ、レポート情報を取得できます。
  • Event Webhook
    • メール送信時の各種イベント情報を、指定したURLにPOSTすることが可能です。各種イベントをリアルタイムに通知したり、イベントをトリガとして処理を実行することができます。
  • Parse Webhook
    • メールをコンテンツ(例:メールヘッダ、件名、本文、添付ファイルなど)ごとに分けて、指定したURLにPOSTすることが可能です。空メールによるユーザ登録などに活用することができます。
  • Subuser API
  • Marketing Email API
    • 宛先リストやマーケティングメールのコンテンツ、そして差出人情報を、アップロード・管理するためのAPIです。

引用:メールシステムの運用をより楽に ~SendGrid APIガイド~ | ブログ | SendGrid

Parse Webhook

そのなかでも、お手軽に使えるのにいろいろ便利な Parse Webhook について説明します。 簡単にいえば、送られてきたメールをParseして、任意のURLにPOSTしてくれるものです。

任意のドメインに届いたメールすべてをSendgridに投げることになるので、受信専用のドメインを準備する MXレコードを書き換える必要があります。 (自社ドメインを通常のメール受信のために使っていたので、別途MX書き換えられるドメインがいるはずって思い込んでました。@a_matsuda さん、ご指摘ありがとうございました!)

Sendgrid側の設定でPOST先を設定すれば、あとは任意の処理を行う事ができます。

Misocaでは、エラーの通知やシステム上の警告など、従来はメールで行われていたものをSlackに連携させる場合に使っています。 これにより、アプリケーション側では「とりあえずメール投げておけば良い」という状態ができるの大変に便利です。

f:id:dominion525:20150710131149p:plain

通知メールをslackに投げている例。(エラー発生しすぎだろ、というのは禁則事項です)

また、アプリケーション側がリクエストを受け取れなかった場合は、ある程度の間隔で再送してくれるので優しい感じです。

実際の例

汎用的な受け取り処理を行う griddler と、そのsendgrid用である griddler-sendgrid を用います。 下記はRailsアプリケーションから関連する部分のみを抜粋したものです。

# Gemfile
(snip)
gem 'griddler'
gem 'griddler-sendgrid'
# config/routes.rb

Rails.application.routes.draw do
  (snip)
  mount_griddler
  (snip)
end
# config/initializers/griddler.rb

Griddler.configure do |config|
  config.processor_class = EmailProcessor 
  config.processor_method = :proccess 
  config.reply_delimiter = '-- REPLY ABOVE THIS LINE --'
  config.email_service = :sendgrid # :cloudmailin, :postmark, :mandrill, :mailgun
end
# lib/email_processor.rb

class EmailProcessor
  def initialize(email)
    @email = email
  end

  def process
     # 任意の処理
  end
(snip)
end

これだけで、メールをパースして EmailProcessor#proccess でいい感じに取り扱うことができます。

主なAttribute

下記のような属性が取得できるので、あとは自力で頑張ればなんとでも出来る感じですね。

Attribute Description
#to An array of hashes containing recipient address information. See Email Addresses for more information.
#from A hash containing the sender address information.
#cc An array of hashes containing cc email address information.
#subject The subject of the email message.
#body The full contents of the email body unless there is a line in the email containing the string -- Reply ABOVE THIS LINE --. In that case .body will contain everything before that line.
#raw_text The raw text part of the body.
#raw_html The raw html part of the body.
#raw_body The raw body information provided by the email service.
#attachments An array of File objects containing any attachments.
#headers A hash of headers parsed by Mail::Header, unless they are already formatted as a hash when received from the adapter in which case the original hash is returned.
#raw_headers The raw headers included in the message.

出典: griddler/README.md at master · thoughtbot/griddler · GitHub

不具合

griddler-sendgrid では添付ファイルが日本語名であるときにうまく取り扱うことが出来ない不具合があります。 現在 PullRequest は出されているのですが、一向に取り込まれる気配がありません。

そのため下記のforkを利用しています。

standfirm/griddler-sendgrid at fix_filename · GitHub

# Gemfile
gem 'griddler-sendgrid', github: 'standfirm/griddler-sendgrid', branch: 'fix_filename'

のようにしてください。

まとめ

  • Sendgrid でMTAのお守りからかなり開放される
  • Sendgrid はAPIが豊富
  • Parse Webhook がお手軽に便利

リンク