/
base.rb
135 lines (120 loc) · 4.95 KB
/
base.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# frozen_string_literal: true
require "active_support/rescuable"
require "action_mailbox/callbacks"
require "action_mailbox/routing"
module ActionMailbox
# = Action Mailbox \Base
#
# 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.
# routing /^replies@/i => :replies
#
# # Any of the recipients of the mail (whether to, cc, bcc) needs to be an exact match for the string.
# routing "help@example.com" => :help
#
# # Any callable (proc, lambda, etc) object is passed the inbound_email record and is a match if true.
# routing ->(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.
# routing CustomAddress.new => :custom
#
# # Any inbound_email that has not been already matched will be sent to the BackstopMailbox.
# routing :all => :backstop
# end
#
# Application mailboxes need to override 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 to ensure that 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
# ActiveSupport::Rescuable approach:
#
# class ForwardsMailbox < ApplicationMailbox
# rescue_from(ApplicationSpecificVerificationError) { bounced! }
# end
class 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 # :nodoc:
ActiveSupport::Notifications.instrument "process.action_mailbox", instrumentation_payload do
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
end
def process
# Override in subclasses
end
def finished_processing? # :nodoc:
inbound_email.delivered? || inbound_email.bounced?
end
# Enqueues the given +message+ for delivery and changes the inbound email's status to +:bounced+.
def bounce_with(message)
inbound_email.bounced!
message.deliver_later
end
# Immediately sends the given +message+ and changes the inbound email's status to +:bounced+.
def bounce_now_with(message)
inbound_email.bounced!
message.deliver_now
end
private
def instrumentation_payload
{
mailbox: self,
inbound_email: inbound_email.instrumentation_payload
}
end
def track_status_of_inbound_email
inbound_email.processing!
yield
inbound_email.delivered! unless inbound_email.bounced?
rescue
inbound_email.failed!
raise
end
end
end
ActiveSupport.run_load_hooks :action_mailbox, ActionMailbox::Base