From fb5c07525f64f15aad3a15b6e6add80d790dad1b Mon Sep 17 00:00:00 2001 From: Rob Zolkos Date: Wed, 9 Jan 2019 15:46:38 -0500 Subject: Bring in all of aws-sdk-sns if using Amazon ingress Requiring _just_ the `Aws::SNS::MessageVerifier` does not work as it references other classes in the AWS SDK that are not in this one class. Bringing in the entire SNS SDK verifies the authenticity correctly. --- .../action_mailbox/ingresses/amazon/inbound_emails_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actionmailbox') diff --git a/actionmailbox/app/controllers/action_mailbox/ingresses/amazon/inbound_emails_controller.rb b/actionmailbox/app/controllers/action_mailbox/ingresses/amazon/inbound_emails_controller.rb index cf45ac8408..e0a187054e 100644 --- a/actionmailbox/app/controllers/action_mailbox/ingresses/amazon/inbound_emails_controller.rb +++ b/actionmailbox/app/controllers/action_mailbox/ingresses/amazon/inbound_emails_controller.rb @@ -37,7 +37,7 @@ module ActionMailbox def self.prepare self.verifier ||= begin - require "aws-sdk-sns/message_verifier" + require "aws-sdk-sns" Aws::SNS::MessageVerifier.new end end -- cgit v1.2.3 From b77d2d9a032b64c7819bf950194c715a3404da37 Mon Sep 17 00:00:00 2001 From: Tomek Maszkowski Date: Mon, 31 Dec 2018 18:16:44 -0500 Subject: Added Postmark ingress support --- .../postmark/inbound_emails_controller.rb | 62 ++++++++++++++++++++++ actionmailbox/config/routes.rb | 1 + .../postmark/inbound_emails_controller_test.rb | 55 +++++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 actionmailbox/app/controllers/action_mailbox/ingresses/postmark/inbound_emails_controller.rb create mode 100644 actionmailbox/test/controllers/ingresses/postmark/inbound_emails_controller_test.rb (limited to 'actionmailbox') diff --git a/actionmailbox/app/controllers/action_mailbox/ingresses/postmark/inbound_emails_controller.rb b/actionmailbox/app/controllers/action_mailbox/ingresses/postmark/inbound_emails_controller.rb new file mode 100644 index 0000000000..ec4e0da5ec --- /dev/null +++ b/actionmailbox/app/controllers/action_mailbox/ingresses/postmark/inbound_emails_controller.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +module ActionMailbox + # Ingests inbound emails from Postmark. Requires +RawEmail+ parameter containing a full RFC 822 message. + # + # Authenticates requests using HTTP basic access authentication. The username is always +actionmailbox+, and the + # password is read from the application's encrypted credentials or an environment variable. See the Usage section below. + # + # Note that basic authentication is insecure over unencrypted HTTP. An attacker that intercepts cleartext requests to + # the Postmark ingress can learn its password. You should only use the Postmark ingress over HTTPS. + # + # Returns: + # + # - 204 No Content if an inbound email is successfully recorded and enqueued for routing to the appropriate mailbox + # - 401 Unauthorized if the request's signature could not be validated + # - 404 Not Found if Action Mailbox is not configured to accept inbound emails from Postmark + # - 422 Unprocessable Entity if the request is missing the required +RawEmail+ parameter + # - 500 Server Error if the ingress password is not configured, or if one of the Active Record database, + # the Active Storage service, or the Active Job backend is misconfigured or unavailable + # + # == Usage + # + # 1. Tell Action Mailbox to accept emails from Postmark: + # + # # config/environments/production.rb + # config.action_mailbox.ingress = :postmark + # + # 2. Generate a strong password that Action Mailbox can use to authenticate requests to the Postmark ingress. + # + # Use rails credentials:edit to add the password to your application's encrypted credentials under + # +action_mailbox.ingress_password+, where Action Mailbox will automatically find it: + # + # action_mailbox: + # ingress_password: ... + # + # Alternatively, provide the password in the +RAILS_INBOUND_EMAIL_PASSWORD+ environment variable. + # + # 3. {Configure Postmark inbound webhook}[https://postmarkapp.com/manual#configure-your-inbound-webhook-url] + # to forward inbound emails to +/rails/action_mailbox/postmark/inbound_emails+ with the username +actionmailbox+ and + # the password you previously generated. If your application lived at https://example.com, you would + # configure Postmark with the following fully-qualified URL: + # + # https://actionmailbox:PASSWORD@example.com/rails/action_mailbox/postmark/inbound_emails + # + # *NOTE:* When configuring your Postmark inbound webhook, be sure to check the box labeled *"Include raw email + # content in JSON payload"*. Action Mailbox needs the raw email content to work. + class Ingresses::Postmark::InboundEmailsController < ActionMailbox::BaseController + before_action :authenticate_by_password + + def create + ActionMailbox::InboundEmail.create_and_extract_message_id! params.require("RawEmail") + rescue ActionController::ParameterMissing => error + logger.error <<~MESSAGE + #{error.message} + + When configuring your Postmark inbound webhook, be sure to check the box + labeled "Include raw email content in JSON payload". + MESSAGE + head :unprocessable_entity + end + end +end diff --git a/actionmailbox/config/routes.rb b/actionmailbox/config/routes.rb index f1bc9847f5..7aa230fdff 100644 --- a/actionmailbox/config/routes.rb +++ b/actionmailbox/config/routes.rb @@ -5,6 +5,7 @@ Rails.application.routes.draw do post "/amazon/inbound_emails" => "amazon/inbound_emails#create", as: :rails_amazon_inbound_emails post "/mandrill/inbound_emails" => "mandrill/inbound_emails#create", as: :rails_mandrill_inbound_emails post "/postfix/inbound_emails" => "postfix/inbound_emails#create", as: :rails_postfix_inbound_emails + post "/postmark/inbound_emails" => "postmark/inbound_emails#create", as: :rails_postmark_inbound_emails post "/sendgrid/inbound_emails" => "sendgrid/inbound_emails#create", as: :rails_sendgrid_inbound_emails # Mailgun requires that a webhook's URL end in 'mime' for it to receive the raw contents of emails. diff --git a/actionmailbox/test/controllers/ingresses/postmark/inbound_emails_controller_test.rb b/actionmailbox/test/controllers/ingresses/postmark/inbound_emails_controller_test.rb new file mode 100644 index 0000000000..11b579b39c --- /dev/null +++ b/actionmailbox/test/controllers/ingresses/postmark/inbound_emails_controller_test.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require "test_helper" + +class ActionMailbox::Ingresses::Postmark::InboundEmailsControllerTest < ActionDispatch::IntegrationTest + setup { ActionMailbox.ingress = :postmark } + + test "receiving an inbound email from Postmark" do + assert_difference -> { ActionMailbox::InboundEmail.count }, +1 do + post rails_postmark_inbound_emails_url, + headers: { authorization: credentials }, params: { RawEmail: file_fixture("../files/welcome.eml").read } + end + + assert_response :no_content + + inbound_email = ActionMailbox::InboundEmail.last + assert_equal file_fixture("../files/welcome.eml").read, inbound_email.raw_email.download + assert_equal "0CB459E0-0336-41DA-BC88-E6E28C697DDB@37signals.com", inbound_email.message_id + end + + test "rejecting when RawEmail param is missing" do + assert_no_difference -> { ActionMailbox::InboundEmail.count } do + post rails_postmark_inbound_emails_url, + headers: { authorization: credentials }, params: { From: "someone@example.com" } + end + + assert_response :unprocessable_entity + end + + test "rejecting an unauthorized inbound email from Postmark" do + assert_no_difference -> { ActionMailbox::InboundEmail.count } do + post rails_postmark_inbound_emails_url, params: { RawEmail: file_fixture("../files/welcome.eml").read } + end + + assert_response :unauthorized + end + + test "raising when the configured password is nil" do + switch_password_to nil do + assert_raises ArgumentError do + post rails_postmark_inbound_emails_url, + headers: { authorization: credentials }, params: { RawEmail: file_fixture("../files/welcome.eml").read } + end + end + end + + test "raising when the configured password is blank" do + switch_password_to "" do + assert_raises ArgumentError do + post rails_postmark_inbound_emails_url, + headers: { authorization: credentials }, params: { RawEmail: file_fixture("../files/welcome.eml").read } + end + end + end +end -- cgit v1.2.3 From 7aaf9b8076704426c5e3f20bc3018c398f58dc88 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Wed, 9 Jan 2019 22:21:57 -0500 Subject: Tweak Postmark ingress docs [ci skip] --- .../ingresses/postmark/inbound_emails_controller.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'actionmailbox') diff --git a/actionmailbox/app/controllers/action_mailbox/ingresses/postmark/inbound_emails_controller.rb b/actionmailbox/app/controllers/action_mailbox/ingresses/postmark/inbound_emails_controller.rb index ec4e0da5ec..309085c82a 100644 --- a/actionmailbox/app/controllers/action_mailbox/ingresses/postmark/inbound_emails_controller.rb +++ b/actionmailbox/app/controllers/action_mailbox/ingresses/postmark/inbound_emails_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module ActionMailbox - # Ingests inbound emails from Postmark. Requires +RawEmail+ parameter containing a full RFC 822 message. + # Ingests inbound emails from Postmark. Requires a +RawEmail+ parameter containing a full RFC 822 message. # # Authenticates requests using HTTP basic access authentication. The username is always +actionmailbox+, and the # password is read from the application's encrypted credentials or an environment variable. See the Usage section below. @@ -35,10 +35,10 @@ module ActionMailbox # # Alternatively, provide the password in the +RAILS_INBOUND_EMAIL_PASSWORD+ environment variable. # - # 3. {Configure Postmark inbound webhook}[https://postmarkapp.com/manual#configure-your-inbound-webhook-url] - # to forward inbound emails to +/rails/action_mailbox/postmark/inbound_emails+ with the username +actionmailbox+ and - # the password you previously generated. If your application lived at https://example.com, you would - # configure Postmark with the following fully-qualified URL: + # 3. {Configure Postmark}[https://postmarkapp.com/manual#configure-your-inbound-webhook-url] to forward inbound emails + # to +/rails/action_mailbox/postmark/inbound_emails+ with the username +actionmailbox+ and the password you + # previously generated. If your application lived at https://example.com, you would configure your + # Postmark inbound webhook with the following fully-qualified URL: # # https://actionmailbox:PASSWORD@example.com/rails/action_mailbox/postmark/inbound_emails # -- cgit v1.2.3 From 9edc84cdfa8908a436901d85309cadf65d457d8f Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Thu, 10 Jan 2019 11:04:54 -0500 Subject: Add Postmark to the ingress lists [ci skip] --- actionmailbox/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actionmailbox') diff --git a/actionmailbox/README.md b/actionmailbox/README.md index f0bde03961..c70f73b40f 100644 --- a/actionmailbox/README.md +++ b/actionmailbox/README.md @@ -1,6 +1,6 @@ # Action Mailbox -Action Mailbox routes incoming emails to controller-like mailboxes for processing in Rails. It ships with ingresses for Amazon SES, Mailgun, Mandrill, and SendGrid. You can also handle inbound mails directly via the built-in Postfix ingress. +Action Mailbox routes incoming emails to controller-like mailboxes for processing in Rails. It ships with ingresses for Amazon SES, Mailgun, Mandrill, Postmark, and SendGrid. You can also handle inbound mails directly via the built-in Postfix ingress. The inbound emails are turned into `InboundEmail` records using Active Record and feature lifecycle tracking, storage of the original email on cloud storage via Active Storage, and responsible data handling with on-by-default incineration. -- cgit v1.2.3 From 512b5316dd33a8aa36821ee9b134d6652fd4a35f Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Sat, 12 Jan 2019 21:38:26 -0500 Subject: Add Exim and Qmail support to Action Mailbox --- actionmailbox/README.md | 2 +- .../ingresses/postfix/inbound_emails_controller.rb | 59 ------------ .../ingresses/relay/inbound_emails_controller.rb | 65 ++++++++++++++ actionmailbox/config/routes.rb | 2 +- .../lib/action_mailbox/postfix_relayer.rb | 67 -------------- actionmailbox/lib/action_mailbox/relayer.rb | 75 ++++++++++++++++ actionmailbox/lib/tasks/ingress.rake | 58 ++++++++++-- .../postfix/inbound_emails_controller_test.rb | 56 ------------ .../relay/inbound_emails_controller_test.rb | 56 ++++++++++++ actionmailbox/test/unit/postfix_relayer_test.rb | 92 ------------------- actionmailbox/test/unit/relayer_test.rb | 100 +++++++++++++++++++++ 11 files changed, 351 insertions(+), 281 deletions(-) delete mode 100644 actionmailbox/app/controllers/action_mailbox/ingresses/postfix/inbound_emails_controller.rb create mode 100644 actionmailbox/app/controllers/action_mailbox/ingresses/relay/inbound_emails_controller.rb delete mode 100644 actionmailbox/lib/action_mailbox/postfix_relayer.rb create mode 100644 actionmailbox/lib/action_mailbox/relayer.rb delete mode 100644 actionmailbox/test/controllers/ingresses/postfix/inbound_emails_controller_test.rb create mode 100644 actionmailbox/test/controllers/ingresses/relay/inbound_emails_controller_test.rb delete mode 100644 actionmailbox/test/unit/postfix_relayer_test.rb create mode 100644 actionmailbox/test/unit/relayer_test.rb (limited to 'actionmailbox') diff --git a/actionmailbox/README.md b/actionmailbox/README.md index c70f73b40f..9a47223d3b 100644 --- a/actionmailbox/README.md +++ b/actionmailbox/README.md @@ -1,6 +1,6 @@ # Action Mailbox -Action Mailbox routes incoming emails to controller-like mailboxes for processing in Rails. It ships with ingresses for Amazon SES, Mailgun, Mandrill, Postmark, and SendGrid. You can also handle inbound mails directly via the built-in Postfix ingress. +Action Mailbox routes incoming emails to controller-like mailboxes for processing in Rails. It ships with ingresses for Amazon SES, Mailgun, Mandrill, Postmark, and SendGrid. You can also handle inbound mails directly via the built-in Exim, Postfix, and Qmail ingresses. The inbound emails are turned into `InboundEmail` records using Active Record and feature lifecycle tracking, storage of the original email on cloud storage via Active Storage, and responsible data handling with on-by-default incineration. diff --git a/actionmailbox/app/controllers/action_mailbox/ingresses/postfix/inbound_emails_controller.rb b/actionmailbox/app/controllers/action_mailbox/ingresses/postfix/inbound_emails_controller.rb deleted file mode 100644 index c0d5002a12..0000000000 --- a/actionmailbox/app/controllers/action_mailbox/ingresses/postfix/inbound_emails_controller.rb +++ /dev/null @@ -1,59 +0,0 @@ -# frozen_string_literal: true - -module ActionMailbox - # Ingests inbound emails relayed from Postfix. - # - # Authenticates requests using HTTP basic access authentication. The username is always +actionmailbox+, and the - # password is read from the application's encrypted credentials or an environment variable. See the Usage section below. - # - # Note that basic authentication is insecure over unencrypted HTTP. An attacker that intercepts cleartext requests to - # the Postfix ingress can learn its password. You should only use the Postfix ingress over HTTPS. - # - # Returns: - # - # - 204 No Content if an inbound email is successfully recorded and enqueued for routing to the appropriate mailbox - # - 401 Unauthorized if the request could not be authenticated - # - 404 Not Found if Action Mailbox is not configured to accept inbound emails from Postfix - # - 415 Unsupported Media Type if the request does not contain an RFC 822 message - # - 500 Server Error if the ingress password is not configured, or if one of the Active Record database, - # the Active Storage service, or the Active Job backend is misconfigured or unavailable - # - # == Usage - # - # 1. Tell Action Mailbox to accept emails from Postfix: - # - # # config/environments/production.rb - # config.action_mailbox.ingress = :postfix - # - # 2. Generate a strong password that Action Mailbox can use to authenticate requests to the Postfix ingress. - # - # Use rails credentials:edit to add the password to your application's encrypted credentials under - # +action_mailbox.ingress_password+, where Action Mailbox will automatically find it: - # - # action_mailbox: - # ingress_password: ... - # - # Alternatively, provide the password in the +RAILS_INBOUND_EMAIL_PASSWORD+ environment variable. - # - # 3. {Configure Postfix}[https://serverfault.com/questions/258469/how-to-configure-postfix-to-pipe-all-incoming-email-to-a-script] - # to pipe inbound emails to bin/rails action_mailbox:ingress:postfix, providing the +URL+ of the Postfix - # ingress and the +INGRESS_PASSWORD+ you previously generated. - # - # If your application lived at https://example.com, 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 - class Ingresses::Postfix::InboundEmailsController < ActionMailbox::BaseController - before_action :authenticate_by_password, :require_valid_rfc822_message - - def create - ActionMailbox::InboundEmail.create_and_extract_message_id! request.body.read - end - - private - def require_valid_rfc822_message - unless request.content_type == "message/rfc822" - head :unsupported_media_type - end - end - end -end diff --git a/actionmailbox/app/controllers/action_mailbox/ingresses/relay/inbound_emails_controller.rb b/actionmailbox/app/controllers/action_mailbox/ingresses/relay/inbound_emails_controller.rb new file mode 100644 index 0000000000..2d91c968c8 --- /dev/null +++ b/actionmailbox/app/controllers/action_mailbox/ingresses/relay/inbound_emails_controller.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +module ActionMailbox + # Ingests inbound emails relayed from an SMTP server. + # + # Authenticates requests using HTTP basic access authentication. The username is always +actionmailbox+, and the + # password is read from the application's encrypted credentials or an environment variable. See the Usage section below. + # + # Note that basic authentication is insecure over unencrypted HTTP. An attacker that intercepts cleartext requests to + # the ingress can learn its password. You should only use this ingress over HTTPS. + # + # Returns: + # + # - 204 No Content if an inbound email is successfully recorded and enqueued for routing to the appropriate mailbox + # - 401 Unauthorized if the request could not be authenticated + # - 404 Not Found if Action Mailbox is not configured to accept inbound emails relayed from an SMTP server + # - 415 Unsupported Media Type if the request does not contain an RFC 822 message + # - 500 Server Error if the ingress password is not configured, or if one of the Active Record database, + # the Active Storage service, or the Active Job backend is misconfigured or unavailable + # + # == Usage + # + # 1. Tell Action Mailbox to accept emails from an SMTP relay: + # + # # config/environments/production.rb + # config.action_mailbox.ingress = :relay + # + # 2. Generate a strong password that Action Mailbox can use to authenticate requests to the ingress. + # + # Use rails credentials:edit to add the password to your application's encrypted credentials under + # +action_mailbox.ingress_password+, where Action Mailbox will automatically find it: + # + # action_mailbox: + # ingress_password: ... + # + # Alternatively, provide the password in the +RAILS_INBOUND_EMAIL_PASSWORD+ environment variable. + # + # 3. Configure your SMTP server to pipe inbound emails to the appropriate ingress command, providing the +URL+ of the + # relay ingress and the +INGRESS_PASSWORD+ you previously generated. + # + # If your application lives at https://example.com, you would configure the Postfix SMTP server to pipe + # inbound emails to the following command: + # + # bin/rails action_mailbox:ingress:postfix URL=https://example.com/rails/action_mailbox/postfix/inbound_emails INGRESS_PASSWORD=... + # + # Built-in ingress commands are available for these popular SMTP servers: + # + # - Exim (bin/rails action_mailbox:ingress:exim) + # - Postfix (bin/rails action_mailbox:ingress:postfix) + # - Qmail (bin/rails action_mailbox:ingress:qmail) + class Ingresses::Relay::InboundEmailsController < ActionMailbox::BaseController + before_action :authenticate_by_password, :require_valid_rfc822_message + + def create + ActionMailbox::InboundEmail.create_and_extract_message_id! request.body.read + end + + private + def require_valid_rfc822_message + unless request.content_type == "message/rfc822" + head :unsupported_media_type + end + end + end +end diff --git a/actionmailbox/config/routes.rb b/actionmailbox/config/routes.rb index 7aa230fdff..517d2835af 100644 --- a/actionmailbox/config/routes.rb +++ b/actionmailbox/config/routes.rb @@ -4,8 +4,8 @@ Rails.application.routes.draw do scope "/rails/action_mailbox", module: "action_mailbox/ingresses" do post "/amazon/inbound_emails" => "amazon/inbound_emails#create", as: :rails_amazon_inbound_emails post "/mandrill/inbound_emails" => "mandrill/inbound_emails#create", as: :rails_mandrill_inbound_emails - post "/postfix/inbound_emails" => "postfix/inbound_emails#create", as: :rails_postfix_inbound_emails post "/postmark/inbound_emails" => "postmark/inbound_emails#create", as: :rails_postmark_inbound_emails + post "/relay/inbound_emails" => "relay/inbound_emails#create", as: :rails_relay_inbound_emails post "/sendgrid/inbound_emails" => "sendgrid/inbound_emails#create", as: :rails_sendgrid_inbound_emails # Mailgun requires that a webhook's URL end in 'mime' for it to receive the raw contents of emails. diff --git a/actionmailbox/lib/action_mailbox/postfix_relayer.rb b/actionmailbox/lib/action_mailbox/postfix_relayer.rb deleted file mode 100644 index d43c56ed2b..0000000000 --- a/actionmailbox/lib/action_mailbox/postfix_relayer.rb +++ /dev/null @@ -1,67 +0,0 @@ -# frozen_string_literal: true - -require "action_mailbox/version" -require "net/http" -require "uri" - -module ActionMailbox - class PostfixRelayer - class Result < Struct.new(:output) - def success? - !failure? - end - - def failure? - output.match?(/\A[45]\.\d{1,3}\.\d{1,3}(\s|\z)/) - end - end - - CONTENT_TYPE = "message/rfc822" - USER_AGENT = "Action Mailbox Postfix relayer v#{ActionMailbox.version}" - - attr_reader :uri, :username, :password - - def initialize(url:, username: "actionmailbox", password:) - @uri, @username, @password = URI(url), username, password - end - - def relay(source) - case response = post(source) - when Net::HTTPSuccess - Result.new "2.0.0 Successfully relayed message to Postfix ingress" - when Net::HTTPUnauthorized - Result.new "4.7.0 Invalid credentials for Postfix ingress" - else - Result.new "4.0.0 HTTP #{response.code}" - end - rescue IOError, SocketError, SystemCallError => error - Result.new "4.4.2 Network error relaying to Postfix ingress: #{error.message}" - rescue Timeout::Error - Result.new "4.4.2 Timed out relaying to Postfix ingress" - rescue => error - Result.new "4.0.0 Error relaying to Postfix ingress: #{error.message}" - end - - private - def post(source) - client.post uri, source, - "Content-Type" => CONTENT_TYPE, - "User-Agent" => USER_AGENT, - "Authorization" => "Basic #{Base64.strict_encode64(username + ":" + password)}" - end - - def client - @client ||= Net::HTTP.new(uri.host, uri.port).tap do |connection| - if uri.scheme == "https" - require "openssl" - - connection.use_ssl = true - connection.verify_mode = OpenSSL::SSL::VERIFY_PEER - end - - connection.open_timeout = 1 - connection.read_timeout = 10 - end - end - end -end diff --git a/actionmailbox/lib/action_mailbox/relayer.rb b/actionmailbox/lib/action_mailbox/relayer.rb new file mode 100644 index 0000000000..e2890acb60 --- /dev/null +++ b/actionmailbox/lib/action_mailbox/relayer.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +require "action_mailbox/version" +require "net/http" +require "uri" + +module ActionMailbox + class Relayer + class Result < Struct.new(:status_code, :message) + def success? + !failure? + end + + def failure? + transient_failure? || permanent_failure? + end + + def transient_failure? + status_code.start_with?("4.") + end + + def permanent_failure? + status_code.start_with?("5.") + end + end + + CONTENT_TYPE = "message/rfc822" + USER_AGENT = "Action Mailbox relayer v#{ActionMailbox.version}" + + attr_reader :uri, :username, :password + + def initialize(url:, username: "actionmailbox", password:) + @uri, @username, @password = URI(url), username, password + end + + def relay(source) + case response = post(source) + when Net::HTTPSuccess + Result.new "2.0.0", "Successfully relayed message to ingress" + when Net::HTTPUnauthorized + Result.new "4.7.0", "Invalid credentials for ingress" + else + Result.new "4.0.0", "HTTP #{response.code}" + end + rescue IOError, SocketError, SystemCallError => error + Result.new "4.4.2", "Network error relaying to ingress: #{error.message}" + rescue Timeout::Error + Result.new "4.4.2", "Timed out relaying to ingress" + rescue => error + Result.new "4.0.0", "Error relaying to ingress: #{error.message}" + end + + private + def post(source) + client.post uri, source, + "Content-Type" => CONTENT_TYPE, + "User-Agent" => USER_AGENT, + "Authorization" => "Basic #{Base64.strict_encode64(username + ":" + password)}" + end + + def client + @client ||= Net::HTTP.new(uri.host, uri.port).tap do |connection| + if uri.scheme == "https" + require "openssl" + + connection.use_ssl = true + connection.verify_mode = OpenSSL::SSL::VERIFY_PEER + end + + connection.open_timeout = 1 + connection.read_timeout = 10 + end + end + end +end diff --git a/actionmailbox/lib/tasks/ingress.rake b/actionmailbox/lib/tasks/ingress.rake index f775bbdfd7..43b613ea12 100644 --- a/actionmailbox/lib/tasks/ingress.rake +++ b/actionmailbox/lib/tasks/ingress.rake @@ -2,12 +2,37 @@ namespace :action_mailbox do namespace :ingress do - desc "Pipe an inbound email from STDIN to the Postfix ingress (URL and INGRESS_PASSWORD required)" - task :postfix do + task :environment do require "active_support" require "active_support/core_ext/object/blank" - require "action_mailbox/postfix_relayer" + require "action_mailbox/relayer" + end + + desc "Relay an inbound email from Exim to Action Mailbox (URL and INGRESS_PASSWORD required)" + task exim: "action_mailbox:ingress:environment" do + url, password = ENV.values_at("URL", "INGRESS_PASSWORD") + + if url.blank? || password.blank? + print "URL and INGRESS_PASSWORD are required" + exit 64 # EX_USAGE + end + + ActionMailbox::Relayer.new(url: url, password: password).relay(STDIN.read).tap do |result| + print result.message + + case + when result.success? + exit 0 + when result.transient_failure? + exit 75 # EX_TEMPFAIL + else + exit 69 # EX_UNAVAILABLE + end + end + end + desc "Relay an inbound email from Postfix to Action Mailbox (URL and INGRESS_PASSWORD required)" + task postfix: "action_mailbox:ingress:environment" do url, password = ENV.values_at("URL", "INGRESS_PASSWORD") if url.blank? || password.blank? @@ -15,10 +40,33 @@ namespace :action_mailbox do exit 1 end - ActionMailbox::PostfixRelayer.new(url: url, password: password).relay(STDIN.read).tap do |result| - print result.output + ActionMailbox::Relayer.new(url: url, password: password).relay(STDIN.read).tap do |result| + print "#{result.status_code} #{result.message}" exit result.success? end end + + desc "Relay an inbound email from Qmail to Action Mailbox (URL and INGRESS_PASSWORD required)" + task qmail: "action_mailbox:ingress:environment" do + url, password = ENV.values_at("URL", "INGRESS_PASSWORD") + + if url.blank? || password.blank? + print "URL and INGRESS_PASSWORD are required" + exit 111 + end + + ActionMailbox::Relayer.new(url: url, password: password).relay(STDIN.read).tap do |result| + print result.message + + case + when result.success? + exit 0 + when result.transient_failure? + exit 111 + else + exit 100 + end + end + end end end diff --git a/actionmailbox/test/controllers/ingresses/postfix/inbound_emails_controller_test.rb b/actionmailbox/test/controllers/ingresses/postfix/inbound_emails_controller_test.rb deleted file mode 100644 index d646f5e859..0000000000 --- a/actionmailbox/test/controllers/ingresses/postfix/inbound_emails_controller_test.rb +++ /dev/null @@ -1,56 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -class ActionMailbox::Ingresses::Postfix::InboundEmailsControllerTest < ActionDispatch::IntegrationTest - setup { ActionMailbox.ingress = :postfix } - - test "receiving an inbound email from Postfix" do - assert_difference -> { ActionMailbox::InboundEmail.count }, +1 do - post rails_postfix_inbound_emails_url, headers: { "Authorization" => credentials, "Content-Type" => "message/rfc822" }, - params: file_fixture("../files/welcome.eml").read - end - - assert_response :no_content - - inbound_email = ActionMailbox::InboundEmail.last - assert_equal file_fixture("../files/welcome.eml").read, inbound_email.raw_email.download - assert_equal "0CB459E0-0336-41DA-BC88-E6E28C697DDB@37signals.com", inbound_email.message_id - end - - test "rejecting an unauthorized inbound email from Postfix" do - assert_no_difference -> { ActionMailbox::InboundEmail.count } do - post rails_postfix_inbound_emails_url, headers: { "Content-Type" => "message/rfc822" }, - params: file_fixture("../files/welcome.eml").read - end - - assert_response :unauthorized - end - - test "rejecting an inbound email of an unsupported media type from Postfix" do - assert_no_difference -> { ActionMailbox::InboundEmail.count } do - post rails_postfix_inbound_emails_url, headers: { "Authorization" => credentials, "Content-Type" => "text/plain" }, - params: file_fixture("../files/welcome.eml").read - end - - assert_response :unsupported_media_type - end - - test "raising when the configured password is nil" do - switch_password_to nil do - assert_raises ArgumentError do - post rails_postfix_inbound_emails_url, headers: { "Authorization" => credentials, "Content-Type" => "message/rfc822" }, - params: file_fixture("../files/welcome.eml").read - end - end - end - - test "raising when the configured password is blank" do - switch_password_to "" do - assert_raises ArgumentError do - post rails_postfix_inbound_emails_url, headers: { "Authorization" => credentials, "Content-Type" => "message/rfc822" }, - params: file_fixture("../files/welcome.eml").read - end - end - end -end diff --git a/actionmailbox/test/controllers/ingresses/relay/inbound_emails_controller_test.rb b/actionmailbox/test/controllers/ingresses/relay/inbound_emails_controller_test.rb new file mode 100644 index 0000000000..67c5993f7f --- /dev/null +++ b/actionmailbox/test/controllers/ingresses/relay/inbound_emails_controller_test.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require "test_helper" + +class ActionMailbox::Ingresses::Relay::InboundEmailsControllerTest < ActionDispatch::IntegrationTest + setup { ActionMailbox.ingress = :relay } + + test "receiving an inbound email relayed from an SMTP server" do + assert_difference -> { ActionMailbox::InboundEmail.count }, +1 do + post rails_relay_inbound_emails_url, headers: { "Authorization" => credentials, "Content-Type" => "message/rfc822" }, + params: file_fixture("../files/welcome.eml").read + end + + assert_response :no_content + + inbound_email = ActionMailbox::InboundEmail.last + assert_equal file_fixture("../files/welcome.eml").read, inbound_email.raw_email.download + assert_equal "0CB459E0-0336-41DA-BC88-E6E28C697DDB@37signals.com", inbound_email.message_id + end + + test "rejecting an unauthorized inbound email" do + assert_no_difference -> { ActionMailbox::InboundEmail.count } do + post rails_relay_inbound_emails_url, headers: { "Content-Type" => "message/rfc822" }, + params: file_fixture("../files/welcome.eml").read + end + + assert_response :unauthorized + end + + test "rejecting an inbound email of an unsupported media type" do + assert_no_difference -> { ActionMailbox::InboundEmail.count } do + post rails_relay_inbound_emails_url, headers: { "Authorization" => credentials, "Content-Type" => "text/plain" }, + params: file_fixture("../files/welcome.eml").read + end + + assert_response :unsupported_media_type + end + + test "raising when the configured password is nil" do + switch_password_to nil do + assert_raises ArgumentError do + post rails_relay_inbound_emails_url, headers: { "Authorization" => credentials, "Content-Type" => "message/rfc822" }, + params: file_fixture("../files/welcome.eml").read + end + end + end + + test "raising when the configured password is blank" do + switch_password_to "" do + assert_raises ArgumentError do + post rails_relay_inbound_emails_url, headers: { "Authorization" => credentials, "Content-Type" => "message/rfc822" }, + params: file_fixture("../files/welcome.eml").read + end + end + end +end diff --git a/actionmailbox/test/unit/postfix_relayer_test.rb b/actionmailbox/test/unit/postfix_relayer_test.rb deleted file mode 100644 index 5f7496ec3f..0000000000 --- a/actionmailbox/test/unit/postfix_relayer_test.rb +++ /dev/null @@ -1,92 +0,0 @@ -# frozen_string_literal: true - -require_relative "../test_helper" - -require "action_mailbox/postfix_relayer" - -module ActionMailbox - class PostfixRelayerTest < ActiveSupport::TestCase - URL = "https://example.com/rails/action_mailbox/postfix/inbound_emails" - INGRESS_PASSWORD = "secret" - - setup do - @relayer = ActionMailbox::PostfixRelayer.new(url: URL, password: INGRESS_PASSWORD) - end - - test "successfully relaying an email" do - stub_request(:post, URL).to_return status: 204 - - result = @relayer.relay(file_fixture("welcome.eml").read) - assert_equal "2.0.0 Successfully relayed message to Postfix ingress", result.output - assert result.success? - assert_not result.failure? - - assert_requested :post, URL, body: file_fixture("welcome.eml").read, - basic_auth: [ "actionmailbox", INGRESS_PASSWORD ], - headers: { "Content-Type" => "message/rfc822", "User-Agent" => /\AAction Mailbox Postfix relayer v\d+\./ } - end - - test "unsuccessfully relaying with invalid credentials" do - stub_request(:post, URL).to_return status: 401 - - result = @relayer.relay(file_fixture("welcome.eml").read) - assert_equal "4.7.0 Invalid credentials for Postfix ingress", result.output - assert_not result.success? - assert result.failure? - end - - test "unsuccessfully relaying due to an unspecified server error" do - stub_request(:post, URL).to_return status: 500 - - result = @relayer.relay(file_fixture("welcome.eml").read) - assert_equal "4.0.0 HTTP 500", result.output - assert_not result.success? - assert result.failure? - end - - test "unsuccessfully relaying due to a gateway timeout" do - stub_request(:post, URL).to_return status: 504 - - result = @relayer.relay(file_fixture("welcome.eml").read) - assert_equal "4.0.0 HTTP 504", result.output - assert_not result.success? - assert result.failure? - end - - test "unsuccessfully relaying due to ECONNRESET" do - stub_request(:post, URL).to_raise Errno::ECONNRESET.new - - result = @relayer.relay(file_fixture("welcome.eml").read) - assert_equal "4.4.2 Network error relaying to Postfix ingress: Connection reset by peer", result.output - assert_not result.success? - assert result.failure? - end - - test "unsuccessfully relaying due to connection failure" do - stub_request(:post, URL).to_raise SocketError.new("Failed to open TCP connection to example.com:443") - - result = @relayer.relay(file_fixture("welcome.eml").read) - assert_equal "4.4.2 Network error relaying to Postfix ingress: Failed to open TCP connection to example.com:443", result.output - assert_not result.success? - assert result.failure? - end - - test "unsuccessfully relaying due to client-side timeout" do - stub_request(:post, URL).to_timeout - - result = @relayer.relay(file_fixture("welcome.eml").read) - assert_equal "4.4.2 Timed out relaying to Postfix ingress", result.output - assert_not result.success? - assert result.failure? - end - - test "unsuccessfully relaying due to an unhandled exception" do - stub_request(:post, URL).to_raise StandardError.new("Something went wrong") - - result = @relayer.relay(file_fixture("welcome.eml").read) - assert_equal "4.0.0 Error relaying to Postfix ingress: Something went wrong", result.output - assert_not result.success? - assert result.failure? - end - end -end diff --git a/actionmailbox/test/unit/relayer_test.rb b/actionmailbox/test/unit/relayer_test.rb new file mode 100644 index 0000000000..fb2b48ea16 --- /dev/null +++ b/actionmailbox/test/unit/relayer_test.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +require_relative "../test_helper" + +require "action_mailbox/relayer" + +module ActionMailbox + class RelayerTest < ActiveSupport::TestCase + URL = "https://example.com/rails/action_mailbox/relay/inbound_emails" + INGRESS_PASSWORD = "secret" + + setup do + @relayer = ActionMailbox::Relayer.new(url: URL, password: INGRESS_PASSWORD) + end + + test "successfully relaying an email" do + stub_request(:post, URL).to_return status: 204 + + result = @relayer.relay(file_fixture("welcome.eml").read) + assert_equal "2.0.0", result.status_code + assert_equal "Successfully relayed message to ingress", result.message + assert result.success? + assert_not result.failure? + + assert_requested :post, URL, body: file_fixture("welcome.eml").read, + basic_auth: [ "actionmailbox", INGRESS_PASSWORD ], + headers: { "Content-Type" => "message/rfc822", "User-Agent" => /\AAction Mailbox relayer v\d+\./ } + end + + test "unsuccessfully relaying with invalid credentials" do + stub_request(:post, URL).to_return status: 401 + + result = @relayer.relay(file_fixture("welcome.eml").read) + assert_equal "4.7.0", result.status_code + assert_equal "Invalid credentials for ingress", result.message + assert_not result.success? + assert result.failure? + end + + test "unsuccessfully relaying due to an unspecified server error" do + stub_request(:post, URL).to_return status: 500 + + result = @relayer.relay(file_fixture("welcome.eml").read) + assert_equal "4.0.0", result.status_code + assert_equal "HTTP 500", result.message + assert_not result.success? + assert result.failure? + end + + test "unsuccessfully relaying due to a gateway timeout" do + stub_request(:post, URL).to_return status: 504 + + result = @relayer.relay(file_fixture("welcome.eml").read) + assert_equal "4.0.0", result.status_code + assert_equal "HTTP 504", result.message + assert_not result.success? + assert result.failure? + end + + test "unsuccessfully relaying due to ECONNRESET" do + stub_request(:post, URL).to_raise Errno::ECONNRESET.new + + result = @relayer.relay(file_fixture("welcome.eml").read) + assert_equal "4.4.2", result.status_code + assert_equal "Network error relaying to ingress: Connection reset by peer", result.message + assert_not result.success? + assert result.failure? + end + + test "unsuccessfully relaying due to connection failure" do + stub_request(:post, URL).to_raise SocketError.new("Failed to open TCP connection to example.com:443") + + result = @relayer.relay(file_fixture("welcome.eml").read) + assert_equal "4.4.2", result.status_code + assert_equal "Network error relaying to ingress: Failed to open TCP connection to example.com:443", result.message + assert_not result.success? + assert result.failure? + end + + test "unsuccessfully relaying due to client-side timeout" do + stub_request(:post, URL).to_timeout + + result = @relayer.relay(file_fixture("welcome.eml").read) + assert_equal "4.4.2", result.status_code + assert_equal "Timed out relaying to ingress", result.message + assert_not result.success? + assert result.failure? + end + + test "unsuccessfully relaying due to an unhandled exception" do + stub_request(:post, URL).to_raise StandardError.new("Something went wrong") + + result = @relayer.relay(file_fixture("welcome.eml").read) + assert_equal "4.0.0", result.status_code + assert_equal "Error relaying to ingress: Something went wrong", result.message + assert_not result.success? + assert result.failure? + end + end +end -- cgit v1.2.3 From 6c156d4f6e2bef5fe190d1426c1b867cd8931feb Mon Sep 17 00:00:00 2001 From: ikepon Date: Wed, 16 Jan 2019 15:40:15 +0900 Subject: Add 'null: false' to Action Mailbox table created_at and updated_at columns in Action Mailbox table aren't intended nullable. --- .../db/migrate/20180917164000_create_action_mailbox_tables.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'actionmailbox') diff --git a/actionmailbox/db/migrate/20180917164000_create_action_mailbox_tables.rb b/actionmailbox/db/migrate/20180917164000_create_action_mailbox_tables.rb index 0124b4b98f..8cf621d7e3 100644 --- a/actionmailbox/db/migrate/20180917164000_create_action_mailbox_tables.rb +++ b/actionmailbox/db/migrate/20180917164000_create_action_mailbox_tables.rb @@ -4,8 +4,8 @@ class CreateActionMailboxTables < ActiveRecord::Migration[6.0] t.integer :status, default: 0, null: false t.string :message_id - t.datetime :created_at, precision: 6 - t.datetime :updated_at, precision: 6 + t.datetime :created_at, precision: 6, null: false + t.datetime :updated_at, precision: 6, null: false end end end -- cgit v1.2.3 From 620ba4c12a6a075607c260fe30c95e51b51b878f Mon Sep 17 00:00:00 2001 From: colorbox Date: Wed, 16 Jan 2019 00:55:42 +0900 Subject: Fix document formatting on ActionMailbox [ci skip] Use `+` instead of backquote. --- .../ingresses/mailgun/inbound_emails_controller.rb | 2 +- .../app/jobs/action_mailbox/incineration_job.rb | 6 +++--- .../app/models/action_mailbox/inbound_email.rb | 14 ++++++------- .../action_mailbox/inbound_email/incineratable.rb | 6 +++--- .../inbound_email/incineratable/incineration.rb | 6 +++--- .../action_mailbox/inbound_email/message_id.rb | 12 +++++------ .../action_mailbox/inbound_email/routable.rb | 10 ++++----- actionmailbox/lib/action_mailbox/base.rb | 24 +++++++++++----------- actionmailbox/lib/action_mailbox/router/route.rb | 2 +- actionmailbox/lib/action_mailbox/routing.rb | 2 +- actionmailbox/lib/action_mailbox/test_helper.rb | 12 +++++------ 11 files changed, 48 insertions(+), 48 deletions(-) (limited to 'actionmailbox') diff --git a/actionmailbox/app/controllers/action_mailbox/ingresses/mailgun/inbound_emails_controller.rb b/actionmailbox/app/controllers/action_mailbox/ingresses/mailgun/inbound_emails_controller.rb index ebe83a9ec0..bf0fd562fe 100644 --- a/actionmailbox/app/controllers/action_mailbox/ingresses/mailgun/inbound_emails_controller.rb +++ b/actionmailbox/app/controllers/action_mailbox/ingresses/mailgun/inbound_emails_controller.rb @@ -38,7 +38,7 @@ module ActionMailbox # config.action_mailbox.ingress = :mailgun # # 3. {Configure Mailgun}[https://documentation.mailgun.com/en/latest/user_manual.html#receiving-forwarding-and-storing-messages] - # to forward inbound emails to `/rails/action_mailbox/mailgun/inbound_emails/mime`. + # to forward inbound emails to +/rails/action_mailbox/mailgun/inbound_emails/mime+. # # If your application lived at https://example.com, you would specify the fully-qualified URL # https://example.com/rails/action_mailbox/mailgun/inbound_emails/mime. diff --git a/actionmailbox/app/jobs/action_mailbox/incineration_job.rb b/actionmailbox/app/jobs/action_mailbox/incineration_job.rb index 224c9329a5..1579a3c7c8 100644 --- a/actionmailbox/app/jobs/action_mailbox/incineration_job.rb +++ b/actionmailbox/app/jobs/action_mailbox/incineration_job.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true module ActionMailbox - # 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. + # 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 + # Since this incineration is set for the future, it'll automatically ignore any InboundEmails # that have already been deleted and discard itself if so. class IncinerationJob < ActiveJob::Base queue_as { ActionMailbox.queues[:incineration] } diff --git a/actionmailbox/app/models/action_mailbox/inbound_email.rb b/actionmailbox/app/models/action_mailbox/inbound_email.rb index 3a8dfd163c..023de19ccc 100644 --- a/actionmailbox/app/models/action_mailbox/inbound_email.rb +++ b/actionmailbox/app/models/action_mailbox/inbound_email.rb @@ -3,22 +3,22 @@ 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: # # * 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. + # * 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 + # 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. + # 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: # diff --git a/actionmailbox/app/models/action_mailbox/inbound_email/incineratable.rb b/actionmailbox/app/models/action_mailbox/inbound_email/incineratable.rb index 825e300648..697331ede4 100644 --- a/actionmailbox/app/models/action_mailbox/inbound_email/incineratable.rb +++ b/actionmailbox/app/models/action_mailbox/inbound_email/incineratable.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -# 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`. +# 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/actionmailbox/app/models/action_mailbox/inbound_email/incineratable/incineration.rb b/actionmailbox/app/models/action_mailbox/inbound_email/incineratable/incineration.rb index 685f7fceb6..dabc83fae6 100644 --- a/actionmailbox/app/models/action_mailbox/inbound_email/incineratable/incineration.rb +++ b/actionmailbox/app/models/action_mailbox/inbound_email/incineratable/incineration.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true module ActionMailbox - # 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 + # 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). + # the +InboundEmail+ was processed after the +incinerate_after+ time). class InboundEmail::Incineratable::Incineration def initialize(inbound_email) @inbound_email = inbound_email diff --git a/actionmailbox/app/models/action_mailbox/inbound_email/message_id.rb b/actionmailbox/app/models/action_mailbox/inbound_email/message_id.rb index 2ad4525929..57b4a2445d 100644 --- a/actionmailbox/app/models/action_mailbox/inbound_email/message_id.rb +++ b/actionmailbox/app/models/action_mailbox/inbound_email/message_id.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true -# 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 +# 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`. +# using the approach from Mail::MessageIdField. module ActionMailbox::InboundEmail::MessageId extend ActiveSupport::Concern @@ -14,9 +14,9 @@ module ActionMailbox::InboundEmail::MessageId end class_methods do - # 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`. + # 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! options.merge(message_id: extract_message_id(source)) do |inbound_email| inbound_email.raw_email.attach io: StringIO.new(source), filename: "message.eml", content_type: "message/rfc822" diff --git a/actionmailbox/app/models/action_mailbox/inbound_email/routable.rb b/actionmailbox/app/models/action_mailbox/inbound_email/routable.rb index 58d67eb20c..39565df166 100644 --- a/actionmailbox/app/models/action_mailbox/inbound_email/routable.rb +++ b/actionmailbox/app/models/action_mailbox/inbound_email/routable.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -# 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. +# 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, +# 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 @@ -12,12 +12,12 @@ module ActionMailbox::InboundEmail::Routable after_create_commit :route_later, if: :pending? end - # Enqueue a `RoutingJob` for this `InboundEmail`. + # 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`. + # Route this +InboundEmail+ using the routing rules declared on the +ApplicationMailbox+. def route ApplicationMailbox.route self end diff --git a/actionmailbox/lib/action_mailbox/base.rb b/actionmailbox/lib/action_mailbox/base.rb index 4ac594b9f8..ff8587acd1 100644 --- a/actionmailbox/lib/action_mailbox/base.rb +++ b/actionmailbox/lib/action_mailbox/base.rb @@ -7,7 +7,7 @@ require "action_mailbox/routing" module ActionMailbox # 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 + # +ApplicationMailbox+ instead, as that's where the app-specific routing is configured. This routing # is specified in the following ways: # # class ApplicationMailbox < ActionMailbox::Base @@ -27,15 +27,15 @@ module ActionMailbox # routing :all => :backstop # end # - # Application mailboxes need to overwrite 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 ensure certain preconditions to processing are fulfilled - # using `before_processing` callbacks. + # Application mailboxes need to overwrite 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 ensure 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, + # 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 + # 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 @@ -50,12 +50,12 @@ module ActionMailbox # 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`. + # 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 `Rescuable` approach: + # Exceptions can be handled at the class level using the familiar +Rescuable+ approach: # # class ForwardsMailbox < ApplicationMailbox # rescue_from(ApplicationSpecificVerificationError) { bounced! } diff --git a/actionmailbox/lib/action_mailbox/router/route.rb b/actionmailbox/lib/action_mailbox/router/route.rb index b681eb7ea8..7e98e83382 100644 --- a/actionmailbox/lib/action_mailbox/router/route.rb +++ b/actionmailbox/lib/action_mailbox/router/route.rb @@ -2,7 +2,7 @@ module ActionMailbox # 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. class Router::Route attr_reader :address, :mailbox_name diff --git a/actionmailbox/lib/action_mailbox/routing.rb b/actionmailbox/lib/action_mailbox/routing.rb index 1ea96c8a9d..58462a44c6 100644 --- a/actionmailbox/lib/action_mailbox/routing.rb +++ b/actionmailbox/lib/action_mailbox/routing.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module ActionMailbox - # See `ActionMailbox::Base` for how to specify routing. + # See +ActionMailbox::Base+ for how to specify routing. module Routing extend ActiveSupport::Concern diff --git a/actionmailbox/lib/action_mailbox/test_helper.rb b/actionmailbox/lib/action_mailbox/test_helper.rb index 02c52fb779..0ec9152844 100644 --- a/actionmailbox/lib/action_mailbox/test_helper.rb +++ b/actionmailbox/lib/action_mailbox/test_helper.rb @@ -4,38 +4,38 @@ require "mail" module ActionMailbox module TestHelper - # Create an `InboundEmail` record using an eml fixture in the format of message/rfc822 + # 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_from_source file_fixture(fixture_name).read, status: status end - # Create an `InboundEmail` by specifying it using `Mail.new` options. Example: + # Create an +InboundEmail+ by specifying it using +Mail.new+ options. Example: # # create_inbound_email_from_mail(from: "david@loudthinking.com", subject: "Hello!") def create_inbound_email_from_mail(status: :processing, **mail_options) create_inbound_email_from_source Mail.new(mail_options).to_s, status: status end - # Create an `InboundEmail` using the raw rfc822 `source` as text. + # Create an +InboundEmail+ using the raw rfc822 +source+ as text. def create_inbound_email_from_source(source, status: :processing) ActionMailbox::InboundEmail.create_and_extract_message_id! source, status: status end - # Create an `InboundEmail` from fixture using the same arguments as `create_inbound_email_from_fixture` + # Create an +InboundEmail+ from fixture using the same arguments as +create_inbound_email_from_fixture+ # and immediately route it to processing. def receive_inbound_email_from_fixture(*args) create_inbound_email_from_fixture(*args).tap(&:route) end - # Create an `InboundEmail` from fixture using the same arguments as `create_inbound_email_from_mail` + # Create an +InboundEmail+ from fixture using the same arguments as +create_inbound_email_from_mail+ # and immediately route it to processing. def receive_inbound_email_from_mail(**kwargs) create_inbound_email_from_mail(**kwargs).tap(&:route) end - # Create an `InboundEmail` from fixture using the same arguments as `create_inbound_email_from_source` + # Create an +InboundEmail+ from fixture using the same arguments as +create_inbound_email_from_source+ # and immediately route it to processing. def receive_inbound_email_from_source(**kwargs) create_inbound_email_from_source(**kwargs).tap(&:route) -- cgit v1.2.3