aboutsummaryrefslogtreecommitdiffstats
path: root/lib/action_mailbox
diff options
context:
space:
mode:
authorDavid Heinemeier Hansson <david@loudthinking.com>2018-09-28 12:19:43 -0700
committerDavid Heinemeier Hansson <david@loudthinking.com>2018-09-28 12:19:43 -0700
commit8a0a1034955544ee2e4c1f85317c0db84f3aa55b (patch)
tree1e00acdce252b2ce505ff2e8f9f5acd4ba19bbeb /lib/action_mailbox
parent5ad0813322820a6c42d7b3074531ac40108bfb69 (diff)
downloadrails-8a0a1034955544ee2e4c1f85317c0db84f3aa55b.tar.gz
rails-8a0a1034955544ee2e4c1f85317c0db84f3aa55b.tar.bz2
rails-8a0a1034955544ee2e4c1f85317c0db84f3aa55b.zip
ActionMailroom -> ActionMailbox
We didn't end up using the mailroom metaphor directly, so let's stick with a more conventional naming strategy.
Diffstat (limited to 'lib/action_mailbox')
-rw-r--r--lib/action_mailbox/base.rb45
-rw-r--r--lib/action_mailbox/callbacks.rb26
-rw-r--r--lib/action_mailbox/engine.rb14
-rw-r--r--lib/action_mailbox/router.rb36
-rw-r--r--lib/action_mailbox/router/route.rb31
-rw-r--r--lib/action_mailbox/routing.rb17
-rw-r--r--lib/action_mailbox/test_helper.rb22
-rw-r--r--lib/action_mailbox/version.rb3
8 files changed, 194 insertions, 0 deletions
diff --git a/lib/action_mailbox/base.rb b/lib/action_mailbox/base.rb
new file mode 100644
index 0000000000..476c343415
--- /dev/null
+++ b/lib/action_mailbox/base.rb
@@ -0,0 +1,45 @@
+require "active_support/rescuable"
+
+require "action_mailbox/callbacks"
+require "action_mailbox/routing"
+
+class ActionMailbox::Base
+ include ActiveSupport::Rescuable
+ include ActionMailbox::Callbacks, ActionMailbox::Routing
+
+ attr_reader :inbound_email
+ delegate :mail, :bounced!, to: :inbound_email
+
+ def self.receive(inbound_email)
+ new(inbound_email).perform_processing
+ end
+
+ def initialize(inbound_email)
+ @inbound_email = inbound_email
+ end
+
+ def perform_processing
+ run_callbacks :process do
+ track_status_of_inbound_email 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
+
+ private
+ def track_status_of_inbound_email
+ inbound_email.processing!
+ yield
+ inbound_email.delivered! unless inbound_email.bounced?
+ rescue => exception
+ inbound_email.failed!
+ raise
+ end
+end
diff --git a/lib/action_mailbox/callbacks.rb b/lib/action_mailbox/callbacks.rb
new file mode 100644
index 0000000000..33caaafd2a
--- /dev/null
+++ b/lib/action_mailbox/callbacks.rb
@@ -0,0 +1,26 @@
+require "active_support/callbacks"
+
+module ActionMailbox
+ module Callbacks
+ extend ActiveSupport::Concern
+ include ActiveSupport::Callbacks
+
+ included do
+ define_callbacks :process
+ end
+
+ module ClassMethods
+ def before_processing(*methods, &block)
+ set_callback(:process, :before, *methods, &block)
+ end
+
+ def after_processing(*methods, &block)
+ set_callback(:process, :after, *methods, &block)
+ end
+
+ def around_processing(*methods, &block)
+ set_callback(:process, :around, *methods, &block)
+ end
+ end
+ end
+end
diff --git a/lib/action_mailbox/engine.rb b/lib/action_mailbox/engine.rb
new file mode 100644
index 0000000000..92852a0fa3
--- /dev/null
+++ b/lib/action_mailbox/engine.rb
@@ -0,0 +1,14 @@
+require "rails/engine"
+
+module ActionMailbox
+ class Engine < Rails::Engine
+ isolate_namespace ActionMailbox
+ config.eager_load_namespaces << ActionMailbox
+
+ initializer "action_mailbox.config" do
+ config.after_initialize do |app|
+ # Configure
+ end
+ end
+ end
+end
diff --git a/lib/action_mailbox/router.rb b/lib/action_mailbox/router.rb
new file mode 100644
index 0000000000..8ba3ad0bae
--- /dev/null
+++ b/lib/action_mailbox/router.rb
@@ -0,0 +1,36 @@
+class ActionMailbox::Router
+ class RoutingError < StandardError; end
+
+ def initialize
+ @routes = []
+ end
+
+ def add_routes(routes)
+ routes.each do |(address, mailbox_name)|
+ add_route address, to: mailbox_name
+ end
+ end
+
+ def add_route(address, to:)
+ routes.append Route.new(address, to: to)
+ end
+
+ def route(inbound_email)
+ if mailbox = match_to_mailbox(inbound_email)
+ mailbox.receive(inbound_email)
+ else
+ inbound_email.bounced!
+
+ raise RoutingError
+ end
+ end
+
+ private
+ attr_reader :routes
+
+ def match_to_mailbox(inbound_email)
+ routes.detect { |route| route.match?(inbound_email) }.try(:mailbox_class)
+ end
+end
+
+require "action_mailbox/router/route"
diff --git a/lib/action_mailbox/router/route.rb b/lib/action_mailbox/router/route.rb
new file mode 100644
index 0000000000..7be4407339
--- /dev/null
+++ b/lib/action_mailbox/router/route.rb
@@ -0,0 +1,31 @@
+class ActionMailbox::Router::Route
+ class InvalidAddressError < StandardError; end
+
+ attr_reader :address, :mailbox_name
+
+ def initialize(address, to:)
+ @address, @mailbox_name = address, to
+ end
+
+ def match?(inbound_email)
+ case address
+ when String
+ recipients_from(inbound_email.mail).include?(address)
+ when Regexp
+ recipients_from(inbound_email.mail).detect { |recipient| address.match?(recipient) }
+ when Proc
+ address.call(inbound_email)
+ else
+ address.try(:match?, inbound_email) || raise(InvalidAddressError)
+ end
+ end
+
+ def mailbox_class
+ "#{mailbox_name.to_s.capitalize}Mailbox".constantize
+ end
+
+ private
+ def recipients_from(mail)
+ Array(mail.to) + Array(mail.cc) + Array(mail.bcc)
+ end
+end
diff --git a/lib/action_mailbox/routing.rb b/lib/action_mailbox/routing.rb
new file mode 100644
index 0000000000..b40e2774e4
--- /dev/null
+++ b/lib/action_mailbox/routing.rb
@@ -0,0 +1,17 @@
+module ActionMailbox
+ module Routing
+ extend ActiveSupport::Concern
+
+ class_methods do
+ attr_reader :router
+
+ def routing(routes)
+ (@router ||= ActionMailbox::Router.new).add_routes(routes)
+ end
+
+ def route(inbound_email)
+ @router.route(inbound_email)
+ end
+ end
+ end
+end
diff --git a/lib/action_mailbox/test_helper.rb b/lib/action_mailbox/test_helper.rb
new file mode 100644
index 0000000000..65a15a1281
--- /dev/null
+++ b/lib/action_mailbox/test_helper.rb
@@ -0,0 +1,22 @@
+require "mail"
+
+module ActionMailbox
+ module TestHelper
+ # Create an InboundEmail record using an eml fixture in the format of message/rfc822
+ # referenced with +fixture_name+ located in +test/fixtures/files/fixture_name+.
+ def create_inbound_email_from_fixture(fixture_name, status: :processing)
+ create_inbound_email file_fixture(fixture_name), filename: fixture_name, status: status
+ end
+
+ def create_inbound_email_from_mail(status: :processing, **mail_options)
+ raw_email = Tempfile.new.tap { |raw_email| raw_email.write Mail.new(mail_options).to_s }
+ create_inbound_email(raw_email, status: status)
+ end
+
+ def create_inbound_email(io, filename: 'mail.eml', status: :processing)
+ ActionMailbox::InboundEmail.create_and_extract_message_id! \
+ ActionDispatch::Http::UploadedFile.new(tempfile: io, filename: filename, type: 'message/rfc822'),
+ status: status
+ end
+ end
+end
diff --git a/lib/action_mailbox/version.rb b/lib/action_mailbox/version.rb
new file mode 100644
index 0000000000..23c615dbbd
--- /dev/null
+++ b/lib/action_mailbox/version.rb
@@ -0,0 +1,3 @@
+module ActionMailbox
+ VERSION = '0.1.0'
+end