actionmailer 3.2.9 → 6.1.7.6

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionmailer might be problematic. Click here for more details.

@@ -1,36 +1,41 @@
1
- require 'mail'
2
- require 'action_mailer/collector'
3
- require 'active_support/core_ext/array/wrap'
4
- require 'active_support/core_ext/object/blank'
5
- require 'active_support/core_ext/proc'
6
- require 'active_support/core_ext/string/inflections'
7
- require 'active_support/core_ext/hash/except'
8
- require 'active_support/core_ext/module/anonymous'
9
- require 'action_mailer/log_subscriber'
10
-
11
- module ActionMailer #:nodoc:
1
+ # frozen_string_literal: true
2
+
3
+ require "action_mailer/mail_with_error_handling"
4
+ require "action_mailer/collector"
5
+ require "active_support/core_ext/string/inflections"
6
+ require "active_support/core_ext/hash/except"
7
+ require "active_support/core_ext/module/anonymous"
8
+
9
+ require "action_mailer/log_subscriber"
10
+ require "action_mailer/rescuable"
11
+
12
+ module ActionMailer
12
13
  # Action Mailer allows you to send email from your application using a mailer model and views.
13
14
  #
14
15
  # = Mailer Models
15
16
  #
16
17
  # To use Action Mailer, you need to create a mailer model.
17
18
  #
18
- # $ rails generate mailer Notifier
19
+ # $ bin/rails generate mailer Notifier
19
20
  #
20
- # The generated model inherits from <tt>ActionMailer::Base</tt>. Emails are defined by creating methods
21
- # within the model which are then used to set variables to be used in the mail template, to
22
- # change options on the mail, or to add attachments.
21
+ # The generated model inherits from <tt>ApplicationMailer</tt> which in turn
22
+ # inherits from <tt>ActionMailer::Base</tt>. A mailer model defines methods
23
+ # used to generate an email message. In these methods, you can set up variables to be used in
24
+ # the mailer views, options on the mail itself such as the <tt>:from</tt> address, and attachments.
23
25
  #
24
- # Examples:
26
+ # class ApplicationMailer < ActionMailer::Base
27
+ # default from: 'from@example.com'
28
+ # layout 'mailer'
29
+ # end
25
30
  #
26
- # class Notifier < ActionMailer::Base
27
- # default :from => 'no-reply@example.com',
28
- # :return_path => 'system@example.com'
31
+ # class NotifierMailer < ApplicationMailer
32
+ # default from: 'no-reply@example.com',
33
+ # return_path: 'system@example.com'
29
34
  #
30
35
  # def welcome(recipient)
31
36
  # @account = recipient
32
- # mail(:to => recipient.email_address_with_name,
33
- # :bcc => ["bcc@example.com", "Order Watcher <watcher@example.com>"])
37
+ # mail(to: recipient.email_address_with_name,
38
+ # bcc: ["bcc@example.com", "Order Watcher <watcher@example.com>"])
34
39
  # end
35
40
  # end
36
41
  #
@@ -43,41 +48,38 @@ module ActionMailer #:nodoc:
43
48
  # in the same manner as <tt>attachments[]=</tt>
44
49
  #
45
50
  # * <tt>headers[]=</tt> - Allows you to specify any header field in your email such
46
- # as <tt>headers['X-No-Spam'] = 'True'</tt>. Note, while most fields like <tt>To:</tt>
47
- # <tt>From:</tt> can only appear once in an email header, other fields like <tt>X-Anything</tt>
48
- # can appear multiple times. If you want to change a field that can appear multiple times,
49
- # you need to set it to nil first so that Mail knows you are replacing it and not adding
50
- # another field of the same name.
51
+ # as <tt>headers['X-No-Spam'] = 'True'</tt>. Note that declaring a header multiple times
52
+ # will add many fields of the same name. Read #headers doc for more information.
51
53
  #
52
54
  # * <tt>headers(hash)</tt> - Allows you to specify multiple headers in your email such
53
55
  # as <tt>headers({'X-No-Spam' => 'True', 'In-Reply-To' => '1234@message.id'})</tt>
54
56
  #
55
57
  # * <tt>mail</tt> - Allows you to specify email to be sent.
56
58
  #
57
- # The hash passed to the mail method allows you to specify any header that a Mail::Message
58
- # will accept (any valid Email header including optional fields).
59
+ # The hash passed to the mail method allows you to specify any header that a <tt>Mail::Message</tt>
60
+ # will accept (any valid email header including optional fields).
59
61
  #
60
- # The mail method, if not passed a block, will inspect your views and send all the views with
62
+ # The +mail+ method, if not passed a block, will inspect your views and send all the views with
61
63
  # the same name as the method, so the above action would send the +welcome.text.erb+ view
62
- # file as well as the +welcome.text.html.erb+ view file in a +multipart/alternative+ email.
64
+ # file as well as the +welcome.html.erb+ view file in a +multipart/alternative+ email.
63
65
  #
64
66
  # If you want to explicitly render only certain templates, pass a block:
65
67
  #
66
- # mail(:to => user.email) do |format|
68
+ # mail(to: user.email) do |format|
67
69
  # format.text
68
70
  # format.html
69
71
  # end
70
72
  #
71
73
  # The block syntax is also useful in providing information specific to a part:
72
74
  #
73
- # mail(:to => user.email) do |format|
74
- # format.text(:content_transfer_encoding => "base64")
75
+ # mail(to: user.email) do |format|
76
+ # format.text(content_transfer_encoding: "base64")
75
77
  # format.html
76
78
  # end
77
79
  #
78
80
  # Or even to render a special view:
79
81
  #
80
- # mail(:to => user.email) do |format|
82
+ # mail(to: user.email) do |format|
81
83
  # format.text
82
84
  # format.html { render "some_other_template" }
83
85
  # end
@@ -87,26 +89,27 @@ module ActionMailer #:nodoc:
87
89
  # Like Action Controller, each mailer class has a corresponding view directory in which each
88
90
  # method of the class looks for a template with its name.
89
91
  #
90
- # To define a template to be used with a mailing, create an <tt>.erb</tt> file with the same
92
+ # To define a template to be used with a mailer, create an <tt>.erb</tt> file with the same
91
93
  # name as the method in your mailer model. For example, in the mailer defined above, the template at
92
- # <tt>app/views/notifier/welcome.text.erb</tt> would be used to generate the email.
94
+ # <tt>app/views/notifier_mailer/welcome.text.erb</tt> would be used to generate the email.
93
95
  #
94
- # Variables defined in the model are accessible as instance variables in the view.
96
+ # Variables defined in the methods of your mailer model are accessible as instance variables in their
97
+ # corresponding view.
95
98
  #
96
99
  # Emails by default are sent in plain text, so a sample view for our model example might look like this:
97
100
  #
98
101
  # Hi <%= @account.name %>,
99
102
  # Thanks for joining our service! Please check back often.
100
103
  #
101
- # You can even use Action Pack helpers in these views. For example:
104
+ # You can even use Action View helpers in these views. For example:
102
105
  #
103
106
  # You got a new note!
104
- # <%= truncate(@note.body, :length => 25) %>
107
+ # <%= truncate(@note.body, length: 25) %>
105
108
  #
106
109
  # If you need to access the subject, from or the recipients in the view, you can do that through message object:
107
110
  #
108
111
  # You got a new note from <%= message.from %>!
109
- # <%= truncate(@note.body, :length => 25) %>
112
+ # <%= truncate(@note.body, length: 25) %>
110
113
  #
111
114
  #
112
115
  # = Generating URLs
@@ -117,48 +120,62 @@ module ActionMailer #:nodoc:
117
120
  #
118
121
  # When using <tt>url_for</tt> you'll need to provide the <tt>:host</tt>, <tt>:controller</tt>, and <tt>:action</tt>:
119
122
  #
120
- # <%= url_for(:host => "example.com", :controller => "welcome", :action => "greeting") %>
123
+ # <%= url_for(host: "example.com", controller: "welcome", action: "greeting") %>
121
124
  #
122
125
  # When using named routes you only need to supply the <tt>:host</tt>:
123
126
  #
124
- # <%= users_url(:host => "example.com") %>
127
+ # <%= users_url(host: "example.com") %>
125
128
  #
126
- # You should use the <tt>named_route_url</tt> style (which generates absolute URLs) and avoid using the
127
- # <tt>named_route_path</tt> style (which generates relative URLs), since clients reading the mail will
129
+ # You should use the <tt>named_route_url</tt> style (which generates absolute URLs) and avoid using the
130
+ # <tt>named_route_path</tt> style (which generates relative URLs), since clients reading the mail will
128
131
  # have no concept of a current URL from which to determine a relative path.
129
132
  #
130
133
  # It is also possible to set a default host that will be used in all mailers by setting the <tt>:host</tt>
131
134
  # option as a configuration option in <tt>config/application.rb</tt>:
132
135
  #
133
- # config.action_mailer.default_url_options = { :host => "example.com" }
136
+ # config.action_mailer.default_url_options = { host: "example.com" }
134
137
  #
135
- # When you decide to set a default <tt>:host</tt> for your mailers, then you need to make sure to use the
136
- # <tt>:only_path => false</tt> option when using <tt>url_for</tt>. Since the <tt>url_for</tt> view helper
137
- # will generate relative URLs by default when a <tt>:host</tt> option isn't explicitly provided, passing
138
- # <tt>:only_path => false</tt> will ensure that absolute URLs are generated.
138
+ # You can also define a <tt>default_url_options</tt> method on individual mailers to override these
139
+ # default settings per-mailer.
140
+ #
141
+ # By default when <tt>config.force_ssl</tt> is +true+, URLs generated for hosts will use the HTTPS protocol.
139
142
  #
140
143
  # = Sending mail
141
144
  #
142
- # Once a mailer action and template are defined, you can deliver your message or create it and save it
143
- # for delivery later:
145
+ # Once a mailer action and template are defined, you can deliver your message or defer its creation and
146
+ # delivery for later:
147
+ #
148
+ # NotifierMailer.welcome(User.first).deliver_now # sends the email
149
+ # mail = NotifierMailer.welcome(User.first) # => an ActionMailer::MessageDelivery object
150
+ # mail.deliver_now # generates and sends the email now
151
+ #
152
+ # The <tt>ActionMailer::MessageDelivery</tt> class is a wrapper around a delegate that will call
153
+ # your method to generate the mail. If you want direct access to the delegator, or <tt>Mail::Message</tt>,
154
+ # you can call the <tt>message</tt> method on the <tt>ActionMailer::MessageDelivery</tt> object.
155
+ #
156
+ # NotifierMailer.welcome(User.first).message # => a Mail::Message object
144
157
  #
145
- # Notifier.welcome(david).deliver # sends the email
146
- # mail = Notifier.welcome(david) # => a Mail::Message object
147
- # mail.deliver # sends the email
158
+ # Action Mailer is nicely integrated with Active Job so you can generate and send emails in the background
159
+ # (example: outside of the request-response cycle, so the user doesn't have to wait on it):
160
+ #
161
+ # NotifierMailer.welcome(User.first).deliver_later # enqueue the email sending to Active Job
162
+ #
163
+ # Note that <tt>deliver_later</tt> will execute your method from the background job.
148
164
  #
149
165
  # You never instantiate your mailer class. Rather, you just call the method you defined on the class itself.
166
+ # All instance methods are expected to return a message object to be sent.
150
167
  #
151
168
  # = Multipart Emails
152
169
  #
153
- # Multipart messages can also be used implicitly because Action Mailer will automatically detect and use
154
- # multipart templates, where each template is named after the name of the action, followed by the content
155
- # type. Each such detected template will be added as a separate part to the message.
170
+ # Multipart messages can also be used implicitly because Action Mailer will automatically detect and use
171
+ # multipart templates, where each template is named after the name of the action, followed by the content
172
+ # type. Each such detected template will be added to the message, as a separate part.
156
173
  #
157
174
  # For example, if the following templates exist:
158
175
  # * signup_notification.text.erb
159
- # * signup_notification.text.html.erb
160
- # * signup_notification.text.xml.builder
161
- # * signup_notification.text.yaml.erb
176
+ # * signup_notification.html.erb
177
+ # * signup_notification.xml.builder
178
+ # * signup_notification.yml.erb
162
179
  #
163
180
  # Each would be rendered and added as a separate part to the message, with the corresponding content
164
181
  # type. The content type for the entire message is automatically set to <tt>multipart/alternative</tt>,
@@ -173,28 +190,51 @@ module ActionMailer #:nodoc:
173
190
  #
174
191
  # Sending attachment in emails is easy:
175
192
  #
176
- # class ApplicationMailer < ActionMailer::Base
193
+ # class NotifierMailer < ApplicationMailer
177
194
  # def welcome(recipient)
178
195
  # attachments['free_book.pdf'] = File.read('path/to/file.pdf')
179
- # mail(:to => recipient, :subject => "New account information")
196
+ # mail(to: recipient, subject: "New account information")
180
197
  # end
181
198
  # end
182
199
  #
183
- # Which will (if it had both a <tt>welcome.text.erb</tt> and <tt>welcome.text.html.erb</tt>
200
+ # Which will (if it had both a <tt>welcome.text.erb</tt> and <tt>welcome.html.erb</tt>
184
201
  # template in the view directory), send a complete <tt>multipart/mixed</tt> email with two parts,
185
202
  # the first part being a <tt>multipart/alternative</tt> with the text and HTML email parts inside,
186
203
  # and the second being a <tt>application/pdf</tt> with a Base64 encoded copy of the file.pdf book
187
204
  # with the filename +free_book.pdf+.
188
205
  #
206
+ # If you need to send attachments with no content, you need to create an empty view for it,
207
+ # or add an empty body parameter like this:
208
+ #
209
+ # class NotifierMailer < ApplicationMailer
210
+ # def welcome(recipient)
211
+ # attachments['free_book.pdf'] = File.read('path/to/file.pdf')
212
+ # mail(to: recipient, subject: "New account information", body: "")
213
+ # end
214
+ # end
215
+ #
216
+ # You can also send attachments with html template, in this case you need to add body, attachments,
217
+ # and custom content type like this:
218
+ #
219
+ # class NotifierMailer < ApplicationMailer
220
+ # def welcome(recipient)
221
+ # attachments["free_book.pdf"] = File.read("path/to/file.pdf")
222
+ # mail(to: recipient,
223
+ # subject: "New account information",
224
+ # content_type: "text/html",
225
+ # body: "<html><body>Hello there</body></html>")
226
+ # end
227
+ # end
228
+ #
189
229
  # = Inline Attachments
190
230
  #
191
231
  # You can also specify that a file should be displayed inline with other HTML. This is useful
192
232
  # if you want to display a corporate logo or a photo.
193
233
  #
194
- # class ApplicationMailer < ActionMailer::Base
234
+ # class NotifierMailer < ApplicationMailer
195
235
  # def welcome(recipient)
196
236
  # attachments.inline['photo.png'] = File.read('path/to/photo.png')
197
- # mail(:to => recipient, :subject => "Here is what we look like")
237
+ # mail(to: recipient, subject: "Here is what we look like")
198
238
  # end
199
239
  # end
200
240
  #
@@ -210,7 +250,7 @@ module ActionMailer #:nodoc:
210
250
  #
211
251
  # <h1>Please Don't Cringe</h1>
212
252
  #
213
- # <%= image_tag attachments['photo.png'].url, :alt => 'Our Photo', :class => 'photo' -%>
253
+ # <%= image_tag attachments['photo.png'].url, alt: 'Our Photo', class: 'photo' -%>
214
254
  #
215
255
  # = Observing and Intercepting Mails
216
256
  #
@@ -223,24 +263,24 @@ module ActionMailer #:nodoc:
223
263
  # An interceptor class must implement the <tt>:delivering_email(message)</tt> method which will be
224
264
  # called before the email is sent, allowing you to make modifications to the email before it hits
225
265
  # the delivery agents. Your class should make any needed modifications directly to the passed
226
- # in Mail::Message instance.
266
+ # in <tt>Mail::Message</tt> instance.
227
267
  #
228
268
  # = Default Hash
229
269
  #
230
270
  # Action Mailer provides some intelligent defaults for your emails, these are usually specified in a
231
271
  # default method inside the class definition:
232
272
  #
233
- # class Notifier < ActionMailer::Base
234
- # default :sender => 'system@example.com'
273
+ # class NotifierMailer < ApplicationMailer
274
+ # default sender: 'system@example.com'
235
275
  # end
236
276
  #
237
277
  # You can pass in any header value that a <tt>Mail::Message</tt> accepts. Out of the box,
238
278
  # <tt>ActionMailer::Base</tt> sets the following:
239
279
  #
240
- # * <tt>:mime_version => "1.0"</tt>
241
- # * <tt>:charset => "UTF-8",</tt>
242
- # * <tt>:content_type => "text/plain",</tt>
243
- # * <tt>:parts_order => [ "text/plain", "text/enriched", "text/html" ]</tt>
280
+ # * <tt>mime_version: "1.0"</tt>
281
+ # * <tt>charset: "UTF-8"</tt>
282
+ # * <tt>content_type: "text/plain"</tt>
283
+ # * <tt>parts_order: [ "text/plain", "text/enriched", "text/html" ]</tt>
244
284
  #
245
285
  # <tt>parts_order</tt> and <tt>charset</tt> are not actually valid <tt>Mail::Message</tt> header fields,
246
286
  # but Action Mailer translates them appropriately and sets the correct values.
@@ -248,38 +288,107 @@ module ActionMailer #:nodoc:
248
288
  # As you can pass in any header, you need to either quote the header as a string, or pass it in as
249
289
  # an underscored symbol, so the following will work:
250
290
  #
251
- # class Notifier < ActionMailer::Base
291
+ # class NotifierMailer < ApplicationMailer
252
292
  # default 'Content-Transfer-Encoding' => '7bit',
253
- # :content_description => 'This is a description'
293
+ # content_description: 'This is a description'
254
294
  # end
255
295
  #
256
- # Finally, Action Mailer also supports passing <tt>Proc</tt> objects into the default hash, so you
257
- # can define methods that evaluate as the message is being generated:
296
+ # Finally, Action Mailer also supports passing <tt>Proc</tt> and <tt>Lambda</tt> objects into the default hash,
297
+ # so you can define methods that evaluate as the message is being generated:
258
298
  #
259
- # class Notifier < ActionMailer::Base
260
- # default 'X-Special-Header' => Proc.new { my_method }
299
+ # class NotifierMailer < ApplicationMailer
300
+ # default 'X-Special-Header' => Proc.new { my_method }, to: -> { @inviter.email_address }
261
301
  #
262
302
  # private
263
- #
264
303
  # def my_method
265
304
  # 'some complex call'
266
305
  # end
267
306
  # end
268
307
  #
269
- # Note that the proc is evaluated right at the start of the mail message generation, so if you
270
- # set something in the defaults using a proc, and then set the same thing inside of your
271
- # mailer method, it will get over written by the mailer method.
308
+ # Note that the proc/lambda is evaluated right at the start of the mail message generation, so if you
309
+ # set something in the default hash using a proc, and then set the same thing inside of your
310
+ # mailer method, it will get overwritten by the mailer method.
311
+ #
312
+ # It is also possible to set these default options that will be used in all mailers through
313
+ # the <tt>default_options=</tt> configuration in <tt>config/application.rb</tt>:
314
+ #
315
+ # config.action_mailer.default_options = { from: "no-reply@example.org" }
316
+ #
317
+ # = Callbacks
318
+ #
319
+ # You can specify callbacks using <tt>before_action</tt> and <tt>after_action</tt> for configuring your messages.
320
+ # This may be useful, for example, when you want to add default inline attachments for all
321
+ # messages sent out by a certain mailer class:
322
+ #
323
+ # class NotifierMailer < ApplicationMailer
324
+ # before_action :add_inline_attachment!
325
+ #
326
+ # def welcome
327
+ # mail
328
+ # end
329
+ #
330
+ # private
331
+ # def add_inline_attachment!
332
+ # attachments.inline["footer.jpg"] = File.read('/path/to/filename.jpg')
333
+ # end
334
+ # end
335
+ #
336
+ # Callbacks in Action Mailer are implemented using
337
+ # <tt>AbstractController::Callbacks</tt>, so you can define and configure
338
+ # callbacks in the same manner that you would use callbacks in classes that
339
+ # inherit from <tt>ActionController::Base</tt>.
340
+ #
341
+ # Note that unless you have a specific reason to do so, you should prefer
342
+ # using <tt>before_action</tt> rather than <tt>after_action</tt> in your
343
+ # Action Mailer classes so that headers are parsed properly.
344
+ #
345
+ # = Previewing emails
346
+ #
347
+ # You can preview your email templates visually by adding a mailer preview file to the
348
+ # <tt>ActionMailer::Base.preview_path</tt>. Since most emails do something interesting
349
+ # with database data, you'll need to write some scenarios to load messages with fake data:
350
+ #
351
+ # class NotifierMailerPreview < ActionMailer::Preview
352
+ # def welcome
353
+ # NotifierMailer.welcome(User.first)
354
+ # end
355
+ # end
356
+ #
357
+ # Methods must return a <tt>Mail::Message</tt> object which can be generated by calling the mailer
358
+ # method without the additional <tt>deliver_now</tt> / <tt>deliver_later</tt>. The location of the
359
+ # mailer previews directory can be configured using the <tt>preview_path</tt> option which has a default
360
+ # of <tt>test/mailers/previews</tt>:
361
+ #
362
+ # config.action_mailer.preview_path = "#{Rails.root}/lib/mailer_previews"
363
+ #
364
+ # An overview of all previews is accessible at <tt>http://localhost:3000/rails/mailers</tt>
365
+ # on a running development server instance.
366
+ #
367
+ # Previews can also be intercepted in a similar manner as deliveries can be by registering
368
+ # a preview interceptor that has a <tt>previewing_email</tt> method:
369
+ #
370
+ # class CssInlineStyler
371
+ # def self.previewing_email(message)
372
+ # # inline CSS styles
373
+ # end
374
+ # end
375
+ #
376
+ # config.action_mailer.preview_interceptors :css_inline_styler
377
+ #
378
+ # Note that interceptors need to be registered both with <tt>register_interceptor</tt>
379
+ # and <tt>register_preview_interceptor</tt> if they should operate on both sending and
380
+ # previewing emails.
272
381
  #
273
382
  # = Configuration options
274
383
  #
275
384
  # These options are specified on the class level, like
276
385
  # <tt>ActionMailer::Base.raise_delivery_errors = true</tt>
277
386
  #
278
- # * <tt>default</tt> - You can pass this in at a class level as well as within the class itself as
387
+ # * <tt>default_options</tt> - You can pass this in at a class level as well as within the class itself as
279
388
  # per the above section.
280
389
  #
281
390
  # * <tt>logger</tt> - the logger is used for generating information on the mailing run if available.
282
- # Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.
391
+ # Can be set to +nil+ for no logging. Compatible with both Ruby's own +Logger+ and Log4r loggers.
283
392
  #
284
393
  # * <tt>smtp_settings</tt> - Allows detailed configuration for <tt>:smtp</tt> delivery method:
285
394
  # * <tt>:address</tt> - Allows you to use a remote mail server. Just change it from its default
@@ -290,19 +399,20 @@ module ActionMailer #:nodoc:
290
399
  # * <tt>:password</tt> - If your mail server requires authentication, set the password in this setting.
291
400
  # * <tt>:authentication</tt> - If your mail server requires authentication, you need to specify the
292
401
  # authentication type here.
293
- # This is a symbol and one of <tt>:plain</tt> (will send the password in the clear), <tt>:login</tt> (will
294
- # send password Base64 encoded) or <tt>:cram_md5</tt> (combines a Challenge/Response mechanism to exchange
402
+ # This is a symbol and one of <tt>:plain</tt> (will send the password Base64 encoded), <tt>:login</tt> (will
403
+ # send the password Base64 encoded) or <tt>:cram_md5</tt> (combines a Challenge/Response mechanism to exchange
295
404
  # information and a cryptographic Message Digest 5 algorithm to hash important information)
296
- # * <tt>:enable_starttls_auto</tt> - When set to true, detects if STARTTLS is enabled in your SMTP server
297
- # and starts to use it.
405
+ # * <tt>:enable_starttls_auto</tt> - Detects if STARTTLS is enabled in your SMTP server and starts
406
+ # to use it. Defaults to <tt>true</tt>.
298
407
  # * <tt>:openssl_verify_mode</tt> - When using TLS, you can set how OpenSSL checks the certificate. This is
299
408
  # really useful if you need to validate a self-signed and/or a wildcard certificate. You can use the name
300
- # of an OpenSSL verify constant ('none', 'peer', 'client_once','fail_if_no_peer_cert') or directly the
301
- # constant (OpenSSL::SSL::VERIFY_NONE, OpenSSL::SSL::VERIFY_PEER,...).
409
+ # of an OpenSSL verify constant (<tt>'none'</tt> or <tt>'peer'</tt>) or directly the constant
410
+ # (<tt>OpenSSL::SSL::VERIFY_NONE</tt> or <tt>OpenSSL::SSL::VERIFY_PEER</tt>).
411
+ # * <tt>:ssl/:tls</tt> Enables the SMTP connection to use SMTP/TLS (SMTPS: SMTP over direct TLS connection)
302
412
  #
303
413
  # * <tt>sendmail_settings</tt> - Allows you to override options for the <tt>:sendmail</tt> delivery method.
304
414
  # * <tt>:location</tt> - The location of the sendmail executable. Defaults to <tt>/usr/sbin/sendmail</tt>.
305
- # * <tt>:arguments</tt> - The command line arguments. Defaults to <tt>-i -t</tt> with <tt>-f sender@address</tt>
415
+ # * <tt>:arguments</tt> - The command line arguments. Defaults to <tt>-i</tt> with <tt>-f sender@address</tt>
306
416
  # added automatically before the message is sent.
307
417
  #
308
418
  # * <tt>file_settings</tt> - Allows you to override options for the <tt>:file</tt> delivery method.
@@ -313,39 +423,46 @@ module ActionMailer #:nodoc:
313
423
  #
314
424
  # * <tt>delivery_method</tt> - Defines a delivery method. Possible values are <tt>:smtp</tt> (default),
315
425
  # <tt>:sendmail</tt>, <tt>:test</tt>, and <tt>:file</tt>. Or you may provide a custom delivery method
316
- # object eg. MyOwnDeliveryMethodClass.new. See the Mail gem documentation on the interface you need to
426
+ # object e.g. +MyOwnDeliveryMethodClass+. See the Mail gem documentation on the interface you need to
317
427
  # implement for a custom delivery agent.
318
428
  #
319
429
  # * <tt>perform_deliveries</tt> - Determines whether emails are actually sent from Action Mailer when you
320
- # call <tt>.deliver</tt> on an mail message or on an Action Mailer method. This is on by default but can
430
+ # call <tt>.deliver</tt> on an email message or on an Action Mailer method. This is on by default but can
321
431
  # be turned off to aid in functional testing.
322
432
  #
323
433
  # * <tt>deliveries</tt> - Keeps an array of all the emails sent out through the Action Mailer with
324
434
  # <tt>delivery_method :test</tt>. Most useful for unit and functional testing.
325
435
  #
436
+ # * <tt>deliver_later_queue_name</tt> - The name of the queue used with <tt>deliver_later</tt>. Defaults to +mailers+.
326
437
  class Base < AbstractController::Base
327
438
  include DeliveryMethods
439
+ include Rescuable
440
+ include Parameterized
441
+ include Previews
442
+
328
443
  abstract!
329
444
 
330
- include AbstractController::Logger
331
445
  include AbstractController::Rendering
332
- include AbstractController::Layouts
446
+
447
+ include AbstractController::Logger
333
448
  include AbstractController::Helpers
334
449
  include AbstractController::Translation
335
450
  include AbstractController::AssetPaths
451
+ include AbstractController::Callbacks
452
+ include AbstractController::Caching
336
453
 
337
- self.protected_instance_variables = %w(@_action_has_layout)
454
+ include ActionView::Layouts
338
455
 
339
- helper ActionMailer::MailHelper
456
+ PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + [:@_action_has_layout]
340
457
 
341
- private_class_method :new #:nodoc:
458
+ helper ActionMailer::MailHelper
342
459
 
343
- class_attribute :default_params
344
- self.default_params = {
345
- :mime_version => "1.0",
346
- :charset => "UTF-8",
347
- :content_type => "text/plain",
348
- :parts_order => [ "text/plain", "text/enriched", "text/html" ]
460
+ class_attribute :delivery_job, default: ::ActionMailer::DeliveryJob
461
+ class_attribute :default_params, default: {
462
+ mime_version: "1.0",
463
+ charset: "UTF-8",
464
+ content_type: "text/plain",
465
+ parts_order: [ "text/plain", "text/enriched", "text/html" ]
349
466
  }.freeze
350
467
 
351
468
  class << self
@@ -354,140 +471,210 @@ module ActionMailer #:nodoc:
354
471
  observers.flatten.compact.each { |observer| register_observer(observer) }
355
472
  end
356
473
 
474
+ # Unregister one or more previously registered Observers.
475
+ def unregister_observers(*observers)
476
+ observers.flatten.compact.each { |observer| unregister_observer(observer) }
477
+ end
478
+
357
479
  # Register one or more Interceptors which will be called before mail is sent.
358
480
  def register_interceptors(*interceptors)
359
481
  interceptors.flatten.compact.each { |interceptor| register_interceptor(interceptor) }
360
482
  end
361
483
 
484
+ # Unregister one or more previously registered Interceptors.
485
+ def unregister_interceptors(*interceptors)
486
+ interceptors.flatten.compact.each { |interceptor| unregister_interceptor(interceptor) }
487
+ end
488
+
362
489
  # Register an Observer which will be notified when mail is delivered.
363
- # Either a class or a string can be passed in as the Observer. If a string is passed in
364
- # it will be <tt>constantize</tt>d.
490
+ # Either a class, string or symbol can be passed in as the Observer.
491
+ # If a string or symbol is passed in it will be camelized and constantized.
365
492
  def register_observer(observer)
366
- delivery_observer = (observer.is_a?(String) ? observer.constantize : observer)
367
- Mail.register_observer(delivery_observer)
493
+ Mail.register_observer(observer_class_for(observer))
494
+ end
495
+
496
+ # Unregister a previously registered Observer.
497
+ # Either a class, string or symbol can be passed in as the Observer.
498
+ # If a string or symbol is passed in it will be camelized and constantized.
499
+ def unregister_observer(observer)
500
+ Mail.unregister_observer(observer_class_for(observer))
368
501
  end
369
502
 
370
503
  # Register an Interceptor which will be called before mail is sent.
371
- # Either a class or a string can be passed in as the Interceptor. If a string is passed in
372
- # it will be <tt>constantize</tt>d.
504
+ # Either a class, string or symbol can be passed in as the Interceptor.
505
+ # If a string or symbol is passed in it will be camelized and constantized.
373
506
  def register_interceptor(interceptor)
374
- delivery_interceptor = (interceptor.is_a?(String) ? interceptor.constantize : interceptor)
375
- Mail.register_interceptor(delivery_interceptor)
507
+ Mail.register_interceptor(observer_class_for(interceptor))
508
+ end
509
+
510
+ # Unregister a previously registered Interceptor.
511
+ # Either a class, string or symbol can be passed in as the Interceptor.
512
+ # If a string or symbol is passed in it will be camelized and constantized.
513
+ def unregister_interceptor(interceptor)
514
+ Mail.unregister_interceptor(observer_class_for(interceptor))
515
+ end
516
+
517
+ def observer_class_for(value) # :nodoc:
518
+ case value
519
+ when String, Symbol
520
+ value.to_s.camelize.constantize
521
+ else
522
+ value
523
+ end
376
524
  end
525
+ private :observer_class_for
377
526
 
527
+ # Returns the name of the current mailer. This method is also being used as a path for a view lookup.
528
+ # If this is an anonymous mailer, this method will return +anonymous+ instead.
378
529
  def mailer_name
379
530
  @mailer_name ||= anonymous? ? "anonymous" : name.underscore
380
531
  end
532
+ # Allows to set the name of current mailer.
381
533
  attr_writer :mailer_name
382
534
  alias :controller_path :mailer_name
383
535
 
536
+ # Sets the defaults through app configuration:
537
+ #
538
+ # config.action_mailer.default(from: "no-reply@example.org")
539
+ #
540
+ # Aliased by ::default_options=
384
541
  def default(value = nil)
385
542
  self.default_params = default_params.merge(value).freeze if value
386
543
  default_params
387
544
  end
388
-
389
- # Receives a raw email, parses it into an email object, decodes it,
390
- # instantiates a new mailer, and passes the email object to the mailer
391
- # object's +receive+ method. If you want your mailer to be able to
392
- # process incoming messages, you'll need to implement a +receive+
393
- # method that accepts the raw email string as a parameter:
545
+ # Allows to set defaults through app configuration:
394
546
  #
395
- # class MyMailer < ActionMailer::Base
396
- # def receive(mail)
397
- # ...
398
- # end
399
- # end
400
- def receive(raw_mail)
401
- ActiveSupport::Notifications.instrument("receive.action_mailer") do |payload|
402
- mail = Mail.new(raw_mail)
403
- set_payload_for_mail(payload, mail)
404
- new.receive(mail)
405
- end
406
- end
547
+ # config.action_mailer.default_options = { from: "no-reply@example.org" }
548
+ alias :default_options= :default
407
549
 
408
- # Wraps an email delivery inside of Active Support Notifications instrumentation. This
409
- # method is actually called by the <tt>Mail::Message</tt> object itself through a callback
410
- # when you call <tt>:deliver</tt> on the Mail::Message, calling +deliver_mail+ directly
411
- # and passing a Mail::Message will do nothing except tell the logger you sent the email.
550
+ # Wraps an email delivery inside of <tt>ActiveSupport::Notifications</tt> instrumentation.
551
+ #
552
+ # This method is actually called by the <tt>Mail::Message</tt> object itself
553
+ # through a callback when you call <tt>:deliver</tt> on the <tt>Mail::Message</tt>,
554
+ # calling +deliver_mail+ directly and passing a <tt>Mail::Message</tt> will do
555
+ # nothing except tell the logger you sent the email.
412
556
  def deliver_mail(mail) #:nodoc:
413
557
  ActiveSupport::Notifications.instrument("deliver.action_mailer") do |payload|
414
- self.set_payload_for_mail(payload, mail)
558
+ set_payload_for_mail(payload, mail)
415
559
  yield # Let Mail do the delivery actions
416
560
  end
417
561
  end
418
562
 
419
- def respond_to?(method, include_private = false) #:nodoc:
420
- super || action_methods.include?(method.to_s)
563
+ # Returns an email in the format "Name <email@example.com>".
564
+ def email_address_with_name(address, name)
565
+ Mail::Address.new.tap do |builder|
566
+ builder.address = address
567
+ builder.display_name = name
568
+ end.to_s
421
569
  end
422
570
 
423
- protected
571
+ private
572
+ def set_payload_for_mail(payload, mail)
573
+ payload[:mail] = mail.encoded
574
+ payload[:mailer] = name
575
+ payload[:message_id] = mail.message_id
576
+ payload[:subject] = mail.subject
577
+ payload[:to] = mail.to
578
+ payload[:from] = mail.from
579
+ payload[:bcc] = mail.bcc if mail.bcc.present?
580
+ payload[:cc] = mail.cc if mail.cc.present?
581
+ payload[:date] = mail.date
582
+ payload[:perform_deliveries] = mail.perform_deliveries
583
+ end
424
584
 
425
- def set_payload_for_mail(payload, mail) #:nodoc:
426
- payload[:mailer] = name
427
- payload[:message_id] = mail.message_id
428
- payload[:subject] = mail.subject
429
- payload[:to] = mail.to
430
- payload[:from] = mail.from
431
- payload[:bcc] = mail.bcc if mail.bcc.present?
432
- payload[:cc] = mail.cc if mail.cc.present?
433
- payload[:date] = mail.date
434
- payload[:mail] = mail.encoded
585
+ def method_missing(method_name, *args)
586
+ if action_methods.include?(method_name.to_s)
587
+ MessageDelivery.new(self, method_name, *args)
588
+ else
589
+ super
590
+ end
435
591
  end
592
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
436
593
 
437
- def method_missing(method, *args) #:nodoc:
438
- return super unless respond_to?(method)
439
- new(method, *args).message
594
+ def respond_to_missing?(method, include_all = false)
595
+ action_methods.include?(method.to_s) || super
440
596
  end
441
597
  end
442
598
 
443
599
  attr_internal :message
444
600
 
445
- # Instantiate a new mailer object. If +method_name+ is not +nil+, the mailer
446
- # will be initialized according to the named method. If not, the mailer will
447
- # remain uninitialized (useful when you only need to invoke the "receive"
448
- # method, for instance).
449
- def initialize(method_name=nil, *args)
601
+ def initialize
450
602
  super()
603
+ @_mail_was_called = false
451
604
  @_message = Mail.new
452
- process(method_name, *args) if method_name
453
605
  end
454
606
 
455
- def process(*args) #:nodoc:
456
- lookup_context.skip_default_locale!
607
+ def process(method_name, *args) #:nodoc:
608
+ payload = {
609
+ mailer: self.class.name,
610
+ action: method_name,
611
+ args: args
612
+ }
457
613
 
458
- generated_mail = super
459
- unless generated_mail
460
- @_message = NullMail.new
614
+ ActiveSupport::Notifications.instrument("process.action_mailer", payload) do
615
+ super
616
+ @_message = NullMail.new unless @_mail_was_called
461
617
  end
462
618
  end
463
619
 
464
620
  class NullMail #:nodoc:
465
- def body; '' end
621
+ def body; "" end
622
+ def header; {} end
623
+
624
+ def respond_to?(string, include_all = false)
625
+ true
626
+ end
466
627
 
467
628
  def method_missing(*args)
468
629
  nil
469
630
  end
470
631
  end
471
632
 
633
+ # Returns the name of the mailer object.
472
634
  def mailer_name
473
635
  self.class.mailer_name
474
636
  end
475
637
 
476
- # Allows you to pass random and unusual headers to the new +Mail::Message+ object
477
- # which will add them to itself.
638
+ # Returns an email in the format "Name <email@example.com>".
639
+ def email_address_with_name(address, name)
640
+ self.class.email_address_with_name(address, name)
641
+ end
642
+
643
+ # Allows you to pass random and unusual headers to the new <tt>Mail::Message</tt>
644
+ # object which will add them to itself.
478
645
  #
479
646
  # headers['X-Special-Domain-Specific-Header'] = "SecretValue"
480
647
  #
481
- # You can also pass a hash into headers of header field names and values, which
482
- # will then be set on the Mail::Message object:
648
+ # You can also pass a hash into headers of header field names and values,
649
+ # which will then be set on the <tt>Mail::Message</tt> object:
483
650
  #
484
651
  # headers 'X-Special-Domain-Specific-Header' => "SecretValue",
485
652
  # 'In-Reply-To' => incoming.message_id
486
653
  #
487
- # The resulting Mail::Message will have the following in it's header:
654
+ # The resulting <tt>Mail::Message</tt> will have the following in its header:
488
655
  #
489
656
  # X-Special-Domain-Specific-Header: SecretValue
490
- def headers(args=nil)
657
+ #
658
+ # Note about replacing already defined headers:
659
+ #
660
+ # * +subject+
661
+ # * +sender+
662
+ # * +from+
663
+ # * +to+
664
+ # * +cc+
665
+ # * +bcc+
666
+ # * +reply-to+
667
+ # * +orig-date+
668
+ # * +message-id+
669
+ # * +references+
670
+ #
671
+ # Fields can only appear once in email headers while other fields such as
672
+ # <tt>X-Anything</tt> can appear multiple times.
673
+ #
674
+ # If you want to replace any header which already exists, first set it to
675
+ # +nil+ in order to reset the value otherwise another field will be added
676
+ # for the same header.
677
+ def headers(args = nil)
491
678
  if args
492
679
  @_message.headers(args)
493
680
  else
@@ -499,23 +686,23 @@ module ActionMailer #:nodoc:
499
686
  #
500
687
  # mail.attachments['filename.jpg'] = File.read('/path/to/filename.jpg')
501
688
  #
502
- # If you do this, then Mail will take the file name and work out the mime type
503
- # set the Content-Type, Content-Disposition, Content-Transfer-Encoding and
504
- # base64 encode the contents of the attachment all for you.
689
+ # If you do this, then Mail will take the file name and work out the mime type.
690
+ # It will also set the Content-Type, Content-Disposition, Content-Transfer-Encoding
691
+ # and encode the contents of the attachment in Base64.
505
692
  #
506
693
  # You can also specify overrides if you want by passing a hash instead of a string:
507
694
  #
508
- # mail.attachments['filename.jpg'] = {:mime_type => 'application/x-gzip',
509
- # :content => File.read('/path/to/filename.jpg')}
695
+ # mail.attachments['filename.jpg'] = {mime_type: 'application/gzip',
696
+ # content: File.read('/path/to/filename.jpg')}
510
697
  #
511
- # If you want to use a different encoding than Base64, you can pass an encoding in,
512
- # but then it is up to you to pass in the content pre-encoded, and don't expect
513
- # Mail to know how to decode this data:
698
+ # If you want to use encoding other than Base64 then you will need to pass encoding
699
+ # type along with the pre-encoded content as Mail doesn't know how to decode the
700
+ # data:
514
701
  #
515
702
  # file_content = SpecialEncode(File.read('/path/to/filename.jpg'))
516
- # mail.attachments['filename.jpg'] = {:mime_type => 'application/x-gzip',
517
- # :encoding => 'SpecialEncoding',
518
- # :content => file_content }
703
+ # mail.attachments['filename.jpg'] = {mime_type: 'application/gzip',
704
+ # encoding: 'SpecialEncoding',
705
+ # content: file_content }
519
706
  #
520
707
  # You can also search for specific attachments:
521
708
  #
@@ -526,224 +713,319 @@ module ActionMailer #:nodoc:
526
713
  # mail.attachments[0] # => Mail::Part (first attachment)
527
714
  #
528
715
  def attachments
529
- @_message.attachments
716
+ if @_mail_was_called
717
+ LateAttachmentsProxy.new(@_message.attachments)
718
+ else
719
+ @_message.attachments
720
+ end
721
+ end
722
+
723
+ class LateAttachmentsProxy < SimpleDelegator
724
+ def inline; self end
725
+ def []=(_name, _content); _raise_error end
726
+
727
+ private
728
+ def _raise_error
729
+ raise RuntimeError, "Can't add attachments after `mail` was called.\n" \
730
+ "Make sure to use `attachments[]=` before calling `mail`."
731
+ end
530
732
  end
531
733
 
532
734
  # The main method that creates the message and renders the email templates. There are
533
735
  # two ways to call this method, with a block, or without a block.
534
736
  #
535
- # Both methods accept a headers hash. This hash allows you to specify the most used headers
536
- # in an email message, these are:
737
+ # It accepts a headers hash. This hash allows you to specify
738
+ # the most used headers in an email message, these are:
537
739
  #
538
- # * <tt>:subject</tt> - The subject of the message, if this is omitted, Action Mailer will
539
- # ask the Rails I18n class for a translated <tt>:subject</tt> in the scope of
740
+ # * +:subject+ - The subject of the message, if this is omitted, Action Mailer will
741
+ # ask the Rails I18n class for a translated +:subject+ in the scope of
540
742
  # <tt>[mailer_scope, action_name]</tt> or if this is missing, will translate the
541
- # humanized version of the <tt>action_name</tt>
542
- # * <tt>:to</tt> - Who the message is destined for, can be a string of addresses, or an array
743
+ # humanized version of the +action_name+
744
+ # * +:to+ - Who the message is destined for, can be a string of addresses, or an array
543
745
  # of addresses.
544
- # * <tt>:from</tt> - Who the message is from
545
- # * <tt>:cc</tt> - Who you would like to Carbon-Copy on this email, can be a string of addresses,
746
+ # * +:from+ - Who the message is from
747
+ # * +:cc+ - Who you would like to Carbon-Copy on this email, can be a string of addresses,
546
748
  # or an array of addresses.
547
- # * <tt>:bcc</tt> - Who you would like to Blind-Carbon-Copy on this email, can be a string of
749
+ # * +:bcc+ - Who you would like to Blind-Carbon-Copy on this email, can be a string of
548
750
  # addresses, or an array of addresses.
549
- # * <tt>:reply_to</tt> - Who to set the Reply-To header of the email to.
550
- # * <tt>:date</tt> - The date to say the email was sent on.
751
+ # * +:reply_to+ - Who to set the Reply-To header of the email to.
752
+ # * +:date+ - The date to say the email was sent on.
551
753
  #
552
- # You can set default values for any of the above headers (except :date) by using the <tt>default</tt>
553
- # class method:
754
+ # You can set default values for any of the above headers (except +:date+)
755
+ # by using the ::default class method:
554
756
  #
555
757
  # class Notifier < ActionMailer::Base
556
- # self.default :from => 'no-reply@test.lindsaar.net',
557
- # :bcc => 'email_logger@test.lindsaar.net',
558
- # :reply_to => 'bounces@test.lindsaar.net'
758
+ # default from: 'no-reply@test.lindsaar.net',
759
+ # bcc: 'email_logger@test.lindsaar.net',
760
+ # reply_to: 'bounces@test.lindsaar.net'
559
761
  # end
560
762
  #
561
763
  # If you need other headers not listed above, you can either pass them in
562
764
  # as part of the headers hash or use the <tt>headers['name'] = value</tt>
563
765
  # method.
564
766
  #
565
- # When a <tt>:return_path</tt> is specified as header, that value will be used as the 'envelope from'
566
- # address for the Mail message. Setting this is useful when you want delivery notifications
567
- # sent to a different address than the one in <tt>:from</tt>. Mail will actually use the
568
- # <tt>:return_path</tt> in preference to the <tt>:sender</tt> in preference to the <tt>:from</tt>
569
- # field for the 'envelope from' value.
767
+ # When a +:return_path+ is specified as header, that value will be used as
768
+ # the 'envelope from' address for the Mail message. Setting this is useful
769
+ # when you want delivery notifications sent to a different address than the
770
+ # one in +:from+. Mail will actually use the +:return_path+ in preference
771
+ # to the +:sender+ in preference to the +:from+ field for the 'envelope
772
+ # from' value.
570
773
  #
571
- # If you do not pass a block to the +mail+ method, it will find all templates in the
572
- # view paths using by default the mailer name and the method name that it is being
573
- # called from, it will then create parts for each of these templates intelligently,
574
- # making educated guesses on correct content type and sequence, and return a fully
575
- # prepared Mail::Message ready to call <tt>:deliver</tt> on to send.
774
+ # If you do not pass a block to the +mail+ method, it will find all
775
+ # templates in the view paths using by default the mailer name and the
776
+ # method name that it is being called from, it will then create parts for
777
+ # each of these templates intelligently, making educated guesses on correct
778
+ # content type and sequence, and return a fully prepared <tt>Mail::Message</tt>
779
+ # ready to call <tt>:deliver</tt> on to send.
576
780
  #
577
781
  # For example:
578
782
  #
579
783
  # class Notifier < ActionMailer::Base
580
- # default :from => 'no-reply@test.lindsaar.net',
784
+ # default from: 'no-reply@test.lindsaar.net'
581
785
  #
582
786
  # def welcome
583
- # mail(:to => 'mikel@test.lindsaar.net')
787
+ # mail(to: 'mikel@test.lindsaar.net')
584
788
  # end
585
789
  # end
586
790
  #
587
- # Will look for all templates at "app/views/notifier" with name "welcome". However, those
588
- # can be customized:
791
+ # Will look for all templates at "app/views/notifier" with name "welcome".
792
+ # If no welcome template exists, it will raise an ActionView::MissingTemplate error.
589
793
  #
590
- # mail(:template_path => 'notifications', :template_name => 'another')
794
+ # However, those can be customized:
795
+ #
796
+ # mail(template_path: 'notifications', template_name: 'another')
591
797
  #
592
798
  # And now it will look for all templates at "app/views/notifications" with name "another".
593
799
  #
594
800
  # If you do pass a block, you can render specific templates of your choice:
595
801
  #
596
- # mail(:to => 'mikel@test.lindsaar.net') do |format|
802
+ # mail(to: 'mikel@test.lindsaar.net') do |format|
597
803
  # format.text
598
804
  # format.html
599
805
  # end
600
806
  #
601
- # You can even render text directly without using a template:
807
+ # You can even render plain text directly without using a template:
602
808
  #
603
- # mail(:to => 'mikel@test.lindsaar.net') do |format|
604
- # format.text { render :text => "Hello Mikel!" }
605
- # format.html { render :text => "<h1>Hello Mikel!</h1>" }
809
+ # mail(to: 'mikel@test.lindsaar.net') do |format|
810
+ # format.text { render plain: "Hello Mikel!" }
811
+ # format.html { render html: "<h1>Hello Mikel!</h1>".html_safe }
606
812
  # end
607
813
  #
608
- # Which will render a <tt>multipart/alternative</tt> email with <tt>text/plain</tt> and
609
- # <tt>text/html</tt> parts.
814
+ # Which will render a +multipart/alternative+ email with +text/plain+ and
815
+ # +text/html+ parts.
610
816
  #
611
817
  # The block syntax also allows you to customize the part headers if desired:
612
818
  #
613
- # mail(:to => 'mikel@test.lindsaar.net') do |format|
614
- # format.text(:content_transfer_encoding => "base64")
819
+ # mail(to: 'mikel@test.lindsaar.net') do |format|
820
+ # format.text(content_transfer_encoding: "base64")
615
821
  # format.html
616
822
  # end
617
823
  #
618
- def mail(headers={}, &block)
619
- # Guard flag to prevent both the old and the new API from firing
620
- # Should be removed when old API is removed
621
- @mail_was_called = true
622
- m = @_message
824
+ def mail(headers = {}, &block)
825
+ return message if @_mail_was_called && headers.blank? && !block
623
826
 
624
- # At the beginning, do not consider class default for parts order neither content_type
827
+ # At the beginning, do not consider class default for content_type
625
828
  content_type = headers[:content_type]
626
- parts_order = headers[:parts_order]
627
-
628
- # Call all the procs (if any)
629
- default_values = self.class.default.merge(self.class.default) do |k,v|
630
- v.respond_to?(:call) ? v.bind(self).call : v
631
- end
632
829
 
633
- # Handle defaults
634
- headers = headers.reverse_merge(default_values)
635
- headers[:subject] ||= default_i18n_subject
830
+ headers = apply_defaults(headers)
636
831
 
637
832
  # Apply charset at the beginning so all fields are properly quoted
638
- m.charset = charset = headers[:charset]
833
+ message.charset = charset = headers[:charset]
639
834
 
640
835
  # Set configure delivery behavior
641
- wrap_delivery_behavior!(headers.delete(:delivery_method))
836
+ wrap_delivery_behavior!(headers[:delivery_method], headers[:delivery_method_options])
642
837
 
643
- # Assign all headers except parts_order, content_type and body
644
- assignable = headers.except(:parts_order, :content_type, :body, :template_name, :template_path)
645
- assignable.each { |k, v| m[k] = v }
838
+ assign_headers_to_message(message, headers)
646
839
 
647
840
  # Render the templates and blocks
648
- responses, explicit_order = collect_responses_and_parts_order(headers, &block)
649
- create_parts_from_responses(m, responses)
841
+ responses = collect_responses(headers, &block)
842
+ @_mail_was_called = true
650
843
 
651
- # Setup content type, reapply charset and handle parts order
652
- m.content_type = set_content_type(m, content_type, headers[:content_type])
653
- m.charset = charset
844
+ create_parts_from_responses(message, responses)
845
+ wrap_inline_attachments(message)
654
846
 
655
- if m.multipart?
656
- parts_order ||= explicit_order || headers[:parts_order]
657
- m.body.set_sort_order(parts_order)
658
- m.body.sort_parts!
847
+ # Set up content type, reapply charset and handle parts order
848
+ message.content_type = set_content_type(message, content_type, headers[:content_type])
849
+ message.charset = charset
850
+
851
+ if message.multipart?
852
+ message.body.set_sort_order(headers[:parts_order])
853
+ message.body.sort_parts!
659
854
  end
660
855
 
661
- m
856
+ message
662
857
  end
663
858
 
664
- protected
859
+ private
860
+ # Used by #mail to set the content type of the message.
861
+ #
862
+ # It will use the given +user_content_type+, or multipart if the mail
863
+ # message has any attachments. If the attachments are inline, the content
864
+ # type will be "multipart/related", otherwise "multipart/mixed".
865
+ #
866
+ # If there is no content type passed in via headers, and there are no
867
+ # attachments, or the message is multipart, then the default content type is
868
+ # used.
869
+ def set_content_type(m, user_content_type, class_default) # :doc:
870
+ params = m.content_type_parameters || {}
871
+ case
872
+ when user_content_type.present?
873
+ user_content_type
874
+ when m.has_attachments?
875
+ if m.attachments.all?(&:inline?)
876
+ ["multipart", "related", params]
877
+ else
878
+ ["multipart", "mixed", params]
879
+ end
880
+ when m.multipart?
881
+ ["multipart", "alternative", params]
882
+ else
883
+ m.content_type || class_default
884
+ end
885
+ end
886
+
887
+ # Translates the +subject+ using Rails I18n class under <tt>[mailer_scope, action_name]</tt> scope.
888
+ # If it does not find a translation for the +subject+ under the specified scope it will default to a
889
+ # humanized version of the <tt>action_name</tt>.
890
+ # If the subject has interpolations, you can pass them through the +interpolations+ parameter.
891
+ def default_i18n_subject(interpolations = {}) # :doc:
892
+ mailer_scope = self.class.mailer_name.tr("/", ".")
893
+ I18n.t(:subject, **interpolations.merge(scope: [mailer_scope, action_name], default: action_name.humanize))
894
+ end
895
+
896
+ # Emails do not support relative path links.
897
+ def self.supports_path? # :doc:
898
+ false
899
+ end
900
+
901
+ def apply_defaults(headers)
902
+ default_values = self.class.default.transform_values do |value|
903
+ compute_default(value)
904
+ end
905
+
906
+ headers_with_defaults = headers.reverse_merge(default_values)
907
+ headers_with_defaults[:subject] ||= default_i18n_subject
908
+ headers_with_defaults
909
+ end
910
+
911
+ def compute_default(value)
912
+ return value unless value.is_a?(Proc)
665
913
 
666
- def set_content_type(m, user_content_type, class_default)
667
- params = m.content_type_parameters || {}
668
- case
669
- when user_content_type.present?
670
- user_content_type
671
- when m.has_attachments?
672
- if m.attachments.detect { |a| a.inline? }
673
- ["multipart", "related", params]
914
+ if value.arity == 1
915
+ instance_exec(self, &value)
674
916
  else
675
- ["multipart", "mixed", params]
917
+ instance_exec(&value)
676
918
  end
677
- when m.multipart?
678
- ["multipart", "alternative", params]
679
- else
680
- m.content_type || class_default
681
919
  end
682
- end
683
920
 
684
- # Translates the +subject+ using Rails I18n class under <tt>[:actionmailer, mailer_scope, action_name]</tt> scope.
685
- # If it does not find a translation for the +subject+ under the specified scope it will default to a
686
- # humanized version of the <tt>action_name</tt>.
687
- def default_i18n_subject #:nodoc:
688
- mailer_scope = self.class.mailer_name.gsub('/', '.')
689
- I18n.t(:subject, :scope => [mailer_scope, action_name], :default => action_name.humanize)
690
- end
921
+ def assign_headers_to_message(message, headers)
922
+ assignable = headers.except(:parts_order, :content_type, :body, :template_name,
923
+ :template_path, :delivery_method, :delivery_method_options)
924
+ assignable.each { |k, v| message[k] = v }
925
+ end
691
926
 
692
- def collect_responses_and_parts_order(headers) #:nodoc:
693
- responses, parts_order = [], nil
927
+ def collect_responses(headers, &block)
928
+ if block_given?
929
+ collect_responses_from_block(headers, &block)
930
+ elsif headers[:body]
931
+ collect_responses_from_text(headers)
932
+ else
933
+ collect_responses_from_templates(headers)
934
+ end
935
+ end
694
936
 
695
- if block_given?
696
- collector = ActionMailer::Collector.new(lookup_context) { render(action_name) }
937
+ def collect_responses_from_block(headers)
938
+ templates_name = headers[:template_name] || action_name
939
+ collector = ActionMailer::Collector.new(lookup_context) { render(templates_name) }
697
940
  yield(collector)
698
- parts_order = collector.responses.map { |r| r[:content_type] }
699
- responses = collector.responses
700
- elsif headers[:body]
701
- responses << {
702
- :body => headers.delete(:body),
703
- :content_type => self.class.default[:content_type] || "text/plain"
704
- }
705
- else
706
- templates_path = headers.delete(:template_path) || self.class.mailer_name
707
- templates_name = headers.delete(:template_name) || action_name
941
+ collector.responses
942
+ end
943
+
944
+ def collect_responses_from_text(headers)
945
+ [{
946
+ body: headers.delete(:body),
947
+ content_type: headers[:content_type] || "text/plain"
948
+ }]
949
+ end
708
950
 
709
- each_template(templates_path, templates_name) do |template|
710
- self.formats = template.formats
951
+ def collect_responses_from_templates(headers)
952
+ templates_path = headers[:template_path] || self.class.mailer_name
953
+ templates_name = headers[:template_name] || action_name
711
954
 
712
- responses << {
713
- :body => render(:template => template),
714
- :content_type => template.mime_type.to_s
955
+ each_template(Array(templates_path), templates_name).map do |template|
956
+ format = template.format || self.formats.first
957
+ {
958
+ body: render(template: template, formats: [format]),
959
+ content_type: Mime[format].to_s
715
960
  }
716
961
  end
717
962
  end
718
963
 
719
- [responses, parts_order]
720
- end
964
+ def each_template(paths, name, &block)
965
+ templates = lookup_context.find_all(name, paths)
966
+ if templates.empty?
967
+ raise ActionView::MissingTemplate.new(paths, name, paths, false, "mailer")
968
+ else
969
+ templates.uniq(&:format).each(&block)
970
+ end
971
+ end
721
972
 
722
- def each_template(paths, name, &block) #:nodoc:
723
- templates = lookup_context.find_all(name, Array.wrap(paths))
724
- templates.uniq_by { |t| t.formats }.each(&block)
725
- end
973
+ def wrap_inline_attachments(message)
974
+ # If we have both types of attachment, wrap all the inline attachments
975
+ # in multipart/related, but not the actual attachments
976
+ if message.attachments.detect(&:inline?) && message.attachments.detect { |a| !a.inline? }
977
+ related = Mail::Part.new
978
+ related.content_type = "multipart/related"
979
+ mixed = [ related ]
726
980
 
727
- def create_parts_from_responses(m, responses) #:nodoc:
728
- if responses.size == 1 && !m.has_attachments?
729
- responses[0].each { |k,v| m[k] = v }
730
- elsif responses.size > 1 && m.has_attachments?
731
- container = Mail::Part.new
732
- container.content_type = "multipart/alternative"
733
- responses.each { |r| insert_part(container, r, m.charset) }
734
- m.add_part(container)
735
- else
736
- responses.each { |r| insert_part(m, r, m.charset) }
981
+ message.parts.each do |p|
982
+ if p.attachment? && !p.inline?
983
+ mixed << p
984
+ else
985
+ related.add_part(p)
986
+ end
987
+ end
988
+
989
+ message.parts.clear
990
+ mixed.each { |c| message.add_part(c) }
991
+ end
737
992
  end
738
- end
739
993
 
740
- def insert_part(container, response, charset) #:nodoc:
741
- response[:charset] ||= charset
742
- part = Mail::Part.new(response)
743
- container.add_part(part)
744
- end
994
+ def create_parts_from_responses(m, responses)
995
+ if responses.size == 1 && !m.has_attachments?
996
+ responses[0].each { |k, v| m[k] = v }
997
+ elsif responses.size > 1 && m.has_attachments?
998
+ container = Mail::Part.new
999
+ container.content_type = "multipart/alternative"
1000
+ responses.each { |r| insert_part(container, r, m.charset) }
1001
+ m.add_part(container)
1002
+ else
1003
+ responses.each { |r| insert_part(m, r, m.charset) }
1004
+ end
1005
+ end
1006
+
1007
+ def insert_part(container, response, charset)
1008
+ response[:charset] ||= charset
1009
+ part = Mail::Part.new(response)
1010
+ container.add_part(part)
1011
+ end
745
1012
 
746
- ActiveSupport.run_load_hooks(:action_mailer, self)
1013
+ # This and #instrument_name is for caching instrument
1014
+ def instrument_payload(key)
1015
+ {
1016
+ mailer: mailer_name,
1017
+ key: key
1018
+ }
1019
+ end
1020
+
1021
+ def instrument_name
1022
+ "action_mailer"
1023
+ end
1024
+
1025
+ def _protected_ivars
1026
+ PROTECTED_IVARS
1027
+ end
1028
+
1029
+ ActiveSupport.run_load_hooks(:action_mailer, self)
747
1030
  end
748
1031
  end
749
-