/actionmailbox/lib/action_mailbox/base.rb

https://github.com/rails/rails · Ruby · 127 lines · 54 code · 15 blank · 58 comment · 2 complexity · 1c6b21768e97cffd4b3a2676b211c45a MD5 · raw file

  1. # frozen_string_literal: true
  2. require "active_support/rescuable"
  3. require "action_mailbox/callbacks"
  4. require "action_mailbox/routing"
  5. module ActionMailbox
  6. # The base class for all application mailboxes. Not intended to be inherited from directly. Inherit from
  7. # +ApplicationMailbox+ instead, as that's where the app-specific routing is configured. This routing
  8. # is specified in the following ways:
  9. #
  10. # class ApplicationMailbox < ActionMailbox::Base
  11. # # Any of the recipients of the mail (whether to, cc, bcc) are matched against the regexp.
  12. # routing /^replies@/i => :replies
  13. #
  14. # # Any of the recipients of the mail (whether to, cc, bcc) needs to be an exact match for the string.
  15. # routing "help@example.com" => :help
  16. #
  17. # # Any callable (proc, lambda, etc) object is passed the inbound_email record and is a match if true.
  18. # routing ->(inbound_email) { inbound_email.mail.to.size > 2 } => :multiple_recipients
  19. #
  20. # # Any object responding to #match? is called with the inbound_email record as an argument. Match if true.
  21. # routing CustomAddress.new => :custom
  22. #
  23. # # Any inbound_email that has not been already matched will be sent to the BackstopMailbox.
  24. # routing :all => :backstop
  25. # end
  26. #
  27. # Application mailboxes need to override the #process method, which is invoked by the framework after
  28. # callbacks have been run. The callbacks available are: +before_processing+, +after_processing+, and
  29. # +around_processing+. The primary use case is ensure certain preconditions to processing are fulfilled
  30. # using +before_processing+ callbacks.
  31. #
  32. # If a precondition fails to be met, you can halt the processing using the +#bounced!+ method,
  33. # which will silently prevent any further processing, but not actually send out any bounce notice. You
  34. # can also pair this behavior with the invocation of an Action Mailer class responsible for sending out
  35. # an actual bounce email. This is done using the #bounce_with method, which takes the mail object returned
  36. # by an Action Mailer method, like so:
  37. #
  38. # class ForwardsMailbox < ApplicationMailbox
  39. # before_processing :ensure_sender_is_a_user
  40. #
  41. # private
  42. # def ensure_sender_is_a_user
  43. # unless User.exist?(email_address: mail.from)
  44. # bounce_with UserRequiredMailer.missing(inbound_email)
  45. # end
  46. # end
  47. # end
  48. #
  49. # During the processing of the inbound email, the status will be tracked. Before processing begins,
  50. # the email will normally have the +pending+ status. Once processing begins, just before callbacks
  51. # and the #process method is called, the status is changed to +processing+. If processing is allowed to
  52. # complete, the status is changed to +delivered+. If a bounce is triggered, then +bounced+. If an unhandled
  53. # exception is bubbled up, then +failed+.
  54. #
  55. # Exceptions can be handled at the class level using the familiar +Rescuable+ approach:
  56. #
  57. # class ForwardsMailbox < ApplicationMailbox
  58. # rescue_from(ApplicationSpecificVerificationError) { bounced! }
  59. # end
  60. class Base
  61. include ActiveSupport::Rescuable
  62. include ActionMailbox::Callbacks, ActionMailbox::Routing
  63. attr_reader :inbound_email
  64. delegate :mail, :delivered!, :bounced!, to: :inbound_email
  65. delegate :logger, to: ActionMailbox
  66. def self.receive(inbound_email)
  67. new(inbound_email).perform_processing
  68. end
  69. def initialize(inbound_email)
  70. @inbound_email = inbound_email
  71. end
  72. def perform_processing # :nodoc:
  73. ActiveSupport::Notifications.instrument "process.action_mailbox", instrumentation_payload do
  74. track_status_of_inbound_email do
  75. run_callbacks :process do
  76. process
  77. end
  78. end
  79. rescue => exception
  80. # TODO: Include a reference to the inbound_email in the exception raised so error handling becomes easier
  81. rescue_with_handler(exception) || raise
  82. end
  83. end
  84. def process
  85. # Override in subclasses
  86. end
  87. def finished_processing? # :nodoc:
  88. inbound_email.delivered? || inbound_email.bounced?
  89. end
  90. # Enqueues the given +message+ for delivery and changes the inbound email's status to +:bounced+.
  91. def bounce_with(message)
  92. inbound_email.bounced!
  93. message.deliver_later
  94. end
  95. private
  96. def instrumentation_payload
  97. {
  98. mailbox: self,
  99. inbound_email: inbound_email.instrumentation_payload
  100. }
  101. end
  102. def track_status_of_inbound_email
  103. inbound_email.processing!
  104. yield
  105. inbound_email.delivered! unless inbound_email.bounced?
  106. rescue
  107. inbound_email.failed!
  108. raise
  109. end
  110. end
  111. end
  112. ActiveSupport.run_load_hooks :action_mailbox, ActionMailbox::Base