actionmailbox 6.0.2

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.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +46 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.md +13 -0
  5. data/app/controllers/action_mailbox/base_controller.rb +34 -0
  6. data/app/controllers/action_mailbox/ingresses/mailgun/inbound_emails_controller.rb +103 -0
  7. data/app/controllers/action_mailbox/ingresses/mandrill/inbound_emails_controller.rb +82 -0
  8. data/app/controllers/action_mailbox/ingresses/postmark/inbound_emails_controller.rb +62 -0
  9. data/app/controllers/action_mailbox/ingresses/relay/inbound_emails_controller.rb +65 -0
  10. data/app/controllers/action_mailbox/ingresses/sendgrid/inbound_emails_controller.rb +54 -0
  11. data/app/controllers/rails/conductor/action_mailbox/inbound_emails_controller.rb +35 -0
  12. data/app/controllers/rails/conductor/action_mailbox/reroutes_controller.rb +19 -0
  13. data/app/controllers/rails/conductor/base_controller.rb +14 -0
  14. data/app/jobs/action_mailbox/incineration_job.rb +25 -0
  15. data/app/jobs/action_mailbox/routing_job.rb +13 -0
  16. data/app/models/action_mailbox/inbound_email.rb +49 -0
  17. data/app/models/action_mailbox/inbound_email/incineratable.rb +20 -0
  18. data/app/models/action_mailbox/inbound_email/incineratable/incineration.rb +26 -0
  19. data/app/models/action_mailbox/inbound_email/message_id.rb +38 -0
  20. data/app/models/action_mailbox/inbound_email/routable.rb +24 -0
  21. data/app/views/layouts/rails/conductor.html.erb +8 -0
  22. data/app/views/rails/conductor/action_mailbox/inbound_emails/index.html.erb +15 -0
  23. data/app/views/rails/conductor/action_mailbox/inbound_emails/new.html.erb +47 -0
  24. data/app/views/rails/conductor/action_mailbox/inbound_emails/show.html.erb +15 -0
  25. data/config/routes.rb +19 -0
  26. data/db/migrate/20180917164000_create_action_mailbox_tables.rb +13 -0
  27. data/lib/action_mailbox.rb +17 -0
  28. data/lib/action_mailbox/base.rb +118 -0
  29. data/lib/action_mailbox/callbacks.rb +34 -0
  30. data/lib/action_mailbox/engine.rb +33 -0
  31. data/lib/action_mailbox/gem_version.rb +17 -0
  32. data/lib/action_mailbox/mail_ext.rb +6 -0
  33. data/lib/action_mailbox/mail_ext/address_equality.rb +9 -0
  34. data/lib/action_mailbox/mail_ext/address_wrapping.rb +9 -0
  35. data/lib/action_mailbox/mail_ext/addresses.rb +29 -0
  36. data/lib/action_mailbox/mail_ext/from_source.rb +7 -0
  37. data/lib/action_mailbox/mail_ext/recipients.rb +9 -0
  38. data/lib/action_mailbox/relayer.rb +75 -0
  39. data/lib/action_mailbox/router.rb +42 -0
  40. data/lib/action_mailbox/router/route.rb +42 -0
  41. data/lib/action_mailbox/routing.rb +22 -0
  42. data/lib/action_mailbox/test_case.rb +12 -0
  43. data/lib/action_mailbox/test_helper.rb +48 -0
  44. data/lib/action_mailbox/version.rb +10 -0
  45. data/lib/rails/generators/installer.rb +10 -0
  46. data/lib/rails/generators/mailbox/USAGE +12 -0
  47. data/lib/rails/generators/mailbox/mailbox_generator.rb +32 -0
  48. data/lib/rails/generators/mailbox/templates/application_mailbox.rb.tt +3 -0
  49. data/lib/rails/generators/mailbox/templates/mailbox.rb.tt +4 -0
  50. data/lib/rails/generators/test_unit/mailbox_generator.rb +20 -0
  51. data/lib/rails/generators/test_unit/templates/mailbox_test.rb.tt +11 -0
  52. data/lib/tasks/ingress.rake +72 -0
  53. data/lib/tasks/install.rake +20 -0
  54. metadata +186 -0
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ class Conductor::ActionMailbox::InboundEmailsController < Rails::Conductor::BaseController
5
+ def index
6
+ @inbound_emails = ActionMailbox::InboundEmail.order(created_at: :desc)
7
+ end
8
+
9
+ def new
10
+ end
11
+
12
+ def show
13
+ @inbound_email = ActionMailbox::InboundEmail.find(params[:id])
14
+ end
15
+
16
+ def create
17
+ inbound_email = create_inbound_email(new_mail)
18
+ redirect_to main_app.rails_conductor_inbound_email_url(inbound_email)
19
+ end
20
+
21
+ private
22
+ def new_mail
23
+ Mail.new(params.require(:mail).permit(:from, :to, :cc, :bcc, :in_reply_to, :subject, :body).to_h).tap do |mail|
24
+ mail[:bcc]&.include_in_headers = true
25
+ params[:mail][:attachments].to_a.each do |attachment|
26
+ mail.add_file(filename: attachment.original_filename, content: attachment.read)
27
+ end
28
+ end
29
+ end
30
+
31
+ def create_inbound_email(mail)
32
+ ActionMailbox::InboundEmail.create_and_extract_message_id!(mail.to_s)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ # Rerouting will run routing and processing on an email that has already been, or attempted to be, processed.
5
+ class Conductor::ActionMailbox::ReroutesController < Rails::Conductor::BaseController
6
+ def create
7
+ inbound_email = ActionMailbox::InboundEmail.find(params[:inbound_email_id])
8
+ reroute inbound_email
9
+
10
+ redirect_to main_app.rails_conductor_inbound_email_url(inbound_email)
11
+ end
12
+
13
+ private
14
+ def reroute(inbound_email)
15
+ inbound_email.pending!
16
+ inbound_email.route_later
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ # TODO: Move this to Rails::Conductor gem
5
+ class Conductor::BaseController < ActionController::Base
6
+ layout "rails/conductor"
7
+ before_action :ensure_development_env
8
+
9
+ private
10
+ def ensure_development_env
11
+ head :forbidden unless Rails.env.development?
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionMailbox
4
+ # You can configure when this +IncinerationJob+ will be run as a time-after-processing using the
5
+ # +config.action_mailbox.incinerate_after+ or +ActionMailbox.incinerate_after+ setting.
6
+ #
7
+ # Since this incineration is set for the future, it'll automatically ignore any <tt>InboundEmail</tt>s
8
+ # that have already been deleted and discard itself if so.
9
+ #
10
+ # You can disable incinerating processed emails by setting +config.action_mailbox.incinerate+ or
11
+ # +ActionMailbox.incinerate+ to +false+.
12
+ class IncinerationJob < ActiveJob::Base
13
+ queue_as { ActionMailbox.queues[:incineration] }
14
+
15
+ discard_on ActiveRecord::RecordNotFound
16
+
17
+ def self.schedule(inbound_email)
18
+ set(wait: ActionMailbox.incinerate_after).perform_later(inbound_email)
19
+ end
20
+
21
+ def perform(inbound_email)
22
+ inbound_email.incinerate
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionMailbox
4
+ # Routing a new InboundEmail is an asynchronous operation, which allows the ingress controllers to quickly
5
+ # accept new incoming emails without being burdened to hang while they're actually being processed.
6
+ class RoutingJob < ActiveJob::Base
7
+ queue_as { ActionMailbox.queues[:routing] }
8
+
9
+ def perform(inbound_email)
10
+ inbound_email.route
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mail"
4
+
5
+ module ActionMailbox
6
+ # The +InboundEmail+ is an Active Record that keeps a reference to the raw email stored in Active Storage
7
+ # and tracks the status of processing. By default, incoming emails will go through the following lifecycle:
8
+ #
9
+ # * Pending: Just received by one of the ingress controllers and scheduled for routing.
10
+ # * Processing: During active processing, while a specific mailbox is running its #process method.
11
+ # * Delivered: Successfully processed by the specific mailbox.
12
+ # * Failed: An exception was raised during the specific mailbox's execution of the +#process+ method.
13
+ # * Bounced: Rejected processing by the specific mailbox and bounced to sender.
14
+ #
15
+ # Once the +InboundEmail+ has reached the status of being either +delivered+, +failed+, or +bounced+,
16
+ # it'll count as having been +#processed?+. Once processed, the +InboundEmail+ will be scheduled for
17
+ # automatic incineration at a later point.
18
+ #
19
+ # When working with an +InboundEmail+, you'll usually interact with the parsed version of the source,
20
+ # which is available as a +Mail+ object from +#mail+. But you can also access the raw source directly
21
+ # using the +#source+ method.
22
+ #
23
+ # Examples:
24
+ #
25
+ # inbound_email.mail.from # => 'david@loudthinking.com'
26
+ # inbound_email.source # Returns the full rfc822 source of the email as text
27
+ class InboundEmail < ActiveRecord::Base
28
+ self.table_name = "action_mailbox_inbound_emails"
29
+
30
+ include Incineratable, MessageId, Routable
31
+
32
+ has_one_attached :raw_email
33
+ enum status: %i[ pending processing delivered failed bounced ]
34
+
35
+ def mail
36
+ @mail ||= Mail.from_source(source)
37
+ end
38
+
39
+ def source
40
+ @source ||= raw_email.download
41
+ end
42
+
43
+ def processed?
44
+ delivered? || failed? || bounced?
45
+ end
46
+ end
47
+ end
48
+
49
+ ActiveSupport.run_load_hooks :action_mailbox_inbound_email, ActionMailbox::InboundEmail
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Ensure that the +InboundEmail+ is automatically scheduled for later incineration if the status has been
4
+ # changed to +processed+. The later incineration will be invoked at the time specified by the
5
+ # +ActionMailbox.incinerate_after+ time using the +IncinerationJob+.
6
+ module ActionMailbox::InboundEmail::Incineratable
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ after_update_commit :incinerate_later, if: -> { ActionMailbox.incinerate && status_previously_changed? && processed? }
11
+ end
12
+
13
+ def incinerate_later
14
+ ActionMailbox::IncinerationJob.schedule self
15
+ end
16
+
17
+ def incinerate
18
+ Incineration.new(self).run
19
+ end
20
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionMailbox
4
+ # Command class for carrying out the actual incineration of the +InboundMail+ that's been scheduled
5
+ # for removal. Before the incineration – which really is just a call to +#destroy!+ – is run, we verify
6
+ # that it's both eligible (by virtue of having already been processed) and time to do so (that is,
7
+ # the +InboundEmail+ was processed after the +incinerate_after+ time).
8
+ class InboundEmail::Incineratable::Incineration
9
+ def initialize(inbound_email)
10
+ @inbound_email = inbound_email
11
+ end
12
+
13
+ def run
14
+ @inbound_email.destroy! if due? && processed?
15
+ end
16
+
17
+ private
18
+ def due?
19
+ @inbound_email.updated_at < ActionMailbox.incinerate_after.ago.end_of_day
20
+ end
21
+
22
+ def processed?
23
+ @inbound_email.processed?
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The +Message-ID+ as specified by rfc822 is supposed to be a unique identifier for that individual email.
4
+ # That makes it an ideal tracking token for debugging and forensics, just like +X-Request-Id+ does for
5
+ # web request.
6
+ #
7
+ # If an inbound email does not, against the rfc822 mandate, specify a Message-ID, one will be generated
8
+ # using the approach from <tt>Mail::MessageIdField</tt>.
9
+ module ActionMailbox::InboundEmail::MessageId
10
+ extend ActiveSupport::Concern
11
+
12
+ class_methods do
13
+ # Create a new +InboundEmail+ from the raw +source+ of the email, which be uploaded as a Active Storage
14
+ # attachment called +raw_email+. Before the upload, extract the Message-ID from the +source+ and set
15
+ # it as an attribute on the new +InboundEmail+.
16
+ def create_and_extract_message_id!(source, **options)
17
+ message_checksum = Digest::SHA1.hexdigest(source)
18
+ message_id = extract_message_id(source) || generate_missing_message_id(message_checksum)
19
+
20
+ create! options.merge(message_id: message_id, message_checksum: message_checksum) do |inbound_email|
21
+ inbound_email.raw_email.attach io: StringIO.new(source), filename: "message.eml", content_type: "message/rfc822"
22
+ end
23
+ rescue ActiveRecord::RecordNotUnique
24
+ nil
25
+ end
26
+
27
+ private
28
+ def extract_message_id(source)
29
+ Mail.from_source(source).message_id rescue nil
30
+ end
31
+
32
+ def generate_missing_message_id(message_checksum)
33
+ Mail::MessageIdField.new("<#{message_checksum}@#{::Socket.gethostname}.mail>").message_id.tap do |message_id|
34
+ logger.warn "Message-ID couldn't be parsed or is missing. Generated a new Message-ID: #{message_id}"
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A newly received +InboundEmail+ will not be routed synchronously as part of ingress controller's receival.
4
+ # Instead, the routing will be done asynchronously, using a +RoutingJob+, to ensure maximum parallel capacity.
5
+ #
6
+ # By default, all newly created +InboundEmail+ records that have the status of +pending+, which is the default,
7
+ # will be scheduled for automatic, deferred routing.
8
+ module ActionMailbox::InboundEmail::Routable
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ after_create_commit :route_later, if: :pending?
13
+ end
14
+
15
+ # Enqueue a +RoutingJob+ for this +InboundEmail+.
16
+ def route_later
17
+ ActionMailbox::RoutingJob.perform_later self
18
+ end
19
+
20
+ # Route this +InboundEmail+ using the routing rules declared on the +ApplicationMailbox+.
21
+ def route
22
+ ApplicationMailbox.route self
23
+ end
24
+ end
@@ -0,0 +1,8 @@
1
+ <html>
2
+ <head>
3
+ <title>Rails Conductor: <%= yield :title %></title>
4
+ </head>
5
+ <body>
6
+ <%= yield %>
7
+ </body>
8
+ </html>
@@ -0,0 +1,15 @@
1
+ <% provide :title, "Deliver new inbound email" %>
2
+
3
+ <h1>All inbound emails</h1>
4
+
5
+ <table>
6
+ <tr><th>Message ID</th><th>Status</th></tr>
7
+ <% @inbound_emails.each do |inbound_email| %>
8
+ <tr>
9
+ <td><%= link_to inbound_email.message_id, main_app.rails_conductor_inbound_email_path(inbound_email) %></td>
10
+ <td><%= inbound_email.status %></td>
11
+ </tr>
12
+ <% end %>
13
+ </table>
14
+
15
+ <%= link_to "Deliver new inbound email", main_app.new_rails_conductor_inbound_email_path %>
@@ -0,0 +1,47 @@
1
+ <% provide :title, "Deliver new inbound email" %>
2
+
3
+ <h1>Deliver new inbound email</h1>
4
+
5
+ <%= form_with(url: main_app.rails_conductor_inbound_emails_path, scope: :mail, local: true) do |form| %>
6
+ <div>
7
+ <%= form.label :from, "From" %><br>
8
+ <%= form.text_field :from %>
9
+ </div>
10
+
11
+ <div>
12
+ <%= form.label :to, "To" %><br>
13
+ <%= form.text_field :to %>
14
+ </div>
15
+
16
+ <div>
17
+ <%= form.label :cc, "CC" %><br>
18
+ <%= form.text_field :cc %>
19
+ </div>
20
+
21
+ <div>
22
+ <%= form.label :bcc, "BCC" %><br>
23
+ <%= form.text_field :bcc %>
24
+ </div>
25
+
26
+ <div>
27
+ <%= form.label :in_reply_to, "In-Reply-To" %><br>
28
+ <%= form.text_field :in_reply_to %>
29
+ </div>
30
+
31
+ <div>
32
+ <%= form.label :subject, "Subject" %><br>
33
+ <%= form.text_field :subject %>
34
+ </div>
35
+
36
+ <div>
37
+ <%= form.label :body, "Body" %><br>
38
+ <%= form.text_area :body, size: "40x20" %>
39
+ </div>
40
+
41
+ <div>
42
+ <%= form.label :attachments, "Attachments" %><br>
43
+ <%= form.file_field :attachments, multiple: true %>
44
+ </div>
45
+
46
+ <%= form.submit "Deliver inbound email" %>
47
+ <% end %>
@@ -0,0 +1,15 @@
1
+ <% provide :title, @inbound_email.message_id %>
2
+
3
+ <h1><%= @inbound_email.message_id %>: <%= @inbound_email.status %></h1>
4
+
5
+ <ul>
6
+ <li><%= button_to "Route again", main_app.rails_conductor_inbound_email_reroute_path(@inbound_email), method: :post %></li>
7
+ <li>Incinerate</li>
8
+ </ul>
9
+
10
+ <details>
11
+ <summary>Full email source</summary>
12
+ <pre><%= @inbound_email.source %></pre>
13
+ </details>
14
+
15
+ <%= link_to "Back to all inbound emails", main_app.rails_conductor_inbound_emails_path %>
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ Rails.application.routes.draw do
4
+ scope "/rails/action_mailbox", module: "action_mailbox/ingresses" do
5
+ post "/mandrill/inbound_emails" => "mandrill/inbound_emails#create", as: :rails_mandrill_inbound_emails
6
+ post "/postmark/inbound_emails" => "postmark/inbound_emails#create", as: :rails_postmark_inbound_emails
7
+ post "/relay/inbound_emails" => "relay/inbound_emails#create", as: :rails_relay_inbound_emails
8
+ post "/sendgrid/inbound_emails" => "sendgrid/inbound_emails#create", as: :rails_sendgrid_inbound_emails
9
+
10
+ # Mailgun requires that a webhook's URL end in 'mime' for it to receive the raw contents of emails.
11
+ post "/mailgun/inbound_emails/mime" => "mailgun/inbound_emails#create", as: :rails_mailgun_inbound_emails
12
+ end
13
+
14
+ # TODO: Should these be mounted within the engine only?
15
+ scope "rails/conductor/action_mailbox/", module: "rails/conductor/action_mailbox" do
16
+ resources :inbound_emails, as: :rails_conductor_inbound_emails
17
+ post ":inbound_email_id/reroute" => "reroutes#create", as: :rails_conductor_inbound_email_reroute
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ class CreateActionMailboxTables < ActiveRecord::Migration[6.0]
2
+ def change
3
+ create_table :action_mailbox_inbound_emails do |t|
4
+ t.integer :status, default: 0, null: false
5
+ t.string :message_id, null: false
6
+ t.string :message_checksum, null: false
7
+
8
+ t.timestamps
9
+
10
+ t.index [ :message_id, :message_checksum ], name: "index_action_mailbox_inbound_emails_uniqueness", unique: true
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_mailbox/mail_ext"
4
+
5
+ module ActionMailbox
6
+ extend ActiveSupport::Autoload
7
+
8
+ autoload :Base
9
+ autoload :Router
10
+ autoload :TestCase
11
+
12
+ mattr_accessor :ingress
13
+ mattr_accessor :logger
14
+ mattr_accessor :incinerate, default: true
15
+ mattr_accessor :incinerate_after, default: 30.days
16
+ mattr_accessor :queues, default: {}
17
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/rescuable"
4
+
5
+ require "action_mailbox/callbacks"
6
+ require "action_mailbox/routing"
7
+
8
+ module ActionMailbox
9
+ # The base class for all application mailboxes. Not intended to be inherited from directly. Inherit from
10
+ # +ApplicationMailbox+ instead, as that's where the app-specific routing is configured. This routing
11
+ # is specified in the following ways:
12
+ #
13
+ # class ApplicationMailbox < ActionMailbox::Base
14
+ # # Any of the recipients of the mail (whether to, cc, bcc) are matched against the regexp.
15
+ # routing /^replies@/i => :replies
16
+ #
17
+ # # Any of the recipients of the mail (whether to, cc, bcc) needs to be an exact match for the string.
18
+ # routing "help@example.com" => :help
19
+ #
20
+ # # Any callable (proc, lambda, etc) object is passed the inbound_email record and is a match if true.
21
+ # routing ->(inbound_email) { inbound_email.mail.to.size > 2 } => :multiple_recipients
22
+ #
23
+ # # Any object responding to #match? is called with the inbound_email record as an argument. Match if true.
24
+ # routing CustomAddress.new => :custom
25
+ #
26
+ # # Any inbound_email that has not been already matched will be sent to the BackstopMailbox.
27
+ # routing :all => :backstop
28
+ # end
29
+ #
30
+ # Application mailboxes need to overwrite the +#process+ method, which is invoked by the framework after
31
+ # callbacks have been run. The callbacks available are: +before_processing+, +after_processing+, and
32
+ # +around_processing+. The primary use case is ensure certain preconditions to processing are fulfilled
33
+ # using +before_processing+ callbacks.
34
+ #
35
+ # If a precondition fails to be met, you can halt the processing using the +#bounced!+ method,
36
+ # which will silently prevent any further processing, but not actually send out any bounce notice. You
37
+ # can also pair this behavior with the invocation of an Action Mailer class responsible for sending out
38
+ # an actual bounce email. This is done using the +#bounce_with+ method, which takes the mail object returned
39
+ # by an Action Mailer method, like so:
40
+ #
41
+ # class ForwardsMailbox < ApplicationMailbox
42
+ # before_processing :ensure_sender_is_a_user
43
+ #
44
+ # private
45
+ # def ensure_sender_is_a_user
46
+ # unless User.exist?(email_address: mail.from)
47
+ # bounce_with UserRequiredMailer.missing(inbound_email)
48
+ # end
49
+ # end
50
+ # end
51
+ #
52
+ # During the processing of the inbound email, the status will be tracked. Before processing begins,
53
+ # the email will normally have the +pending+ status. Once processing begins, just before callbacks
54
+ # and the +#process+ method is called, the status is changed to +processing+. If processing is allowed to
55
+ # complete, the status is changed to +delivered+. If a bounce is triggered, then +bounced+. If an unhandled
56
+ # exception is bubbled up, then +failed+.
57
+ #
58
+ # Exceptions can be handled at the class level using the familiar +Rescuable+ approach:
59
+ #
60
+ # class ForwardsMailbox < ApplicationMailbox
61
+ # rescue_from(ApplicationSpecificVerificationError) { bounced! }
62
+ # end
63
+ class Base
64
+ include ActiveSupport::Rescuable
65
+ include ActionMailbox::Callbacks, ActionMailbox::Routing
66
+
67
+ attr_reader :inbound_email
68
+ delegate :mail, :delivered!, :bounced!, to: :inbound_email
69
+
70
+ delegate :logger, to: ActionMailbox
71
+
72
+ def self.receive(inbound_email)
73
+ new(inbound_email).perform_processing
74
+ end
75
+
76
+ def initialize(inbound_email)
77
+ @inbound_email = inbound_email
78
+ end
79
+
80
+ def perform_processing #:nodoc:
81
+ track_status_of_inbound_email do
82
+ run_callbacks :process do
83
+ process
84
+ end
85
+ end
86
+ rescue => exception
87
+ # TODO: Include a reference to the inbound_email in the exception raised so error handling becomes easier
88
+ rescue_with_handler(exception) || raise
89
+ end
90
+
91
+ def process
92
+ # Overwrite in subclasses
93
+ end
94
+
95
+ def finished_processing? #:nodoc:
96
+ inbound_email.delivered? || inbound_email.bounced?
97
+ end
98
+
99
+
100
+ # Enqueues the given +message+ for delivery and changes the inbound email's status to +:bounced+.
101
+ def bounce_with(message)
102
+ inbound_email.bounced!
103
+ message.deliver_later
104
+ end
105
+
106
+ private
107
+ def track_status_of_inbound_email
108
+ inbound_email.processing!
109
+ yield
110
+ inbound_email.delivered! unless inbound_email.bounced?
111
+ rescue
112
+ inbound_email.failed!
113
+ raise
114
+ end
115
+ end
116
+ end
117
+
118
+ ActiveSupport.run_load_hooks :action_mailbox, ActionMailbox::Base