aboutsummaryrefslogtreecommitdiffstats
path: root/actionmailer/lib/action_mailer/base.rb
diff options
context:
space:
mode:
Diffstat (limited to 'actionmailer/lib/action_mailer/base.rb')
-rw-r--r--actionmailer/lib/action_mailer/base.rb742
1 files changed, 338 insertions, 404 deletions
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 356861b591..44df30b1ba 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -1,6 +1,11 @@
require 'active_support/core_ext/class'
+require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/array/uniq_by'
+require 'active_support/core_ext/module/delegation'
+require 'active_support/core_ext/string/inflections'
require 'mail'
require 'action_mailer/tmail_compat'
+require 'action_mailer/collector'
module ActionMailer #:nodoc:
# Action Mailer allows you to send email from your application using a mailer model and views.
@@ -11,46 +16,71 @@ module ActionMailer #:nodoc:
#
# $ script/generate mailer Notifier
#
- # The generated model inherits from ActionMailer::Base. Emails are defined by creating methods within the model which are then
- # used to set variables to be used in the mail template, to change options on the mail, or
- # to add attachments.
+ # The generated model inherits from ActionMailer::Base. Emails are defined by creating methods
+ # within the model which are then used to set variables to be used in the mail template, to
+ # change options on the mail, or to add attachments.
#
# Examples:
#
# class Notifier < ActionMailer::Base
- # def signup_notification(recipient)
- # recipients recipient.email_address_with_name
- # bcc ["bcc@example.com", "Order Watcher <watcher@example.com>"]
- # from "system@example.com"
- # subject "New account information"
- # body :account => recipient
+ # delivers_from 'system@example.com'
+ #
+ # def welcome(recipient)
+ # @account = recipient
+ # mail(:to => recipient.email_address_with_name,
+ # :bcc => ["bcc@example.com", "Order Watcher <watcher@example.com>"])
+ # end
# end
- # end
+ #
+ # Within the mailer method, you have access to the following methods:
+ #
+ # * <tt>attachments[]=</tt> - Allows you to add attachments to your email in an intuitive
+ # manner; <tt>attachments['filename.png'] = File.read('path/to/filename.png')</tt>
+ #
+ # * <tt>headers[]=</tt> - Allows you to specify non standard headers in your email such
+ # as <tt>headers['X-No-Spam'] = 'True'</tt>
+ #
+ # * <tt>mail</tt> - Allows you to specify your email to send.
+ #
+ # The hash passed to the mail method allows you to specify the most used headers in an email
+ # message, such as <tt>Subject</tt>, <tt>To</tt>, <tt>From</tt>, <tt>Cc</tt>, <tt>Bcc</tt>,
+ # <tt>Reply-To</tt> and <tt>Date</tt>. See the <tt>ActionMailer#mail</tt> method for more details.
+ #
+ # If you need other headers not listed above, use the <tt>headers['name'] = value</tt> method.
+ #
+ # The mail method, if not passed a block, will inspect your views and send all the views with
+ # the same name as the method, so the above action would send the +welcome.plain.erb+ view file
+ # as well as the +welcome.html.erb+ view file in a +multipart/alternative+ email.
+ #
+ # If you want to explicitly render only certain templates, pass a block:
+ #
+ # mail(:to => user.emai) do |format|
+ # format.text
+ # format.html
+ # end
#
- # Mailer methods have the following configuration methods available.
+ # The block syntax is useful if also need to specify information specific to a part:
#
- # * <tt>recipients</tt> - Takes one or more email addresses. These addresses are where your email will be delivered to. Sets the <tt>To:</tt> header.
- # * <tt>subject</tt> - The subject of your email. Sets the <tt>Subject:</tt> header.
- # * <tt>from</tt> - Who the email you are sending is from. Sets the <tt>From:</tt> header.
- # * <tt>cc</tt> - Takes one or more email addresses. These addresses will receive a carbon copy of your email. Sets the <tt>Cc:</tt> header.
- # * <tt>bcc</tt> - Takes one or more email addresses. These addresses will receive a blind carbon copy of your email. Sets the <tt>Bcc:</tt> header.
- # * <tt>reply_to</tt> - Takes one or more email addresses. These addresses will be listed as the default recipients when replying to your email. Sets the <tt>Reply-To:</tt> header.
- # * <tt>sent_on</tt> - The date on which the message was sent. If not set, the header will be set by the delivery agent.
- # * <tt>content_type</tt> - Specify the content type of the message. Defaults to <tt>text/plain</tt>.
- # * <tt>headers</tt> - Specify additional headers to be set for the message, e.g. <tt>headers 'X-Mail-Count' => 107370</tt>.
+ # mail(:to => user.emai) do |format|
+ # format.text(:content_transfer_encoding => "base64")
+ # format.html
+ # end
#
- # When a <tt>headers 'return-path'</tt> is specified, that value will be used as the 'envelope from'
- # address. Setting this is useful when you want delivery notifications sent to a different address than
- # the one in <tt>from</tt>.
+ # Or even to renderize a special view:
#
+ # mail(:to => user.emai) do |format|
+ # format.text
+ # format.html { render "some_other_template" }
+ # end
#
# = Mailer views
#
- # Like Action Controller, each mailer class has a corresponding view directory
- # in which each method of the class looks for a template with its name.
- # To define a template to be used with a mailing, create an <tt>.erb</tt> file with the same name as the method
- # in your mailer model. For example, in the mailer defined above, the template at
- # <tt>app/views/notifier/signup_notification.erb</tt> would be used to generate the email.
+ # Like Action Controller, each mailer class has a corresponding view directory in which each
+ # method of the class looks for a template with its name.
+ #
+ # To define a template to be used with a mailing, create an <tt>.erb</tt> file with the same
+ # name as the method in your mailer model. For example, in the mailer defined above, the template at
+ # <tt>app/views/notifier/signup_notification.text.erb</tt> would be used to generate the email.
#
# Variables defined in the model are accessible as instance variables in the view.
#
@@ -64,9 +94,9 @@ module ActionMailer #:nodoc:
# You got a new note!
# <%= truncate(@note.body, 25) %>
#
- # If you need to access the subject, from or the recipients in the view, you can do that through mailer object:
+ # If you need to access the subject, from or the recipients in the view, you can do that through message object:
#
- # You got a new note from <%= mailer.from %>!
+ # You got a new note from <%= message.from %>!
# <%= truncate(@note.body, 25) %>
#
#
@@ -106,54 +136,13 @@ module ActionMailer #:nodoc:
# Once a mailer action and template are defined, you can deliver your message or create it and save it
# for delivery later:
#
- # Notifier.deliver_signup_notification(david) # sends the email
- # mail = Notifier.create_signup_notification(david) # => a tmail object
- # Notifier.deliver(mail)
- #
- # You never instantiate your mailer class. Rather, your delivery instance
- # methods are automatically wrapped in class methods that start with the word
- # <tt>deliver_</tt> followed by the name of the mailer method that you would
- # like to deliver. The <tt>signup_notification</tt> method defined above is
- # delivered by invoking <tt>Notifier.deliver_signup_notification</tt>.
- #
- #
- # = HTML email
- #
- # To send mail as HTML, make sure your view (the <tt>.erb</tt> file) generates HTML and
- # set the content type to html.
+ # Notifier.welcome(david).deliver # sends the email
+ # mail = Notifier.welcome(david) # => a Mail::Message object
+ # mail.deliver # sends the email
#
- # class MyMailer < ActionMailer::Base
- # def signup_notification(recipient)
- # recipients recipient.email_address_with_name
- # subject "New account information"
- # from "system@example.com"
- # body :account => recipient
- # content_type "text/html"
- # end
- # end
- #
- #
- # = Multipart email
+ # You never instantiate your mailer class. Rather, you just call the method on the class itself.
#
- # You can explicitly specify multipart messages:
- #
- # class ApplicationMailer < ActionMailer::Base
- # def signup_notification(recipient)
- # recipients recipient.email_address_with_name
- # subject "New account information"
- # from "system@example.com"
- # content_type "multipart/alternative"
- # body :account => recipient
- #
- # part :content_type => "text/html",
- # :data => render_message("signup-as-html")
- #
- # part "text/plain" do |p|
- # p.body = render_message("signup-as-plain")
- # p.content_transfer_encoding = "base64"
- # end
- # end
- # end
+ # = Multipart Emails
#
# Multipart messages can also be used implicitly because Action Mailer will automatically
# detect and use multipart templates, where each template is named after the name of the action, followed
@@ -163,13 +152,12 @@ module ActionMailer #:nodoc:
# * signup_notification.text.plain.erb
# * signup_notification.text.html.erb
# * signup_notification.text.xml.builder
- # * signup_notification.text.x-yaml.erb
+ # * signup_notification.text.yaml.erb
#
- # Each would be rendered and added as a separate part to the message,
- # with the corresponding content type. The content type for the entire
- # message is automatically set to <tt>multipart/alternative</tt>, which indicates
- # that the email contains multiple different representations of the same email
- # body. The same body hash is passed to each template.
+ # Each would be rendered and added as a separate part to the message, with the corresponding content
+ # type. The content type for the entire message is automatically set to <tt>multipart/alternative</tt>,
+ # which indicates that the email contains multiple different representations of the same email
+ # body. The same instance variables defined in the action are passed to all email templates.
#
# Implicit template rendering is not performed if any attachments or parts have been added to the email.
# This means that you'll have to manually add each part to the email and set the content type of the email
@@ -177,32 +165,30 @@ module ActionMailer #:nodoc:
#
# = Attachments
#
- # Attachments can be added by using the +attachment+ method.
- #
- # Example:
+ # You can see above how to make a multipart HTML / Text email, to send attachments is just
+ # as easy:
#
# class ApplicationMailer < ActionMailer::Base
- # # attachments
- # def signup_notification(recipient)
- # recipients recipient.email_address_with_name
- # subject "New account information"
- # from "system@example.com"
- #
- # attachment :content_type => "image/jpeg",
- # :body => File.read("an-image.jpg")
- #
- # attachment "application/pdf" do |a|
- # a.body = generate_your_pdf_here()
- # end
+ # def welcome(recipient)
+ # attachments['free_book.pdf'] = { :data => File.read('path/to/file.pdf') }
+ # mail(:to => recipient, :subject => "New account information")
# end
# end
+ #
+ # Which will (if it had both a <tt>.text.erb</tt> and <tt>.html.erb</tt> tempalte in the view
+ # directory), send a complete <tt>multipart/mixed</tt> email with two parts, the first part being
+ # a <tt>multipart/alternative</tt> with the text and HTML email parts inside, and the second being
+ # a <tt>application/pdf</tt> with a Base64 encoded copy of the file.pdf book with the filename
+ # +free_book.pdf+.
#
#
# = Configuration options
#
# These options are specified on the class level, like <tt>ActionMailer::Base.template_root = "/my/templates"</tt>
#
- # * <tt>template_root</tt> - Determines the base from which template references will be made.
+ # * <tt>delivers_from</tt> - Pass this the address that then defaults as the +from+ address on all the
+ # emails sent. Can be overridden on a per mail basis by passing <tt>:from => 'another@address'</tt> in
+ # the +mail+ method.
#
# * <tt>logger</tt> - the logger is used for generating information on the mailing run if available.
# Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.
@@ -251,8 +237,8 @@ module ActionMailer #:nodoc:
# and appear last in the mime encoded message. You can also pick a different order from inside a method with
# +implicit_parts_order+.
class Base < AbstractController::Base
- include Quoting
- extend AdvAttrAccessor
+ include DeliveryMethods, Quoting
+ abstract!
include AbstractController::Logger
include AbstractController::Rendering
@@ -262,27 +248,23 @@ module ActionMailer #:nodoc:
include AbstractController::UrlFor
helper ActionMailer::MailHelper
- include ActionMailer::DeprecatedBody
-
- private_class_method :new #:nodoc:
- @@raise_delivery_errors = true
- cattr_accessor :raise_delivery_errors
+ include ActionMailer::OldApi
+ include ActionMailer::DeprecatedApi
- @@perform_deliveries = true
- cattr_accessor :perform_deliveries
+ private_class_method :new #:nodoc:
- @@deliveries = []
- cattr_accessor :deliveries
+ extlib_inheritable_accessor :default_from
+ self.default_from = nil
- @@default_charset = "utf-8"
- cattr_accessor :default_charset
+ extlib_inheritable_accessor :default_charset
+ self.default_charset = "utf-8"
- @@default_content_type = "text/plain"
- cattr_accessor :default_content_type
+ extlib_inheritable_accessor :default_content_type
+ self.default_content_type = "text/plain"
- @@default_mime_version = "1.0"
- cattr_accessor :default_mime_version
+ extlib_inheritable_accessor :default_mime_version
+ self.default_mime_version = "1.0"
# This specifies the order that the parts of a multipart email will be. Usually you put
# text/plain at the top so someone without a MIME capable email reader can read the plain
@@ -290,101 +272,20 @@ module ActionMailer #:nodoc:
#
# Any content type that is not listed here will be inserted in the order you add them to
# the email after the content types you list here.
- @@default_implicit_parts_order = [ "text/plain", "text/enriched", "text/html" ]
- cattr_accessor :default_implicit_parts_order
-
- @@protected_instance_variables = %w(@parts @mail)
- cattr_reader :protected_instance_variables
-
- # Specify the BCC addresses for the message
- adv_attr_accessor :bcc
-
- # Specify the CC addresses for the message.
- adv_attr_accessor :cc
-
- # Specify the charset to use for the message. This defaults to the
- # +default_charset+ specified for ActionMailer::Base.
- adv_attr_accessor :charset
-
- # Specify the content type for the message. This defaults to <tt>text/plain</tt>
- # in most cases, but can be automatically set in some situations.
- adv_attr_accessor :content_type
-
- # Specify the from address for the message.
- adv_attr_accessor :from
-
- # Specify the address (if different than the "from" address) to direct
- # replies to this message.
- adv_attr_accessor :reply_to
-
- # Specify additional headers to be added to the message.
- adv_attr_accessor :headers
-
- # Specify the order in which parts should be sorted, based on content-type.
- # This defaults to the value for the +default_implicit_parts_order+.
- adv_attr_accessor :implicit_parts_order
-
- # Defaults to "1.0", but may be explicitly given if needed.
- adv_attr_accessor :mime_version
-
- # The recipient addresses for the message, either as a string (for a single
- # address) or an array (for multiple addresses).
- adv_attr_accessor :recipients
-
- # The date on which the message was sent. If not set (the default), the
- # header will be set by the delivery agent.
- adv_attr_accessor :sent_on
-
- # Specify the subject of the message.
- adv_attr_accessor :subject
-
- # Specify the template name to use for current message. This is the "base"
- # template name, without the extension or directory, and may be used to
- # have multiple mailer methods share the same template.
- adv_attr_accessor :template
-
- # Override the mailer name, which defaults to an inflected version of the
- # mailer's class name. If you want to use a template in a non-standard
- # location, you can use this to specify that location.
- adv_attr_accessor :mailer_name
-
- # Expose the internal mail
- attr_reader :mail
-
- # Alias controller_path to mailer_name so render :partial in views work.
- alias :controller_path :mailer_name
+ extlib_inheritable_accessor :default_implicit_parts_order
+ self.default_implicit_parts_order = [ "text/plain", "text/enriched", "text/html" ]
class << self
- attr_writer :mailer_name
-
- delegate :settings, :settings=, :to => ActionMailer::DeliveryMethod::File, :prefix => :file
- delegate :settings, :settings=, :to => ActionMailer::DeliveryMethod::Sendmail, :prefix => :sendmail
- delegate :settings, :settings=, :to => ActionMailer::DeliveryMethod::Smtp, :prefix => :smtp
-
def mailer_name
@mailer_name ||= name.underscore
end
+ attr_writer :mailer_name
alias :controller_path :mailer_name
- def delivery_method=(method_name)
- @delivery_method = ActionMailer::DeliveryMethod.lookup_method(method_name)
- end
-
- def respond_to?(method_symbol, include_private = false) #:nodoc:
- matches_dynamic_method?(method_symbol) || super
- end
-
- def method_missing(method_symbol, *parameters) #:nodoc:
- if match = matches_dynamic_method?(method_symbol)
- case match[1]
- when 'create' then new(match[2], *parameters).mail
- when 'deliver' then new(match[2], *parameters).deliver!
- when 'new' then nil
- else super
- end
- else
- super
- end
+ # Sets who is the default sender for the e-mail
+ def delivers_from(value = nil)
+ self.default_from = value if value
+ self.default_from
end
# Receives a raw email, parses it into an email object, decodes it,
@@ -406,26 +307,24 @@ module ActionMailer #:nodoc:
end
end
- # Deliver the given mail object directly. This can be used to deliver
- # a preconstructed mail object, like:
- #
- # email = MyMailer.create_some_mail(parameters)
- # email.set_some_obscure_header "frobnicate"
- # MyMailer.deliver(email)
- def deliver(mail)
- new.deliver!(mail)
+ # Delivers a mail object. This is actually called by the <tt>Mail::Message</tt> object
+ # itself through a call back when you call <tt>:deliver</tt> on the Mail::Message,
+ # calling +deliver_mail+ directly and passing an Mail::Message will do nothing.
+ def deliver_mail(mail) #:nodoc:
+ ActiveSupport::Notifications.instrument("action_mailer.deliver") do |payload|
+ self.set_payload_for_mail(payload, mail)
+ yield # Let Mail do the delivery actions
+ end
end
- def template_root
- self.view_paths && self.view_paths.first
+ def respond_to?(method, *args) #:nodoc:
+ super || action_methods.include?(method.to_s)
end
- # Should template root overwrite the whole view_paths?
- def template_root=(root)
- self.view_paths = ActionView::Base.process_view_paths(root)
- end
+ protected
def set_payload_for_mail(payload, mail) #:nodoc:
+ payload[:mailer] = self.name
payload[:message_id] = mail.message_id
payload[:subject] = mail.subject
payload[:to] = mail.to
@@ -436,61 +335,16 @@ module ActionMailer #:nodoc:
payload[:mail] = mail.encoded
end
- private
-
- def matches_dynamic_method?(method_name) #:nodoc:
- method_name = method_name.to_s
- /^(create|deliver)_([_a-z]\w*)/.match(method_name) || /^(new)$/.match(method_name)
+ def method_missing(method, *args) #:nodoc:
+ if action_methods.include?(method.to_s)
+ new(method, *args).message
+ else
+ super
end
- end
-
- # Configure delivery method. Check ActionMailer::DeliveryMethod for more
- # instructions.
- superclass_delegating_reader :delivery_method
- self.delivery_method = :smtp
-
- # Add a part to a multipart message, with the given content-type. The
- # part itself is yielded to the block so that other properties (charset,
- # body, headers, etc.) can be set on it.
- def part(params)
- params = {:content_type => params} if String === params
-
- if custom_headers = params.delete(:headers)
- ActiveSupport::Deprecation.warn('Passing custom headers with :headers => {} is deprecated. ' <<
- 'Please just pass in custom headers directly.', caller[0,10])
- params.merge!(custom_headers)
end
-
- part = Mail::Part.new(params)
- yield part if block_given?
- @parts << part
end
- # Add an attachment to a multipart message. This is simply a part with the
- # content-disposition set to "attachment".
- def attachment(params, &block)
- super # Run deprecation hooks
-
- params = { :content_type => params } if String === params
- params = { :content_disposition => "attachment",
- :content_transfer_encoding => "base64" }.merge(params)
-
- part(params, &block)
- end
-
- # Allow you to set assigns for your template:
- #
- # body :greetings => "Hi"
- #
- # Will make @greetings available in the template to be rendered.
- def body(object=nil)
- returning(super) do # Run deprecation hooks
- if object.is_a?(Hash)
- @assigns_set = true
- object.each { |k, v| instance_variable_set(:"@#{k}", v) }
- end
- end
- end
+ attr_internal :message
# Instantiate a new mailer object. If +method_name+ is not +nil+, the mailer
# will be initialized according to the named method. If not, the mailer will
@@ -498,167 +352,247 @@ module ActionMailer #:nodoc:
# method, for instance).
def initialize(method_name=nil, *args)
super()
+ @_message = Mail.new
process(method_name, *args) if method_name
end
- # Process the mailer via the given +method_name+. The body will be
- # rendered and a new Mail object created.
- def process(method_name, *args)
- initialize_defaults(method_name)
- super
-
- # Create e-mail parts
- create_parts
-
- # Set the subject if not set yet
- @subject ||= I18n.t(:subject, :scope => [:actionmailer, mailer_name, method_name],
- :default => method_name.humanize)
-
- # Build the mail object itself
- create_mail
+ # Allows you to pass random and unusual headers to the new +Mail::Message+ object
+ # which will add them to itself.
+ #
+ # headers['X-Special-Domain-Specific-Header'] = "SecretValue"
+ #
+ # The resulting Mail::Message will have the following in it's header:
+ #
+ # X-Special-Domain-Specific-Header: SecretValue
+ def headers(args=nil)
+ if args
+ ActiveSupport::Deprecation.warn "headers(Hash) is deprecated, please do headers[key] = value instead", caller[0,2]
+ @headers = args
+ else
+ @_message
+ end
end
- # Delivers a Mail object. By default, it delivers the cached mail
- # object (from the <tt>create!</tt> method). If no cached mail object exists, and
- # no alternate has been given as the parameter, this will fail.
- def deliver!(mail = @mail)
- raise "no mail object available for delivery!" unless mail
+ # Allows you to add attachments to an email, like so:
+ #
+ # mail.attachments['filename.jpg'] = File.read('/path/to/filename.jpg')
+ #
+ # If you do this, then Mail will take the file name and work out the mime type
+ # set the Content-Type, Content-Disposition, Content-Transfer-Encoding and
+ # base64 encode the contents of the attachment all for you.
+ #
+ # You can also specify overrides if you want by passing a hash instead of a string:
+ #
+ # mail.attachments['filename.jpg'] = {:mime_type => 'application/x-gzip',
+ # :content => File.read('/path/to/filename.jpg')}
+ #
+ # If you want to use a different encoding than Base64, you can pass an encoding in,
+ # but then it is up to you to pass in the content pre-encoded, and don't expect
+ # Mail to know how to decode this data:
+ #
+ # file_content = SpecialEncode(File.read('/path/to/filename.jpg'))
+ # mail.attachments['filename.jpg'] = {:mime_type => 'application/x-gzip',
+ # :encoding => 'SpecialEncoding',
+ # :content => file_content }
+ #
+ # You can also search for specific attachments:
+ #
+ # # By Filename
+ # mail.attachments['filename.jpg'] #=> Mail::Part object or nil
+ #
+ # # or by index
+ # mail.attachments[0] #=> Mail::Part (first attachment)
+ #
+ def attachments
+ @_message.attachments
+ end
- ActiveSupport::Notifications.instrument("action_mailer.deliver",
- :template => template, :mailer => self.class.name) do |payload|
+ # The main method that creates the message and renders the email templates. There are
+ # two ways to call this method, with a block, or without a block.
+ #
+ # Both methods accept a headers hash. This hash allows you to specify the most used headers
+ # in an email message, these are:
+ #
+ # * <tt>:subject</tt> - The subject of the message, if this is omitted, ActionMailer will
+ # ask the Rails I18n class for a translated <tt>:subject</tt> in the scope of
+ # <tt>[:actionmailer, mailer_scope, action_name]</tt> or if this is missing, will translate the
+ # humanized version of the <tt>action_name</tt>
+ # * <tt>:to</tt> - Who the message is destined for, can be a string of addresses, or an array
+ # of addresses.
+ # * <tt>:from</tt> - Who the message is from, if missing, will use the <tt>:delivers_from</tt>
+ # value in the class (if it exists)
+ # * <tt>:cc</tt> - Who you would like to Carbon-Copy on this email, can be a string of addresses,
+ # or an array of addresses.
+ # * <tt>:bcc</tt> - Who you would like to Blind-Carbon-Copy on this email, can be a string of
+ # addresses, or an array of addresses.
+ # * <tt>:reply_to</tt> - Who to set the Reply-To header of the email to.
+ # * <tt>:date</tt> - The date to say the email was sent on.
+ #
+ # If you need other headers not listed above, use the <tt>headers['name'] = value</tt> method.
+ #
+ # When a <tt>:return_path</tt> is specified as header, that value will be used as the 'envelope from'
+ # address for the Mail message. Setting this is useful when you want delivery notifications
+ # sent to a different address than the one in <tt>:from</tt>. Mail will actually use the
+ # <tt>:return_path</tt> in preference to the <tt>:sender</tt> in preference to the <tt>:from</tt>
+ # field for the 'envelope from' value.
+ #
+ # If you do not pass a block to the +mail+ method, it will find all templates in the
+ # template path that match the method name that it is being called from, it will then
+ # create parts for each of these templates intelligently, making educated guesses
+ # on correct content type and sequence, and return a fully prepared Mail::Message
+ # ready to call <tt>:deliver</tt> on to send.
+ #
+ # If you do pass a block, you can render specific templates of your choice:
+ #
+ # mail(:to => 'mikel@test.lindsaar.net') do |format|
+ # format.text
+ # format.html
+ # end
+ #
+ # You can even render text directly without using a template:
+ #
+ # mail(:to => 'mikel@test.lindsaar.net') do |format|
+ # format.text { render :text => "Hello Mikel!" }
+ # format.html { render :text => "<h1>Hello Mikel!</h1>" }
+ # end
+ #
+ # Which will render a <tt>multipart/alternative</tt> email with <tt>text/plain</tt> and
+ # <tt>text/html</tt> parts.
+ #
+ # The block syntax also allows you to customize the part headers if desired:
+ #
+ # mail(:to => 'mikel@test.lindsaar.net') do |format|
+ # format.text(:content_transfer_encoding => "base64")
+ # format.html
+ # end
+ #
+ def mail(headers={}, &block)
+ # Guard flag to prevent both the old and the new API from firing
+ # Should be removed when old API is removed
+ @mail_was_called = true
+ m = @_message
+
+ # Give preference to headers and fallback to the ones set in mail
+ content_type = headers[:content_type] || m.content_type
+ charset = headers[:charset] || m.charset || self.class.default_charset.dup
+ mime_version = headers[:mime_version] || m.mime_version || self.class.default_mime_version.dup
+
+ # Set fields quotings
+ headers[:subject] ||= default_subject
+ headers[:from] ||= self.class.default_from.dup
+ quote_fields!(headers, charset)
+
+ # Render the templates and blocks
+ responses, sort_order = collect_responses_and_sort_order(headers, &block)
+
+ create_parts_from_responses(m, responses, charset)
- self.class.set_payload_for_mail(payload, mail)
+ # Tidy up content type, charset, mime version and sort order
+ m.content_type = set_content_type(m, content_type)
+ m.charset = charset
+ m.mime_version = mime_version
+ sort_order = headers[:parts_order] || sort_order || self.class.default_implicit_parts_order.dup
- begin
- self.delivery_method.perform_delivery(mail) if perform_deliveries
- rescue Exception => e # Net::SMTP errors or sendmail pipe errors
- raise e if raise_delivery_errors
- end
+ if m.multipart?
+ m.body.set_sort_order(sort_order)
+ m.body.sort_parts!
end
- mail
+ # Finaly set delivery behavior configured in class
+ wrap_delivery_behavior!(headers[:delivery_method])
+ m
end
- private
-
- # Render a message but does not set it as mail body. Useful for rendering
- # data for part and attachments.
- #
- # Examples:
- #
- # render_message "special_message"
- # render_message :template => "special_message"
- # render_message :inline => "<%= 'Hi!' %>"
- def render_message(object)
- case object
- when String
- render_to_body(:template => object)
- else
- render_to_body(object)
- end
+ protected
+
+ def set_content_type(m, user_content_type)
+ params = m.content_type_parameters || {}
+ case
+ when user_content_type.present?
+ user_content_type
+ when m.has_attachments?
+ ["multipart", "mixed", params]
+ when m.multipart?
+ ["multipart", "alternative", params]
+ else
+ self.class.default_content_type.dup
end
+ end
- # Set up the default values for the various instance variables of this
- # mailer. Subclasses may override this method to provide different
- # defaults.
- def initialize_defaults(method_name) #:nodoc:
- @charset ||= @@default_charset.dup
- @content_type ||= @@default_content_type.dup
- @implicit_parts_order ||= @@default_implicit_parts_order.dup
- @mime_version ||= @@default_mime_version.dup if @@default_mime_version
-
- @mailer_name ||= self.class.mailer_name.dup
- @template ||= method_name
-
- @parts ||= []
- @headers ||= {}
- @sent_on ||= Time.now
-
- super # Run deprecation hooks
- end
-
- def create_parts #:nodoc:
- super # Run deprecation hooks
-
- if String === response_body
- @parts.unshift create_inline_part(response_body)
- else
- self.class.template_root.find_all(@template, {}, @mailer_name).each do |template|
- @parts << create_inline_part(render_to_body(:_template => template), template.mime_type)
- end
+ def default_subject #:nodoc:
+ mailer_scope = self.class.mailer_name.gsub('/', '.')
+ I18n.t(:subject, :scope => [:actionmailer, mailer_scope, action_name], :default => action_name.humanize)
+ end
- if @parts.size > 1
- @content_type = "multipart/alternative" if @content_type !~ /^multipart/
- end
+ # TODO: Move this into Mail
+ def quote_fields!(headers, charset) #:nodoc:
+ m = @_message
+ m.subject ||= quote_if_necessary(headers[:subject], charset) if headers[:subject]
+ m.to ||= quote_address_if_necessary(headers[:to], charset) if headers[:to]
+ m.from ||= quote_address_if_necessary(headers[:from], charset) if headers[:from]
+ m.cc ||= quote_address_if_necessary(headers[:cc], charset) if headers[:cc]
+ m.bcc ||= quote_address_if_necessary(headers[:bcc], charset) if headers[:bcc]
+ m.reply_to ||= quote_address_if_necessary(headers[:reply_to], charset) if headers[:reply_to]
+ m.date ||= headers[:date] if headers[:date]
+ end
- # If this is a multipart e-mail add the mime_version if it is not
- # already set.
- @mime_version ||= "1.0" if !@parts.empty?
+ def collect_responses_and_sort_order(headers) #:nodoc:
+ responses, sort_order = [], nil
+
+ if block_given?
+ collector = ActionMailer::Collector.new(self) { render(action_name) }
+ yield(collector)
+ sort_order = collector.responses.map { |r| r[:content_type] }
+ responses = collector.responses
+ elsif headers[:body]
+ responses << {
+ :body => headers[:body],
+ :content_type => self.class.default_content_type.dup
+ }
+ else
+ each_template do |template|
+ responses << {
+ :body => render_to_body(:_template => template),
+ :content_type => template.mime_type.to_s
+ }
end
end
- def create_inline_part(body, mime_type=nil) #:nodoc:
- ct = mime_type || "text/plain"
- main_type, sub_type = split_content_type(ct.to_s)
-
- Mail::Part.new(
- :content_type => [main_type, sub_type, {:charset => charset}],
- :content_disposition => "inline",
- :body => body
- )
- end
-
- def create_mail #:nodoc:
- m = Mail.new
-
- m.subject, = quote_any_if_necessary(charset, subject)
- m.to, m.from = quote_any_address_if_necessary(charset, recipients, from)
- m.bcc = quote_address_if_necessary(bcc, charset) unless bcc.nil?
- m.cc = quote_address_if_necessary(cc, charset) unless cc.nil?
- m.reply_to = quote_address_if_necessary(reply_to, charset) unless reply_to.nil?
- m.mime_version = mime_version unless mime_version.nil?
- m.date = sent_on.to_time rescue sent_on if sent_on
-
- headers.each { |k, v| m[k] = v }
-
- real_content_type, ctype_attrs = parse_content_type
- main_type, sub_type = split_content_type(real_content_type)
-
- if @parts.size == 1 && @parts.first.parts.empty?
- m.content_type([main_type, sub_type, ctype_attrs])
- m.body = @parts.first.body.encoded
- else
- @parts.each do |p|
- m.add_part(p)
- end
+ [responses, sort_order]
+ end
- m.body.set_sort_order(@implicit_parts_order)
- m.body.sort_parts!
+ def each_template(&block) #:nodoc:
+ self.class.view_paths.each do |load_paths|
+ templates = load_paths.find_all(action_name, {}, self.class.mailer_name)
+ templates = templates.uniq_by { |t| t.details[:formats] }
- if real_content_type =~ /multipart/
- ctype_attrs.delete "charset"
- m.content_type([main_type, sub_type, ctype_attrs])
- end
+ unless templates.empty?
+ templates.each(&block)
+ return
end
-
- m.content_transfer_encoding = '8bit' unless m.body.only_us_ascii?
-
- @mail = m
- end
-
- def split_content_type(ct) #:nodoc:
- ct.to_s.split("/")
end
+ end
- def parse_content_type(defaults=nil) #:nodoc:
- if @content_type.blank?
- [ nil, {} ]
- else
- ctype, *attrs = @content_type.split(/;\s*/)
- attrs = attrs.inject({}) { |h,s| k,v = s.split(/\=/, 2); h[k] = v; h }
- [ctype, {"charset" => @charset}.merge(attrs)]
- end
+ def create_parts_from_responses(m, responses, charset) #:nodoc:
+ if responses.size == 1 && !m.has_attachments?
+ headers = responses[0]
+ headers.each { |k,v| m[k] = v }
+ return responses[0][:content_type]
+ elsif responses.size > 1 && m.has_attachments?
+ container = Mail::Part.new
+ container.content_type = "multipart/alternative"
+ responses.each { |r| insert_part(container, r, charset) }
+ m.add_part(container)
+ else
+ responses.each { |r| insert_part(m, r, charset) }
end
+ end
+
+ def insert_part(container, response, charset) #:nodoc:
+ response[:charset] ||= charset
+ part = Mail::Part.new(response)
+ container.add_part(part)
+ end
end
end