From 4a6eba3232fec13892f36fc4730bb2deef342fc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim=20and=20Mikel=20Lindsaar?= Date: Mon, 25 Jan 2010 23:46:09 +1100 Subject: [PATCH] Added initial documentation for the new API --- actionmailer/CHANGELOG | 2 + actionmailer/README | 62 ++++-- actionmailer/lib/action_mailer/base.rb | 255 +++++++++++++++---------- 3 files changed, 201 insertions(+), 118 deletions(-) diff --git a/actionmailer/CHANGELOG b/actionmailer/CHANGELOG index 785bf98c55..0018a2ed5d 100644 --- a/actionmailer/CHANGELOG +++ b/actionmailer/CHANGELOG @@ -1,5 +1,7 @@ *Rails 3.0 (pending)* +* Whole new API added with tests. See base.rb for full details. Old API is deprecated. + * The Mail::Message class has helped methods for all the field types that return 'common' defaults for the common use case, so to get the subject, mail.subject will give you a string, mail.date will give you a DateTime object, mail.from will give you an array of address specs (mikel@test.lindsaar.net) etc. If you want to access the field object itself, call mail[:field_name] which will return the field object you want, which you can then chain, like mail[:from].formatted * Mail#content_type now returns the content_type field as a string. If you want the mime type of a mail, then you call Mail#mime_type (eg, text/plain), if you want the parameters of the content type field, you call Mail#content_type_parameters which gives you a hash, eg {'format' => 'flowed', 'charset' => 'utf-8'} diff --git a/actionmailer/README b/actionmailer/README index 0e16ea6ec6..542996f87b 100644 --- a/actionmailer/README +++ b/actionmailer/README @@ -5,51 +5,74 @@ are used to consolidate code for sending out forgotten passwords, welcome wishes on signup, invoices for billing, and any other use case that requires a written notification to either a person or another system. +Action Mailer is in essence a wrapper around Action Controller and the +Mail gem. It provides a way to make emails using templates in the same +way that Action Controller renders views using templates. + Additionally, an Action Mailer class can be used to process incoming email, such as allowing a weblog to accept new posts from an email (which could even have been sent from a phone). == Sending emails -The framework works by setting up all the email details, except the body, -in methods on the service layer. Subject, recipients, sender, and timestamp -are all set up this way. An example of such a method: +The framework works by initializing any instance variables you want to be +available in the email template, followed by a call to +mail+ to deliver +the email. + +This can be as simple as: + + class Notifier < ActionMailer::Base + + delivers_from 'system@loudthinking.com' + + def welcome(recipient) + @recipient = recipient + mail(:to => recipient, + :subject => "[Signed up] Welcome #{recipient}") + end - def signed_up(recipient) - recipients recipient - subject "[Signed up] Welcome #{recipient}" - from "system@loudthinking.com" - body :recipient => recipient end The body of the email is created by using an Action View template (regular -ERb) that has the content of the body hash parameter available as instance variables. +ERb) that has the instance variables that are declared in the mailer action. + So the corresponding body template for the method above could look like this: Hello there, Mr. <%= @recipient %> + + Thank you for signing up! And if the recipient was given as "david@loudthinking.com", the email generated would look like this: - Date: Sun, 12 Dec 2004 00:00:00 +0100 + Date: Mon, 25 Jan 2010 22:48:09 +1100 From: system@loudthinking.com To: david@loudthinking.com + Message-ID: <4b5d84f9dd6a5_7380800b81ac29578@void.loudthinking.com.mail> Subject: [Signed up] Welcome david@loudthinking.com + Mime-Version: 1.0 + Content-Type: text/plain; + charset="US-ASCII"; + Content-Transfer-Encoding: 7bit Hello there, Mr. david@loudthinking.com -You never actually call the instance methods like signed_up directly. Instead, -you call class methods like deliver_* and create_* that are automatically -created for each instance method. So if the signed_up method sat on -ApplicationMailer, it would look like this: +In previous version of rails you would call create_method_name and +deliver_method_name. Rails 3.0 has a much simpler interface, you +simply call the method and optionally call +deliver+ on the return value. - ApplicationMailer.create_signed_up("david@loudthinking.com") # => tmail object for testing - ApplicationMailer.deliver_signed_up("david@loudthinking.com") # sends the email - ApplicationMailer.new.signed_up("david@loudthinking.com") # won't work! +Calling the method returns a Mail Message object: + + message = Notifier.welcome #=> Returns a Mail::Message object + message.deliver #=> delivers the email + +Or you can just chain the methods together like: + + Notifier.welcome.deliver # Creates the email and sends it immediately == Receiving emails @@ -103,16 +126,13 @@ The Base class has the full list of configuration options. Here's an example: Action Mailer requires that the Action Pack is either available to be required immediately or is accessible as a GEM. +Additionally, Action Mailer requires the Mail gem, http://github.com/mikel/mail == Bundled software -* tmail 0.10.8 by Minero Aoki released under LGPL - Read more on http://i.loveruby.net/en/prog/tmail.html - * Text::Format 0.63 by Austin Ziegler released under OpenSource Read more on http://www.halostatue.ca/ruby/Text__Format.html - == Download The latest version of Action Mailer can be found at diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 47d0ffaf68..51a8c07a28 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -16,46 +16,57 @@ 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 "] - # 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 "], + # :subject => "New account information" } + # end # end - # end - # - # Mailer methods have the following configuration methods available. - # - # * recipients - Takes one or more email addresses. These addresses are where your email will be delivered to. Sets the To: header. - # * subject - The subject of your email. Sets the Subject: header. - # * from - Who the email you are sending is from. Sets the From: header. - # * cc - Takes one or more email addresses. These addresses will receive a carbon copy of your email. Sets the Cc: header. - # * bcc - Takes one or more email addresses. These addresses will receive a blind carbon copy of your email. Sets the Bcc: header. - # * reply_to - Takes one or more email addresses. These addresses will be listed as the default recipients when replying to your email. Sets the Reply-To: header. - # * sent_on - The date on which the message was sent. If not set, the header will be set by the delivery agent. - # * content_type - Specify the content type of the message. Defaults to text/plain. - # * headers - Specify additional headers to be set for the message, e.g. headers 'X-Mail-Count' => 107370. - # - # When a headers 'return-path' 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 from. + # + # Within the mailer method, you have access to the following methods: + # + # * attachments[]= - Allows you to add attachments to your email in an intuitive + # manner; attachments['filename.png'] = File.read('path/to/filename.png') + # * headers[]= - Allows you to specify non standard headers in your email such + # as headers['X-No-Spam'] = 'True' + # * mail - 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 Subject, To, From, Cc, Bcc, + # Reply-To and Date. See the ActionMailer#mail method for more details. + # + # If you need other headers not listed above, use the headers['name'] = value 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/alternate+ email. + # + # If you want to explicitly render only certain templates, pass a block: + # + # mail(:to => user.emai) do |format| + # format.text + # format.enriched, {:content_type => 'text/rtf'} + # format.html + # 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 .erb file with the same name as the method - # in your mailer model. For example, in the mailer defined above, the template at - # app/views/notifier/signup_notification.erb 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 .erb file with the same + # name as the method in your mailer model. For example, in the mailer defined above, the template at + # app/views/notifier/signup_notification.text.erb would be used to generate the email. # # Variables defined in the model are accessible as instance variables in the view. # @@ -111,54 +122,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) + # Notifier.welcome(david).deliver # sends the email + # mail = Notifier.welcome(david) # => a Mail::Message object + # mail.deliver # sends the email # - # You never instantiate your mailer class. Rather, your delivery instance - # methods are automatically wrapped in class methods that start with the word - # deliver_ followed by the name of the mailer method that you would - # like to deliver. The signup_notification method defined above is - # delivered by invoking Notifier.deliver_signup_notification. + # You never instantiate your mailer class. Rather, you just call the method on the class itself. # - # - # = HTML email - # - # To send mail as HTML, make sure your view (the .erb file) generates HTML and - # set the content type to html. - # - # 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 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 @@ -170,11 +140,10 @@ module ActionMailer #:nodoc: # * signup_notification.text.xml.builder # * signup_notification.text.x-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 multipart/alternative, 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 multipart/alternative, + # 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 @@ -182,31 +151,31 @@ 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 .text.erb and .html.erb tempalte in the view + # directory), send a complete multipart/mixed email with two parts, the first part being + # a multipart/alternate with the text and HTML email parts inside, and the second being + # a application/pdf 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 ActionMailer::Base.template_root = "/my/templates" # + # * delivers_from - 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 :from => 'another@address' in + # the +mail+ method. + # # * template_root - Determines the base from which template references will be made. # # * logger - the logger is used for generating information on the mailing run if available. @@ -326,6 +295,9 @@ module ActionMailer #:nodoc: end end + # Delivers a mail object. This is actually called by the Mail::Message object + # itself through a call back when you call :deliver 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) @@ -374,6 +346,14 @@ module ActionMailer #:nodoc: process(method_name, *args) if method_name end + # 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] @@ -383,10 +363,91 @@ module ActionMailer #:nodoc: end end + # 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 + # 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: + # + # * :subject - The subject of the message, if this is omitted, ActionMailer will + # ask the Rails I18n class for a translated :subject in the scope of + # [:actionmailer, mailer_scope, action_name] or if this is missing, will translate the + # humanized version of the action_name + # * :to - Who the message is destined for, can be a string of addresses, or an array + # of addresses. + # * :from - Who the message is from, if missing, will use the :delivers_from + # value in the class (if it exists) + # * :cc - Who you would like to Carbon-Copy on this email, can be a string of addresses, + # or an array of addresses. + # * :bcc - Who you would like to Blind-Carbon-Copy on this email, can be a string of + # addresses, or an array of addresses. + # * :reply_to - Who to set the Reply-To header of the email to. + # * :date - The date to say the email was sent on. + # + # If you need other headers not listed above, use the headers['name'] = value method. + # + # When a :return_path is specified, 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 :from. Mail will actually use the + # :return_path in preference to the :sender in preference to the :from + # 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 :deliver 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 => "

Hello Mikel!

" } + # end + # + # Which will render a multipart/alternate email with text/plain and + # text/html parts. 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