Merge pull request #34907 from wildbit/actionmailbox-postmark
Add Postmark ingress support to ActionMailbox
This commit is contained in:
commit
58d52079c1
62
actionmailbox/app/controllers/action_mailbox/ingresses/postmark/inbound_emails_controller.rb
Normal file
62
actionmailbox/app/controllers/action_mailbox/ingresses/postmark/inbound_emails_controller.rb
Normal file
@ -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
|
@ -5,6 +5,7 @@
|
||||
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.
|
||||
|
@ -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
|
@ -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:
|
||||
|
@ -23,6 +23,7 @@ class RakeRoutesTest < 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
|
||||
|
@ -27,6 +27,7 @@ class Rails::Command::RoutesTest < ActiveSupport::TestCase
|
||||
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
|
||||
@ -140,6 +142,7 @@ class Rails::Command::RoutesTest < ActiveSupport::TestCase
|
||||
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)
|
||||
|
Loading…
Reference in New Issue
Block a user