omg-actionmailer 8.0.0.alpha2

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