Rails×SES バウンスメール(不達メール)対策
「SESで不達メールが多いから、対策してくれなかったらSES止めるよ」って過去にAWSから言われたことがあって、そのときの対応メモを書きだしてみた。
対応の基本的な流れ
SESは不達メールがあった場合に特定のURLに対してリクエストをjson付きで投げてくれるので、それを特定のURLでうけてJSONパースして、そのパースしたJSONの中にメールアドレスがあるので、それをブラックリストテーブルか何か作ってINSERTして、メール送信の時はそのブラックリストテーブルを見て、送っていいメールアドレスいいか判定するって感じ。
AWSの設定はここを参考に。
Amazon SESのBounce SNS通知をRailsで処理する|WEBデザイン Tips
Railsのソースはこんな感じ。
app/controllers/api/aws_controller.rb
# # AWSから飛んできたリクエストを受け取るAPI # require 'json' require 'open-uri' class Api::AwsController < ActionController::Base skip_before_filter :verify_authenticity_token # # SESからリクエストされる # 不達メールアドレスリストをブラックリストに登録する # def receive_bounce_notice # data = JSON.parse(dummy_raw_post) # debug用 data = JSON.parse(request.raw_post) # Subscription認証用URLを開いて認証を完了する if data['SubscribeURL'].present? open(data['SubscribeURL']) # ブラックリスト登録する else data2 = JSON.load(data['Message']) type = data2['notificationType'] if type=='Bounce' bounce = data2['bounce'] bouncerecps = bounce['bouncedRecipients'] bouncerecps.each do |recp| email = recp['emailAddress'] if BouncedEmailAddress.where(email: email).blank? BouncedEmailAddress.create(email: email) end end end end render text: "" end private def dummy_raw_post str =<<EOS { "Type" : "Notification", "MessageId" : "463fbb0b-63ea-4a6d-90c5-33c8686e3bd1", "TopicArn" : "arn:aws:sns:us-east-1:811118151095:suz-lab-ses", "Message" : { "notificationType":"Bounce", "bounce":{ "bounceType":"Permanent", "bounceSubType": "General", "bouncedRecipients":[ { "emailAddress":"recipient1@example.com" }, { "emailAddress":"recipient2@example.com" } ], "timestamp":"2012-05-25T14:59:38.237-07:00", "feedbackId":"00000137860315fd-869464a4-8680-4114-98d3-716fe35851f9-000000" }, "mail":{ "timestamp":"2012-05-25T14:59:38.237-07:00", "messageId":"00000137860315fd-34208509-5b74-41f3-95c5-22c1edc3c924-000000", "source":"email_1337983178237@amazon.com", "destination":[ "recipient1@example.com", "recipient2@example.com", "recipient3@example.com", "recipient4@example.com" ] } }, "Timestamp" : "2012-10-08T13:00:40.691Z", "SignatureVersion" : "1", "Signature" : "FDNBWbFhc5MXs+2tjw327zXhiKca3GLHbbVEN8vUmLAmnj60...", "SigningCertURL" : "https://sns.us-east-1.amazonaws.com/SimpleN...", "UnsubscribeURL" : "https://sns.us-east-1.amazonaws.com/?Action..." } EOS return str end end
lib/mail/email_interceptor.rb
# # email_interceptor は、action mailer の # before action 的な動きをしてくれるヤツ(らしい。。) # class EmailInterceptor # # ブラックリスト宛には送らない # def self.delivering_email(message) _to = message.to message.to = probe_email(message.to) if message.to.present? message.cc = probe_email(message.cc) if message.cc.present? message.bcc = probe_email(message.bcc) if message.bcc.present? message = set_to(message, _to) # rescue => e # binding.pry # Rails.logger.error(e.to_s << "\n" << e.backtrace.join("\n")) end # # ブラックリストに載っていないアドレスだけにする # def self.probe_email(emails) bounced_email_addresses = BouncedEmailAddress.pluck(:email) list = [] emails = [emails] if emails.instance_of?(String) emails.each do |email| list << email if !(bounced_email_addresses.include?(email)) end return list end # # toがすべて空だった場合 # 送信処理するとエラーになるため、 # とりあえず仮で送信先を入れる # def self.set_to(message, _to) return message if message.to.present? # ブラックリストに送ろうとしたメールはここに送信する message.to = ["sample@gmail.com"] # message.subject = "Can not sent to bouced email address" return message end end
email_interceptor.rb は、lib/mail の配下とかにおいて、
config/application.rb で読みこむようにすればOK。
class Application < Rails::Application # 略 config.autoload_paths += %W(#{config.root}/lib/mail) # 略
※BouncedEmailAddressというモデルはブラックリスト用テーブル。
メールアドレスを格納しているだけなのでソースは割愛。