Later Mail を作る(続き)

昨日の続きです。昨日はメールを受信して返信用メールを保存するところまでできましたので、今日は時間がきたメールを送信するところを作ります。

第2ステップ:メール送信

実はこちらの方がはるかに簡単です。「5秒間隔でディレクトリの一覧をみて、時間がきたファイルを Postfix sendmail に渡す」ということだけしかやっていません。50行ほどですので、このページの最後にソースを貼り付けておきます。(注意:今後プログラムの改変が行われたとしても、ここには反映しません)

プログラム本体
  require 'daemonize'
  include Daemonize

  daemonize()
  sl = SendLater.new("/home/hml/Maildir/tmp/")
  loop do
    sl.monitor
    sleep 5
  end

本体はこれだけです。ずっと動かす前提なので、こちら(→ http://grub.ath.cx/daemonize/ )をインクルードして daemon にしてあります。(Ubuntu起動時に自動起動させるためには update-rc.d で登録してやらないといけないんですが、まだ試してみていません)
動作させるときのパーミッションには注意してください。作成されたファイルは Postfix ユーザ(nogroup)の所有権でリードオンリーになっているはずですから、このサンプルのまま動かすのであれば、「sudo nice ruby sendlater.rb」とするなど、一工夫必要です。(こんなものを管理者権限で動かしたくないということであれば、File.chmod でコネコネやるか、書き込むときのパーミッションを工夫してください)

ファイル監視
  def monitor
    Dir.glob("#{@dir}*").each do |path|
      name = File.basename(path)
      dt = name.split('.')[0]
      # 8日以上前は消す
      if dt < timef(Time.now - 3600*24*8)
        File.delete(path)
      elsif dt < timef(Time.now)
        if send(path)
          File.delete(path)
        else
          # 次は10分後に実施
          File.rename(path, add(name, 600))
        end
      end
    end
  end

やってることは 8日以上古いファイルは消してしまうことと、現在時刻より古いファイルを送信することです。送信が成功(system レベルで true が返ってくる)すれば、対象ファイルを削除して、失敗したらファイル名を 10分後のファイル名に書き換えます。

ファイル送信
  def send(path)
    system("sendmail -t < #{path}")
  end

Postfix sendmail にファイルを渡しているだけです。-t は、メッセージヘッダから受信者を抽出してセットするオプションです。

sendlater.rb

追記:'$'、バッククォートが入ったファイル名をエスケープしてしまい、not found になってしまっていたため、system("sendmail -t < '#{path}'") にシングルクォートを追加しました。

require 'time'

class SendLater
  def initialize(mdir = '')
    @dir = mdir
  end

  def timef(t)
    t.strftime("%Y%m%d%H%M%S")
  end
  
  # name  "20080825173033.20080825083051.76E9B2908D"
  def add(name, sec)
    @dir + timef(Time.parse(name.split('.')[0]) + sec) + '.' + name.split('.')[1..-1].join('.')
  end

  def send(path)
    system("sendmail -t < '#{path}'")
  end

  def monitor
    Dir.glob("#{@dir}*").each do |path|
      name = File.basename(path)
      dt = name.split('.')[0]
      if dt < timef(Time.now - 3600*24*8)      # 8日以上前は消す
        File.delete(path)
        # puts "old delete=#{path}"
      elsif dt < timef(Time.now)
        if send(path)
          File.delete(path)
          # puts "send OK delete=#{path}"
        else
          File.rename(path, add(name, 600))    # 次は10分後に実施
          # puts "send NG rename=#{path} - #{add(name, 600)}"
        end
      end
    end
  end
end

if __FILE__ == $0
  # ruby sendlater.rb

  require 'daemonize'
  include Daemonize

  daemonize()
  sl = SendLater.new("/home/hml/Maildir/tmp/")
  loop do
    # puts Time.now.to_s
    sl.monitor
    sleep 5
  end
end


Creative Commons License