aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGeorge Claghorn <george.claghorn@gmail.com>2019-01-09 19:07:11 -0500
committerGitHub <noreply@github.com>2019-01-09 19:07:11 -0500
commit58d52079c1e96a81582b7f3cb94f829eaddad4b5 (patch)
tree60d00428f4d70664672a54959de713617191377c
parentff807f823b869d3491935a096183ee2bebd58e7b (diff)
parentb77d2d9a032b64c7819bf950194c715a3404da37 (diff)
downloadrails-58d52079c1e96a81582b7f3cb94f829eaddad4b5.tar.gz
rails-58d52079c1e96a81582b7f3cb94f829eaddad4b5.tar.bz2
rails-58d52079c1e96a81582b7f3cb94f829eaddad4b5.zip
Merge pull request #34907 from wildbit/actionmailbox-postmark
Add Postmark ingress support to ActionMailbox
-rw-r--r--actionmailbox/app/controllers/action_mailbox/ingresses/postmark/inbound_emails_controller.rb62
-rw-r--r--actionmailbox/config/routes.rb1
-rw-r--r--actionmailbox/test/controllers/ingresses/postmark/inbound_emails_controller_test.rb55
-rw-r--r--guides/source/action_mailbox_basics.md36
-rw-r--r--railties/test/application/rake/routes_test.rb1
-rw-r--r--railties/test/commands/routes_test.rb75
6 files changed, 197 insertions, 33 deletions
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:
+ #
+ # - <tt>204 No Content</tt> if an inbound email is successfully recorded and enqueued for routing to the appropriate mailbox
+ # - <tt>401 Unauthorized</tt> if the request's signature could not be validated
+ # - <tt>404 Not Found</tt> if Action Mailbox is not configured to accept inbound emails from Postmark
+ # - <tt>422 Unprocessable Entity</tt> if the request is missing the required +RawEmail+ parameter
+ # - <tt>500 Server Error</tt> 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 <tt>rails credentials:edit</tt> 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 <tt>https://example.com</tt>, 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
diff --git a/guides/source/action_mailbox_basics.md b/guides/source/action_mailbox_basics.md
index eb8a14b4d2..f2c8a0a6b7 100644
--- a/guides/source/action_mailbox_basics.md
+++ b/guides/source/action_mailbox_basics.md
@@ -155,6 +155,42 @@ would look like this:
$ URL=https://example.com/rails/action_mailbox/postfix/inbound_emails INGRESS_PASSWORD=... rails action_mailbox:ingress:postfix
```
+### Postmark
+
+Tell Action Mailbox to accept emails from Postmark:
+
+```ruby
+# config/environments/production.rb
+config.action_mailbox.ingress = :postmark
+```
+
+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:
+
+```yaml
+action_mailbox:
+ ingress_password: ...
+```
+
+Alternatively, provide the password in the `RAILS_INBOUND_EMAIL_PASSWORD`
+environment variable.
+
+[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.
+
### SendGrid
Tell Action Mailbox to accept emails from SendGrid:
diff --git a/railties/test/application/rake/routes_test.rb b/railties/test/application/rake/routes_test.rb
index 933c735078..bbcda7c258 100644
--- a/railties/test/application/rake/routes_test.rb
+++ b/railties/test/application/rake/routes_test.rb
@@ -23,6 +23,7 @@ module ApplicationTests
rails_amazon_inbound_emails POST /rails/action_mailbox/amazon/inbound_emails(.:format) action_mailbox/ingresses/amazon/inbound_emails#create
rails_mandrill_inbound_emails POST /rails/action_mailbox/mandrill/inbound_emails(.:format) action_mailbox/ingresses/mandrill/inbound_emails#create
rails_postfix_inbound_emails POST /rails/action_mailbox/postfix/inbound_emails(.:format) action_mailbox/ingresses/postfix/inbound_emails#create
+ rails_postmark_inbound_emails POST /rails/action_mailbox/postmark/inbound_emails(.:format) action_mailbox/ingresses/postmark/inbound_emails#create
rails_sendgrid_inbound_emails POST /rails/action_mailbox/sendgrid/inbound_emails(.:format) action_mailbox/ingresses/sendgrid/inbound_emails#create
rails_mailgun_inbound_emails POST /rails/action_mailbox/mailgun/inbound_emails/mime(.:format) action_mailbox/ingresses/mailgun/inbound_emails#create
rails_conductor_inbound_emails GET /rails/conductor/action_mailbox/inbound_emails(.:format) rails/conductor/action_mailbox/inbound_emails#index
diff --git a/railties/test/commands/routes_test.rb b/railties/test/commands/routes_test.rb
index a43a6d32b9..f77214ac2a 100644
--- a/railties/test/commands/routes_test.rb
+++ b/railties/test/commands/routes_test.rb
@@ -18,15 +18,16 @@ class Rails::Command::RoutesTest < ActiveSupport::TestCase
RUBY
assert_equal <<~OUTPUT, run_routes_command([ "-c", "PostController" ])
- Prefix Verb URI Pattern Controller#Action
- new_post GET /post/new(.:format) posts#new
- edit_post GET /post/edit(.:format) posts#edit
- post GET /post(.:format) posts#show
- PATCH /post(.:format) posts#update
- PUT /post(.:format) posts#update
- DELETE /post(.:format) posts#destroy
- POST /post(.:format) posts#create
- rails_postfix_inbound_emails POST /rails/action_mailbox/postfix/inbound_emails(.:format) action_mailbox/ingresses/postfix/inbound_emails#create
+ Prefix Verb URI Pattern Controller#Action
+ new_post GET /post/new(.:format) posts#new
+ edit_post GET /post/edit(.:format) posts#edit
+ post GET /post(.:format) posts#show
+ PATCH /post(.:format) posts#update
+ PUT /post(.:format) posts#update
+ DELETE /post(.:format) posts#destroy
+ POST /post(.:format) posts#create
+ rails_postfix_inbound_emails POST /rails/action_mailbox/postfix/inbound_emails(.:format) action_mailbox/ingresses/postfix/inbound_emails#create
+ rails_postmark_inbound_emails POST /rails/action_mailbox/postmark/inbound_emails(.:format) action_mailbox/ingresses/postmark/inbound_emails#create
OUTPUT
assert_equal <<~OUTPUT, run_routes_command([ "-c", "UserPermissionController" ])
@@ -65,6 +66,7 @@ class Rails::Command::RoutesTest < ActiveSupport::TestCase
rails_amazon_inbound_emails POST /rails/action_mailbox/amazon/inbound_emails(.:format) action_mailbox/ingresses/amazon/inbound_emails#create
rails_mandrill_inbound_emails POST /rails/action_mailbox/mandrill/inbound_emails(.:format) action_mailbox/ingresses/mandrill/inbound_emails#create
rails_postfix_inbound_emails POST /rails/action_mailbox/postfix/inbound_emails(.:format) action_mailbox/ingresses/postfix/inbound_emails#create
+ rails_postmark_inbound_emails POST /rails/action_mailbox/postmark/inbound_emails(.:format) action_mailbox/ingresses/postmark/inbound_emails#create
rails_sendgrid_inbound_emails POST /rails/action_mailbox/sendgrid/inbound_emails(.:format) action_mailbox/ingresses/sendgrid/inbound_emails#create
rails_mailgun_inbound_emails POST /rails/action_mailbox/mailgun/inbound_emails/mime(.:format) action_mailbox/ingresses/mailgun/inbound_emails#create
POST /rails/conductor/action_mailbox/inbound_emails(.:format) rails/conductor/action_mailbox/inbound_emails#create
@@ -131,15 +133,16 @@ class Rails::Command::RoutesTest < ActiveSupport::TestCase
OUTPUT
assert_equal <<~OUTPUT, run_routes_command([ "-c", "PostController" ])
- Prefix Verb URI Pattern Controller#Action
- new_admin_post GET /admin/post/new(.:format) admin/posts#new
- edit_admin_post GET /admin/post/edit(.:format) admin/posts#edit
- admin_post GET /admin/post(.:format) admin/posts#show
- PATCH /admin/post(.:format) admin/posts#update
- PUT /admin/post(.:format) admin/posts#update
- DELETE /admin/post(.:format) admin/posts#destroy
- POST /admin/post(.:format) admin/posts#create
- rails_postfix_inbound_emails POST /rails/action_mailbox/postfix/inbound_emails(.:format) action_mailbox/ingresses/postfix/inbound_emails#create
+ Prefix Verb URI Pattern Controller#Action
+ new_admin_post GET /admin/post/new(.:format) admin/posts#new
+ edit_admin_post GET /admin/post/edit(.:format) admin/posts#edit
+ admin_post GET /admin/post(.:format) admin/posts#show
+ PATCH /admin/post(.:format) admin/posts#update
+ PUT /admin/post(.:format) admin/posts#update
+ DELETE /admin/post(.:format) admin/posts#destroy
+ POST /admin/post(.:format) admin/posts#create
+ rails_postfix_inbound_emails POST /rails/action_mailbox/postfix/inbound_emails(.:format) action_mailbox/ingresses/postfix/inbound_emails#create
+ rails_postmark_inbound_emails POST /rails/action_mailbox/postmark/inbound_emails(.:format) action_mailbox/ingresses/postmark/inbound_emails#create
OUTPUT
expected_permission_output = <<~OUTPUT
@@ -168,6 +171,7 @@ class Rails::Command::RoutesTest < ActiveSupport::TestCase
rails_amazon_inbound_emails POST /rails/action_mailbox/amazon/inbound_emails(.:format) action_mailbox/ingresses/amazon/inbound_emails#create
rails_mandrill_inbound_emails POST /rails/action_mailbox/mandrill/inbound_emails(.:format) action_mailbox/ingresses/mandrill/inbound_emails#create
rails_postfix_inbound_emails POST /rails/action_mailbox/postfix/inbound_emails(.:format) action_mailbox/ingresses/postfix/inbound_emails#create
+ rails_postmark_inbound_emails POST /rails/action_mailbox/postmark/inbound_emails(.:format) action_mailbox/ingresses/postmark/inbound_emails#create
rails_sendgrid_inbound_emails POST /rails/action_mailbox/sendgrid/inbound_emails(.:format) action_mailbox/ingresses/sendgrid/inbound_emails#create
rails_mailgun_inbound_emails POST /rails/action_mailbox/mailgun/inbound_emails/mime(.:format) action_mailbox/ingresses/mailgun/inbound_emails#create
rails_conductor_inbound_emails GET /rails/conductor/action_mailbox/inbound_emails(.:format) rails/conductor/action_mailbox/inbound_emails#index
@@ -220,81 +224,86 @@ class Rails::Command::RoutesTest < ActiveSupport::TestCase
URI | /rails/action_mailbox/postfix/inbound_emails(.:format)
Controller#Action | action_mailbox/ingresses/postfix/inbound_emails#create
--[ Route 5 ]--------------
+ Prefix | rails_postmark_inbound_emails
+ Verb | POST
+ URI | /rails/action_mailbox/postmark/inbound_emails(.:format)
+ Controller#Action | action_mailbox/ingresses/postmark/inbound_emails#create
+ --[ Route 6 ]--------------
Prefix | rails_sendgrid_inbound_emails
Verb | POST
URI | /rails/action_mailbox/sendgrid/inbound_emails(.:format)
Controller#Action | action_mailbox/ingresses/sendgrid/inbound_emails#create
- --[ Route 6 ]--------------
+ --[ Route 7 ]--------------
Prefix | rails_mailgun_inbound_emails
Verb | POST
URI | /rails/action_mailbox/mailgun/inbound_emails/mime(.:format)
Controller#Action | action_mailbox/ingresses/mailgun/inbound_emails#create
- --[ Route 7 ]--------------
+ --[ Route 8 ]--------------
Prefix | rails_conductor_inbound_emails
Verb | GET
URI | /rails/conductor/action_mailbox/inbound_emails(.:format)
Controller#Action | rails/conductor/action_mailbox/inbound_emails#index
- --[ Route 8 ]--------------
+ --[ Route 9 ]--------------
Prefix |
Verb | POST
URI | /rails/conductor/action_mailbox/inbound_emails(.:format)
Controller#Action | rails/conductor/action_mailbox/inbound_emails#create
- --[ Route 9 ]--------------
+ --[ Route 10 ]-------------
Prefix | new_rails_conductor_inbound_email
Verb | GET
URI | /rails/conductor/action_mailbox/inbound_emails/new(.:format)
Controller#Action | rails/conductor/action_mailbox/inbound_emails#new
- --[ Route 10 ]-------------
+ --[ Route 11 ]-------------
Prefix | edit_rails_conductor_inbound_email
Verb | GET
URI | /rails/conductor/action_mailbox/inbound_emails/:id/edit(.:format)
Controller#Action | rails/conductor/action_mailbox/inbound_emails#edit
- --[ Route 11 ]-------------
+ --[ Route 12 ]-------------
Prefix | rails_conductor_inbound_email
Verb | GET
URI | /rails/conductor/action_mailbox/inbound_emails/:id(.:format)
Controller#Action | rails/conductor/action_mailbox/inbound_emails#show
- --[ Route 12 ]-------------
+ --[ Route 13 ]-------------
Prefix |
Verb | PATCH
URI | /rails/conductor/action_mailbox/inbound_emails/:id(.:format)
Controller#Action | rails/conductor/action_mailbox/inbound_emails#update
- --[ Route 13 ]-------------
+ --[ Route 14 ]-------------
Prefix |
Verb | PUT
URI | /rails/conductor/action_mailbox/inbound_emails/:id(.:format)
Controller#Action | rails/conductor/action_mailbox/inbound_emails#update
- --[ Route 14 ]-------------
+ --[ Route 15 ]-------------
Prefix |
Verb | DELETE
URI | /rails/conductor/action_mailbox/inbound_emails/:id(.:format)
Controller#Action | rails/conductor/action_mailbox/inbound_emails#destroy
- --[ Route 15 ]-------------
+ --[ Route 16 ]-------------
Prefix | rails_conductor_inbound_email_reroute
Verb | POST
URI | /rails/conductor/action_mailbox/:inbound_email_id/reroute(.:format)
Controller#Action | rails/conductor/action_mailbox/reroutes#create
- --[ Route 16 ]-------------
+ --[ Route 17 ]-------------
Prefix | rails_service_blob
Verb | GET
URI | /rails/active_storage/blobs/:signed_id/*filename(.:format)
Controller#Action | active_storage/blobs#show
- --[ Route 17 ]-------------
+ --[ Route 18 ]-------------
Prefix | rails_blob_representation
Verb | GET
URI | /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format)
Controller#Action | active_storage/representations#show
- --[ Route 18 ]-------------
+ --[ Route 19 ]-------------
Prefix | rails_disk_service
Verb | GET
URI | /rails/active_storage/disk/:encoded_key/*filename(.:format)
Controller#Action | active_storage/disk#show
- --[ Route 19 ]-------------
+ --[ Route 20 ]-------------
Prefix | update_rails_disk_service
Verb | PUT
URI | /rails/active_storage/disk/:encoded_token(.:format)
Controller#Action | active_storage/disk#update
- --[ Route 20 ]-------------
+ --[ Route 21 ]-------------
Prefix | rails_direct_uploads
Verb | POST
URI | /rails/active_storage/direct_uploads(.:format)