受信メールをRailsのコントーラに受け渡すスクリプト

  • Railsアプリでメールを受信して処理
  • ActionMailer::Base.receive(STDIN.read) とかやる
  • 受信のたびに起動とか重いよ!
  • SMTPサーバとRailsアプリのサーバが違うと面倒

ってことで、コントローラで処理させるための簡単なスクリプトを書いた。

使うことに以下のようなメリットがある。

  • 起動負荷の軽減、高速化
  • SMTPサーバとアプリサーバが違っても問題なし
  • 受信メール処理用のサーバを複数指定してロードバランシング

改良すべき点

  • 処理サーバへのアクセスに失敗した際、キューにためておき、後で試す。
※注意
当然無保証です。自己責任でどうぞ。
#!/usr/bin/ruby

# mail-proxy.rb

# /etc/aliases
# user: "| /path-to-script/mail-proxy.rb"

require 'uri'
require 'net/http'
require 'net/https'
module MailProxy

  class Proxy

    def initialize(servers)
      @servers = []
      servers.each do |server|
        @servers << MailProxy::Server.new(server)
      end
    end
    
    def proxy(str, options = {})
      start(str, options) do |server, mail|
        server.post(mail)
      end
    end
    
    private
    def select_server
      server = @servers[rand(@servers.size).to_i]
      server = select_server unless server.alive?
      return server
    end
    
    def start(mail, options, &block)
      options = {
        :trial => 30
      }.merge(options)
      success_flag = false
      options[:trial].to_i.times do
        server = select_server
        if block.call(server, mail)
          success_flag = true
          break
        end
        sleep(1)
      end
      success_flag
    end

  end

  class Server
  
    attr_reader :uri
    def initialize(url)
      @uri = URI.parse(url)
    end

    def alive?
      Net::HTTP.start(uri.host, uri.port){ |http| ; }
      true
    rescue
      false
    end
    
    def post(data)
      Net::HTTP.start(uri.host, uri.port) do |http|
        response = http.post( uri.path, data )
        return [200, 403].include?(response.code.to_i)
      end
    rescue
      false
    end

  end
  
end

servers = [ 'http://192.168.0.100:3000/receive/signup',
            'http://192.168.0.100:3001/receive/signup',
            'http://192.168.0.101:3000/receive/signup',
            'http://192.168.0.101:3001/receive/signup']

proxy = MailProxy::Proxy.new(servers)
proxy.proxy($stdin.read)

コントローラはこんな感じで。

class ReceiveController < ApplicationController
  skip_before_filter :verify_authenticity_token

  class IllegalRequestError < RuntimeError; end

  # 携帯登録要求メール
  def signup
    raise IllegalRequestError.new('許可されていないリクエストです。') if !request.post? || request.raw_post.blank?
    UserNotify.receive(request.raw_post)
    render :text => 'メールを受け付けました。'
  rescue TMail::SyntaxError, IllegalRequestError => e
    render :status => 403,:text => e.message
  end

end