Nest Action Mailbox classes in the API docs

This commit is contained in:
George Claghorn 2018-12-26 16:18:42 -05:00
parent 11a8ba1272
commit 6c168aaffb
19 changed files with 559 additions and 521 deletions

View File

@ -1,7 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
module ActionMailbox
# The base class for all Active Mailbox ingress controllers. # The base class for all Active Mailbox ingress controllers.
class ActionMailbox::BaseController < ActionController::Base class BaseController < ActionController::Base
skip_forgery_protection skip_forgery_protection
before_action :ensure_configured before_action :ensure_configured
@ -34,3 +35,4 @@ class ActionMailbox::BaseController < ActionController::Base
Rails.application.credentials.dig(:action_mailbox, :ingress_password) || ENV["RAILS_INBOUND_EMAIL_PASSWORD"] Rails.application.credentials.dig(:action_mailbox, :ingress_password) || ENV["RAILS_INBOUND_EMAIL_PASSWORD"]
end end
end end
end

View File

@ -1,5 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
module ActionMailbox
# Ingests inbound emails from Amazon's Simple Email Service (SES). # Ingests inbound emails from Amazon's Simple Email Service (SES).
# #
# Requires the full RFC 822 message in the +content+ parameter. Authenticates requests by validating their signatures. # Requires the full RFC 822 message in the +content+ parameter. Authenticates requests by validating their signatures.
@ -29,7 +30,7 @@
# to deliver emails to your application via POST requests to +/rails/action_mailbox/amazon/inbound_emails+. # to deliver emails to your application via POST requests to +/rails/action_mailbox/amazon/inbound_emails+.
# If your application lived at <tt>https://example.com</tt>, you would specify the fully-qualified URL # If your application lived at <tt>https://example.com</tt>, you would specify the fully-qualified URL
# <tt>https://example.com/rails/action_mailbox/amazon/inbound_emails</tt>. # <tt>https://example.com/rails/action_mailbox/amazon/inbound_emails</tt>.
class ActionMailbox::Ingresses::Amazon::InboundEmailsController < ActionMailbox::BaseController class Ingresses::Amazon::InboundEmailsController < BaseController
before_action :authenticate before_action :authenticate
cattr_accessor :verifier cattr_accessor :verifier
@ -50,3 +51,4 @@ class ActionMailbox::Ingresses::Amazon::InboundEmailsController < ActionMailbox:
head :unauthorized unless verifier.authentic?(request.body) head :unauthorized unless verifier.authentic?(request.body)
end end
end end
end

View File

@ -1,5 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
module ActionMailbox
# Ingests inbound emails from Mailgun. Requires the following parameters: # Ingests inbound emails from Mailgun. Requires the following parameters:
# #
# - +body-mime+: The full RFC 822 message # - +body-mime+: The full RFC 822 message
@ -41,7 +42,7 @@
# #
# If your application lived at <tt>https://example.com</tt>, you would specify the fully-qualified URL # If your application lived at <tt>https://example.com</tt>, you would specify the fully-qualified URL
# <tt>https://example.com/rails/action_mailbox/mailgun/inbound_emails/mime</tt>. # <tt>https://example.com/rails/action_mailbox/mailgun/inbound_emails/mime</tt>.
class ActionMailbox::Ingresses::Mailgun::InboundEmailsController < ActionMailbox::BaseController class Ingresses::Mailgun::InboundEmailsController < ActionMailbox::BaseController
before_action :authenticate before_action :authenticate
def create def create
@ -99,3 +100,4 @@ class ActionMailbox::Ingresses::Mailgun::InboundEmailsController < ActionMailbox
end end
end end
end end
end

View File

@ -1,5 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
module ActionMailbox
# Ingests inbound emails from Mandrill. # Ingests inbound emails from Mandrill.
# #
# Requires a +mandrill_events+ parameter containing a JSON array of Mandrill inbound email event objects. # Requires a +mandrill_events+ parameter containing a JSON array of Mandrill inbound email event objects.
@ -13,7 +14,7 @@
# - <tt>422 Unprocessable Entity</tt> if the request is missing required parameters # - <tt>422 Unprocessable Entity</tt> if the request is missing required parameters
# - <tt>500 Server Error</tt> if the Mandrill API key is missing, or one of the Active Record database, # - <tt>500 Server Error</tt> if the Mandrill API key is missing, or one of the Active Record database,
# the Active Storage service, or the Active Job backend is misconfigured or unavailable # the Active Storage service, or the Active Job backend is misconfigured or unavailable
class ActionMailbox::Ingresses::Mandrill::InboundEmailsController < ActionMailbox::BaseController class Ingresses::Mandrill::InboundEmailsController < ActionMailbox::BaseController
before_action :authenticate before_action :authenticate
def create def create
@ -78,3 +79,4 @@ class ActionMailbox::Ingresses::Mandrill::InboundEmailsController < ActionMailbo
end end
end end
end end
end

View File

@ -1,5 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
module ActionMailbox
# Ingests inbound emails relayed from Postfix. # Ingests inbound emails relayed from Postfix.
# #
# Authenticates requests using HTTP basic access authentication. The username is always +actionmailbox+, and the # Authenticates requests using HTTP basic access authentication. The username is always +actionmailbox+, and the
@ -41,7 +42,7 @@
# If your application lived at <tt>https://example.com</tt>, the full command would look like this: # If your application lived at <tt>https://example.com</tt>, the full command would look like this:
# #
# URL=https://example.com/rails/action_mailbox/postfix/inbound_emails INGRESS_PASSWORD=... bin/rails action_mailbox:ingress:postfix # URL=https://example.com/rails/action_mailbox/postfix/inbound_emails INGRESS_PASSWORD=... bin/rails action_mailbox:ingress:postfix
class ActionMailbox::Ingresses::Postfix::InboundEmailsController < ActionMailbox::BaseController class Ingresses::Postfix::InboundEmailsController < ActionMailbox::BaseController
before_action :authenticate_by_password, :require_valid_rfc822_message before_action :authenticate_by_password, :require_valid_rfc822_message
def create def create
@ -55,3 +56,4 @@ class ActionMailbox::Ingresses::Postfix::InboundEmailsController < ActionMailbox
end end
end end
end end
end

View File

@ -1,5 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
module ActionMailbox
# Ingests inbound emails from SendGrid. Requires an +email+ parameter containing a full RFC 822 message. # Ingests inbound emails from SendGrid. Requires an +email+ parameter containing a full RFC 822 message.
# #
# Authenticates requests using HTTP basic access authentication. The username is always +actionmailbox+, and the # Authenticates requests using HTTP basic access authentication. The username is always +actionmailbox+, and the
@ -43,10 +44,11 @@
# #
# *NOTE:* When configuring your SendGrid Inbound Parse webhook, be sure to check the box labeled *"Post the raw, # *NOTE:* When configuring your SendGrid Inbound Parse webhook, be sure to check the box labeled *"Post the raw,
# full MIME message."* Action Mailbox needs the raw MIME message to work. # full MIME message."* Action Mailbox needs the raw MIME message to work.
class ActionMailbox::Ingresses::Sendgrid::InboundEmailsController < ActionMailbox::BaseController class Ingresses::Sendgrid::InboundEmailsController < ActionMailbox::BaseController
before_action :authenticate_by_password before_action :authenticate_by_password
def create def create
ActionMailbox::InboundEmail.create_and_extract_message_id! params.require(:email) ActionMailbox::InboundEmail.create_and_extract_message_id! params.require(:email)
end end
end end
end

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class Rails::Conductor::ActionMailbox::InboundEmailsController < Rails::Conductor::BaseController module Rails
class Conductor::ActionMailbox::InboundEmailsController < Rails::Conductor::BaseController
def index def index
@inbound_emails = ActionMailbox::InboundEmail.order(created_at: :desc) @inbound_emails = ActionMailbox::InboundEmail.order(created_at: :desc)
end end
@ -27,3 +28,4 @@ class Rails::Conductor::ActionMailbox::InboundEmailsController < Rails::Conducto
{ io: StringIO.new(mail.to_s), filename: "inbound.eml", content_type: "message/rfc822" } { io: StringIO.new(mail.to_s), filename: "inbound.eml", content_type: "message/rfc822" }
end end
end end
end

View File

@ -1,7 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
module Rails
# Rerouting will run routing and processing on an email that has already been, or attempted to be, processed. # 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 class Conductor::ActionMailbox::ReroutesController < Rails::Conductor::BaseController
def create def create
inbound_email = ActionMailbox::InboundEmail.find(params[:inbound_email_id]) inbound_email = ActionMailbox::InboundEmail.find(params[:inbound_email_id])
reroute inbound_email reroute inbound_email
@ -15,3 +16,4 @@ class Rails::Conductor::ActionMailbox::ReroutesController < Rails::Conductor::Ba
inbound_email.route_later inbound_email.route_later
end end
end end
end

View File

@ -1,7 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
module Rails
# TODO: Move this to Rails::Conductor gem # TODO: Move this to Rails::Conductor gem
class Rails::Conductor::BaseController < ActionController::Base class Conductor::BaseController < ActionController::Base
layout "rails/conductor" layout "rails/conductor"
before_action :ensure_development_env before_action :ensure_development_env
@ -10,3 +11,4 @@ class Rails::Conductor::BaseController < ActionController::Base
head :forbidden unless Rails.env.development? head :forbidden unless Rails.env.development?
end end
end end
end

View File

@ -1,11 +1,12 @@
# frozen_string_literal: true # frozen_string_literal: true
module ActionMailbox
# You can configure when this `IncinerationJob` will be run as a time-after-processing using the # 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. # `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 # 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. # that have already been deleted and discard itself if so.
class ActionMailbox::IncinerationJob < ActiveJob::Base class IncinerationJob < ActiveJob::Base
queue_as { ActionMailbox.queues[:incineration] } queue_as { ActionMailbox.queues[:incineration] }
discard_on ActiveRecord::RecordNotFound discard_on ActiveRecord::RecordNotFound
@ -18,3 +19,4 @@ class ActionMailbox::IncinerationJob < ActiveJob::Base
inbound_email.incinerate inbound_email.incinerate
end end
end end
end

View File

@ -1,11 +1,13 @@
# frozen_string_literal: true # frozen_string_literal: true
module ActionMailbox
# Routing a new InboundEmail is an asynchronous operation, which allows the ingress controllers to quickly # 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. # accept new incoming emails without being burdened to hang while they're actually being processed.
class ActionMailbox::RoutingJob < ActiveJob::Base class RoutingJob < ActiveJob::Base
queue_as { ActionMailbox.queues[:routing] } queue_as { ActionMailbox.queues[:routing] }
def perform(inbound_email) def perform(inbound_email)
inbound_email.route inbound_email.route
end end
end end
end

View File

@ -2,6 +2,7 @@
require "mail" require "mail"
module ActionMailbox
# The `InboundEmail` is an Active Record that keeps a reference to the raw email stored in Active Storage # 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: # and tracks the status of processing. By default, incoming emails will go through the following lifecycle:
# #
@ -23,7 +24,7 @@ require "mail"
# #
# inbound_email.mail.from # => 'david@loudthinking.com' # inbound_email.mail.from # => 'david@loudthinking.com'
# inbound_email.source # Returns the full rfc822 source of the email as text # inbound_email.source # Returns the full rfc822 source of the email as text
class ActionMailbox::InboundEmail < ActiveRecord::Base class InboundEmail < ActiveRecord::Base
self.table_name = "action_mailbox_inbound_emails" self.table_name = "action_mailbox_inbound_emails"
include Incineratable, MessageId, Routable include Incineratable, MessageId, Routable
@ -43,3 +44,4 @@ class ActionMailbox::InboundEmail < ActiveRecord::Base
delivered? || failed? || bounced? delivered? || failed? || bounced?
end end
end end
end

View File

@ -1,10 +1,11 @@
# frozen_string_literal: true # frozen_string_literal: true
module ActionMailbox
# Command class for carrying out the actual incineration of the `InboundMail` that's been scheduled # 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 # 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, # 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). # the `InboundEmail` was processed after the `incinerate_after` time).
class ActionMailbox::InboundEmail::Incineratable::Incineration class InboundEmail::Incineratable::Incineration
def initialize(inbound_email) def initialize(inbound_email)
@inbound_email = inbound_email @inbound_email = inbound_email
end end
@ -22,3 +23,4 @@ class ActionMailbox::InboundEmail::Incineratable::Incineration
@inbound_email.processed? @inbound_email.processed?
end end
end end
end

View File

@ -1,7 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
class Mail::Address module Mail
class Address
def ==(other_address) def ==(other_address)
other_address.is_a?(Mail::Address) && to_s == other_address.to_s other_address.is_a?(Mail::Address) && to_s == other_address.to_s
end end
end end
end

View File

@ -1,7 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
class Mail::Address module Mail
class Address
def self.wrap(address) def self.wrap(address)
address.is_a?(Mail::Address) ? address : Mail::Address.new(address) address.is_a?(Mail::Address) ? address : Mail::Address.new(address)
end end
end end
end

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class Mail::Message module Mail
class Message
def from_address def from_address
header[:from]&.address_list&.addresses&.first header[:from]&.address_list&.addresses&.first
end end
@ -25,3 +26,4 @@ class Mail::Message
Array(header[:x_original_to]).collect { |header| Mail::Address.new header.to_s } Array(header[:x_original_to]).collect { |header| Mail::Address.new header.to_s }
end end
end end
end

View File

@ -1,7 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
class Mail::Message module Mail
class Message
def recipients def recipients
Array(to) + Array(cc) + Array(bcc) + Array(header[:x_original_to]).map(&:to_s) Array(to) + Array(cc) + Array(bcc) + Array(header[:x_original_to]).map(&:to_s)
end end
end end
end

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
module ActionMailbox
# Encapsulates the routes that live on the ApplicationMailbox and performs the actual routing when # Encapsulates the routes that live on the ApplicationMailbox and performs the actual routing when
# an inbound_email is received. # an inbound_email is received.
class ActionMailbox::Router class Router
class RoutingError < StandardError; end class RoutingError < StandardError; end
def initialize def initialize
@ -36,5 +37,6 @@ class ActionMailbox::Router
routes.detect { |route| route.match?(inbound_email) }.try(:mailbox_class) routes.detect { |route| route.match?(inbound_email) }.try(:mailbox_class)
end end
end end
end
require "action_mailbox/router/route" require "action_mailbox/router/route"

View File

@ -1,9 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
module ActionMailbox
# Encapsulates a route, which can then be matched against an inbound_email and provide a lookup of the matching # Encapsulates a route, which can then be matched against an inbound_email and provide a lookup of the matching
# mailbox class. See examples for the different route addresses and how to use them in the `ActionMailbox::Base` # mailbox class. See examples for the different route addresses and how to use them in the `ActionMailbox::Base`
# documentation. # documentation.
class ActionMailbox::Router::Route class Router::Route
attr_reader :address, :mailbox_name attr_reader :address, :mailbox_name
def initialize(address, to:) def initialize(address, to:)
@ -38,3 +39,4 @@ class ActionMailbox::Router::Route
end end
end end
end end
end