ActionMailerで、メールのヘッダもビューに書けるようにする。
以下はRails 1.2.3の話。2.0でどうなってるかは知らないです。もっと良くなってるかも。
概略
From: info@example.com To: <%= @user.email %> Bcc: admin@example.com Subject: <%= @user.name %>さんにお知らせです。 Content-Transfer-Encoding: 7bit <%= @user.name %>さん ○○サービスのご利用ありがとうございます。 云々。
みたいなrhtmlを使ってメールを送信する。
背景
ActionMailerのrhtmlは、通常のコントローラでrenderされる場合と動作が違う。
- 明示的に指定しないとヘルパーが使えない。
- url_forの:only_path => falseが動作しない。
ヘルパーを有効にする(ドキュメント化されていない)方法は、
に載っていた。ActionMailer::Baseを継承したモデル内で、
helper :application
と書くとApplicationHelperが有効になる。これも何か不安だが、次のurl_forの問題が大きかった。
<%= url_for(:action=> "list", :only_path => false) %>
のように書くとエラーになる。:hostを指定しないと、:only_path => falseにできないみたいだった。
ActionViewのurl_forは:only_pathがデフォルトでtrueなので、falseを指定しないと、スキームやホスト、ポート番号が出力されない。http://exmpale.com/list ではなく、/list になってしまう。
メールにURLを載せたいことはよくあるのに、これでは困ってしまう。
改善
コントローラ内で render_to_string を使ってメール本文を作成して、これをActionMailerに渡す。
ついでに、メールのヘッダをビューに書けるようにした。Subjectをビューに書けないのはヘンだ。
下のsimple_mailer.rbをmodelsに追加。
class SimpleMailer < ActionMailer::Base FIELDS = %w{from to cc bcc subject date content-type} def simple(src) fields = parse src from fields["from"] recipients fields["to"] cc fields["cc"] bcc fields["bcc"] subject fields["subject"] body fields["body"] sent_on fields["date"] unless fields["date"].nil? content_type fields["content-type"] unless fields["content-type"].nil? fields["headers"].each do |name, value| headers name => value end end def parse(src) src = src.gsub(/(?:\r\n|\r)/, "\n") lines = src.split(/\n/) fields = {"headers" => {}} until lines.empty? line = lines.shift if line.strip.empty? fields["body"] = lines.join("\n") break end (name, value) = line.split(/:\s+/, 2) next if value.to_s.empty? name.strip! if FIELDS.include?(name.downcase) set_field_value(fields, name.downcase, value) else fields["headers"][name] = value end end return fields end private :parse def set_field_value(fields, name, value) if fields[name].is_a?(Array) fields[name] << value elsif fields[name].is_a?(String) fields[name] = [fields[name], value] else fields[name] = value end end private :set_field_value end
コントローラで次のようにしてメールを送信する。
def send_notifier src = render_to_string(:action => "mail_notifier", :layout => false) SimpleMailer.deliver_simple(src) end private :send_notifier
mail_notifier.rhtml で宛先などを指定する。
From: info@example.com To: <%= @user.email %>, abc@example.com Cc: address1@example.com Cc: address2@example.com Bcc: admin@example.com Subject: 件名です。 Content-Transfer-Encoding: 7bit 本文です。
from, to, bc, subject, date, content-typeは大文字でも小文字でも可。それ以外はheadersにそのまま渡される。
アドレスは複数指定可。複数行にわけるか、またはコンマでつなぐ。
そのままだとutf-8で送られてしまうので、gettextを使うなどしてください。