From 6edccec1b97af46d2133ce701d7c307d213da9c6 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 12 Dec 2018 16:34:05 -0800 Subject: [PATCH] Basic docs for most classes --- .../action_mailbox/reroutes_controller.rb | 1 + app/jobs/action_mailbox/incineration_job.rb | 5 +++++ app/jobs/action_mailbox/routing_job.rb | 2 ++ app/models/action_mailbox/inbound_email.rb | 21 +++++++++++++++++++ .../inbound_email/incineratable.rb | 3 +++ .../incineratable/incineration.rb | 4 ++++ .../inbound_email/message_id.rb | 9 ++++++++ .../action_mailbox/inbound_email/routable.rb | 7 +++++++ 8 files changed, 52 insertions(+) diff --git a/app/controllers/rails/conductor/action_mailbox/reroutes_controller.rb b/app/controllers/rails/conductor/action_mailbox/reroutes_controller.rb index 226116a3d6..6191bda5e5 100644 --- a/app/controllers/rails/conductor/action_mailbox/reroutes_controller.rb +++ b/app/controllers/rails/conductor/action_mailbox/reroutes_controller.rb @@ -1,3 +1,4 @@ +# Rerouting will run routing and processing on an email that has already been, or attempted to be, processed. class Rails::Conductor::ActionMailbox::ReroutesController < Rails::Conductor::BaseController def create inbound_email = ActionMailbox::InboundEmail.find(params[:inbound_email_id]) diff --git a/app/jobs/action_mailbox/incineration_job.rb b/app/jobs/action_mailbox/incineration_job.rb index 4fc0cb0782..3c6c9aae4f 100644 --- a/app/jobs/action_mailbox/incineration_job.rb +++ b/app/jobs/action_mailbox/incineration_job.rb @@ -1,3 +1,8 @@ +# You can configure when this `IncinerationJob` will be run as a time-after-processing using the +# `config.action_mailbox.incinerate_after` or `ActionMailbox.incinerate_after` setting. +# +# Since this incineration is set for the future, it'll automatically ignore any `InboundEmail`s +# that have already been deleted and discard itself if so. class ActionMailbox::IncinerationJob < ActiveJob::Base queue_as { ActionMailbox.queues[:incineration] } diff --git a/app/jobs/action_mailbox/routing_job.rb b/app/jobs/action_mailbox/routing_job.rb index 786187528a..c345227617 100644 --- a/app/jobs/action_mailbox/routing_job.rb +++ b/app/jobs/action_mailbox/routing_job.rb @@ -1,3 +1,5 @@ +# Routing a new InboundEmail is an asynchronous operation, which allows the ingress controllers to quickly +# accept new incoming emails without being burdened to hang while they're actually being processed. class ActionMailbox::RoutingJob < ActiveJob::Base queue_as { ActionMailbox.queues[:routing] } diff --git a/app/models/action_mailbox/inbound_email.rb b/app/models/action_mailbox/inbound_email.rb index ea564a254e..156e2d4dbc 100644 --- a/app/models/action_mailbox/inbound_email.rb +++ b/app/models/action_mailbox/inbound_email.rb @@ -1,5 +1,26 @@ require "mail" +# The `InboundEmail` is an Active Record that keeps a reference to the raw email stored in Active Storage +# and tracks the status of processing. By default, incoming emails will go through the following lifecycle: +# +# * Pending: Just received by one of the ingress controllers and scheduled for routing. +# * Processing: During active processing, while a specific mailbox is running its #process method. +# * Delivered: Successfully processed by the specific mailbox. +# * Failed: An exception was raised during the specific mailbox's execution of the `#process` method. +# * Bounced: Rejected processing by the specific mailbox and bounced to sender. +# +# Once the `InboundEmail` has reached the status of being either `delivered`, `failed`, or `bounced`, +# it'll count as having been `#processed?`. Once processed, the `InboundEmail` will be scheduled for +# automatic incineration at a later point. +# +# When working with an `InboundEmail`, you'll usually interact with the parsed version of the source, +# which is available as a `Mail` object from `#mail`. But you can also access the raw source directly +# using the `#source` method. +# +# Examples: +# +# inbound_email.mail.from # => 'david@loudthinking.com' +# inbound_email.source # Returns the full rfc822 source of the email as text class ActionMailbox::InboundEmail < ActiveRecord::Base self.table_name = "action_mailbox_inbound_emails" diff --git a/app/models/action_mailbox/inbound_email/incineratable.rb b/app/models/action_mailbox/inbound_email/incineratable.rb index 198846422c..a049b88b69 100644 --- a/app/models/action_mailbox/inbound_email/incineratable.rb +++ b/app/models/action_mailbox/inbound_email/incineratable.rb @@ -1,3 +1,6 @@ +# Ensure that the `InboundEmail` is automatically scheduled for later incineration if the status has been +# changed to `processed`. The later incineration will be invoked at the time specified by the +# `ActionMailbox.incinerate_after` time using the `IncinerationJob`. module ActionMailbox::InboundEmail::Incineratable extend ActiveSupport::Concern diff --git a/app/models/action_mailbox/inbound_email/incineratable/incineration.rb b/app/models/action_mailbox/inbound_email/incineratable/incineration.rb index 801cc0c8b9..50861bc64b 100644 --- a/app/models/action_mailbox/inbound_email/incineratable/incineration.rb +++ b/app/models/action_mailbox/inbound_email/incineratable/incineration.rb @@ -1,3 +1,7 @@ +# Command class for carrying out the actual incineration of the `InboundMail` that's been scheduled +# for removal. Before the incineration – which really is just a call to `#destroy!` – is run, we verify +# that it's both eligible (by virtue of having already been processed) and time to do so (that is, +# the `InboundEmail` was processed after the `incinerate_after` time). class ActionMailbox::InboundEmail::Incineratable::Incineration def initialize(inbound_email) @inbound_email = inbound_email diff --git a/app/models/action_mailbox/inbound_email/message_id.rb b/app/models/action_mailbox/inbound_email/message_id.rb index 70d39d1e33..dc17e48e74 100644 --- a/app/models/action_mailbox/inbound_email/message_id.rb +++ b/app/models/action_mailbox/inbound_email/message_id.rb @@ -1,3 +1,9 @@ +# The `Message-ID` as specified by rfc822 is supposed to be a unique identifier for that individual email. +# That makes it an ideal tracking token for debugging and forensics, just like `X-Request-Id` does for +# web request. +# +# If an inbound email does not, against the rfc822 mandate, specify a Message-ID, one will be generated +# using the approach from `Mail::MessageIdField`. module ActionMailbox::InboundEmail::MessageId extend ActiveSupport::Concern @@ -6,6 +12,9 @@ module ActionMailbox::InboundEmail::MessageId end module ClassMethods + # Create a new `InboundEmail` from the raw `source` of the email, which be uploaded as a Active Storage + # attachment called `raw_email`. Before the upload, extract the Message-ID from the `source` and set + # it as an attribute on the new `InboundEmail`. def create_and_extract_message_id!(source, **options) create! message_id: extract_message_id(source), **options do |inbound_email| inbound_email.raw_email.attach io: StringIO.new(source), filename: "message.eml", content_type: "message/rfc822" diff --git a/app/models/action_mailbox/inbound_email/routable.rb b/app/models/action_mailbox/inbound_email/routable.rb index 8f5b0ddd39..f042fa4f57 100644 --- a/app/models/action_mailbox/inbound_email/routable.rb +++ b/app/models/action_mailbox/inbound_email/routable.rb @@ -1,3 +1,8 @@ +# A newly received `InboundEmail` will not be routed synchronously as part of ingress controller's receival. +# Instead, the routing will be done asynchronously, using a `RoutingJob`, to ensure maximum parallel capacity. +# +# By default, all newly created `InboundEmail` records that have the status of `pending`, which is the default, +# will be scheduled for automatic, deferred routing. module ActionMailbox::InboundEmail::Routable extend ActiveSupport::Concern @@ -5,10 +10,12 @@ module ActionMailbox::InboundEmail::Routable after_create_commit :route_later, if: :pending? end + # Enqueue a `RoutingJob` for this `InboundEmail`. def route_later ActionMailbox::RoutingJob.perform_later self end + # Route this `InboundEmail` using the routing rules declared on the `ApplicationMailbox`. def route ApplicationMailbox.route self end