aboutsummaryrefslogblamecommitdiffstats
path: root/lib/action_mailbox/base.rb
blob: 0ae83bf691a7fdd075850481e59f765d82ca171a (plain) (tree)
1
2
3
4
5
                                  
 

                                  
 





















































                                                                                                               
                         
                                  
                                                          
 
                            
                                                            
 

                                     

                                         

     



                                  
                        

                                    

               
       
                     
                                                                                                             


                                           
             
                             
     
 




                                                      




                          



                                     
                                                            
          


                           
   
require "active_support/rescuable"

require "action_mailbox/callbacks"
require "action_mailbox/routing"

# The base class for all application mailboxes. Not intended to be inherited from directly. Inherit from
# `ApplicationMailbox` instead, as that's where the app-specific routing is configured. This routing
# is specified in the following ways:
#
#   class ApplicationMailbox < ActionMailbox::Base
#     # Any of the recipients of the mail (whether to, cc, bcc) are matched against the regexp.
#     route /^replies@/i => :replies
#
#     # Any of the recipients of the mail (whether to, cc, bcc) needs to be an exact match for the string.
#     route "help@example.com" => :help
#
#     # Any callable (proc, lambda, etc) object is passed the inbound_email record and is a match if true.
#     route ->(inbound_email) { inbound_email.mail.to.size > 2 } => :multiple_recipients
#
#     # Any object responding to #match? is called with the inbound_email record as an argument. Match if true.
#     route CustomAddress.new => :custom
#
#     # Any inbound_email that has not been already matched will be sent to the BackstopMailbox.
#     route :all => :backstop
#   end
#
# Application mailboxes need to overwrite the `#process` method, which is invoked by the framework after
# callbacks have been run. The callbacks available are: `before_processing`, `after_processing`, and
# `around_processing`. The primary use case is ensure certain preconditions to processing are fulfilled
# using `before_processing` callbacks.
#
# If a precondition fails to be met, you can halt the processing using the `#bounced!` method, 
# which will silently prevent any further processing, but not actually send out any bounce notice. You
# can also pair this behavior with the invocation of an Action Mailer class responsible for sending out
# an actual bounce email. This is done using the `#bounce_with` method, which takes the mail object returned
# by an Action Mailer method, like so:
#
#   class ForwardsMailbox < ApplicationMailbox
#     before_processing :ensure_sender_is_a_user
#
#     private
#       def ensure_sender_is_a_user
#         unless User.exist?(email_address: mail.from)
#           bounce_with UserRequiredMailer.missing(inbound_email)
#         end
#       end
#   end
#
# During the processing of the inbound email, the status will be tracked. Before processing begins,
# the email will normally have the `pending` status. Once processing begins, just before callbacks 
# and the `#process` method is called, the status is changed to `processing`. If processing is allowed to
# complete, the status is changed to `delivered`. If a bounce is triggered, then `bounced`. If an unhandled
# exception is bubbled up, then `failed`.
#
# Exceptions can be handled at the class level using the familiar `Rescuable` approach:
#
#   class ForwardsMailbox < ApplicationMailbox
#    rescue_from(ApplicationSpecificVerificationError) { bounced! }
#   end
class ActionMailbox::Base
  include ActiveSupport::Rescuable
  include ActionMailbox::Callbacks, ActionMailbox::Routing

  attr_reader :inbound_email
  delegate :mail, :delivered!, :bounced!, to: :inbound_email

  delegate :logger, to: ActionMailbox

  def self.receive(inbound_email)
    new(inbound_email).perform_processing
  end

  def initialize(inbound_email)
    @inbound_email = inbound_email
  end

  def perform_processing
    track_status_of_inbound_email do
      run_callbacks :process do
        process
      end
    end
  rescue => exception
    # TODO: Include a reference to the inbound_email in the exception raised so error handling becomes easier
    rescue_with_handler(exception) || raise
  end

  def process
    # Overwrite in subclasses
  end

  def finished_processing?
    inbound_email.delivered? || inbound_email.bounced?
  end


  def bounce_with(message)
    inbound_email.bounced!
    message.deliver_later
  end

  private
    def track_status_of_inbound_email
      inbound_email.processing!
      yield
      inbound_email.delivered! unless inbound_email.bounced?
    rescue
      inbound_email.failed!
      raise
    end
end