actionmailbox 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +159 -0
- data/LICENSE +21 -0
- data/README.md +278 -0
- data/Rakefile +27 -0
- data/actionmailbox.gemspec +27 -0
- data/app/controllers/action_mailbox/base_controller.rb +43 -0
- data/app/controllers/action_mailbox/ingresses/amazon/inbound_emails_controller.rb +50 -0
- data/app/controllers/action_mailbox/ingresses/mailgun/inbound_emails_controller.rb +99 -0
- data/app/controllers/action_mailbox/ingresses/mandrill/inbound_emails_controller.rb +78 -0
- data/app/controllers/action_mailbox/ingresses/postfix/inbound_emails_controller.rb +55 -0
- data/app/controllers/action_mailbox/ingresses/sendgrid/inbound_emails_controller.rb +50 -0
- data/app/controllers/rails/conductor/action_mailbox/inbound_emails_controller.rb +27 -0
- data/app/controllers/rails/conductor/action_mailbox/reroutes_controller.rb +15 -0
- data/app/controllers/rails/conductor/base_controller.rb +10 -0
- data/app/jobs/action_mailbox/incineration_job.rb +18 -0
- data/app/jobs/action_mailbox/routing_job.rb +9 -0
- data/app/models/action_mailbox/inbound_email.rb +43 -0
- data/app/models/action_mailbox/inbound_email/incineratable.rb +18 -0
- data/app/models/action_mailbox/inbound_email/incineratable/incineration.rb +22 -0
- data/app/models/action_mailbox/inbound_email/message_id.rb +36 -0
- data/app/models/action_mailbox/inbound_email/routable.rb +22 -0
- data/app/views/layouts/rails/conductor.html.erb +7 -0
- data/app/views/rails/conductor/action_mailbox/inbound_emails/index.html.erb +15 -0
- data/app/views/rails/conductor/action_mailbox/inbound_emails/new.html.erb +42 -0
- data/app/views/rails/conductor/action_mailbox/inbound_emails/show.html.erb +15 -0
- data/bin/test +5 -0
- data/config/routes.rb +19 -0
- data/db/migrate/20180917164000_create_action_mailbox_tables.rb +11 -0
- data/lib/action_mailbox.rb +15 -0
- data/lib/action_mailbox/base.rb +111 -0
- data/lib/action_mailbox/callbacks.rb +32 -0
- data/lib/action_mailbox/engine.rb +34 -0
- data/lib/action_mailbox/mail_ext.rb +4 -0
- data/lib/action_mailbox/mail_ext/address_equality.rb +5 -0
- data/lib/action_mailbox/mail_ext/address_wrapping.rb +5 -0
- data/lib/action_mailbox/mail_ext/addresses.rb +25 -0
- data/lib/action_mailbox/mail_ext/from_source.rb +5 -0
- data/lib/action_mailbox/mail_ext/recipients.rb +5 -0
- data/lib/action_mailbox/postfix_relayer.rb +67 -0
- data/lib/action_mailbox/router.rb +38 -0
- data/lib/action_mailbox/router/route.rb +38 -0
- data/lib/action_mailbox/routing.rb +20 -0
- data/lib/action_mailbox/test_case.rb +8 -0
- data/lib/action_mailbox/test_helper.rb +42 -0
- data/lib/action_mailbox/version.rb +3 -0
- data/lib/tasks/ingress.rake +24 -0
- data/lib/tasks/install.rake +20 -0
- data/lib/templates/installer.rb +4 -0
- data/lib/templates/mailboxes/application_mailbox.rb +3 -0
- data/test/controllers/ingresses/amazon/inbound_emails_controller_test.rb +20 -0
- data/test/controllers/ingresses/mailgun/inbound_emails_controller_test.rb +89 -0
- data/test/controllers/ingresses/mandrill/inbound_emails_controller_test.rb +58 -0
- data/test/controllers/ingresses/postfix/inbound_emails_controller_test.rb +54 -0
- data/test/controllers/ingresses/sendgrid/inbound_emails_controller_test.rb +44 -0
- data/test/dummy/.babelrc +18 -0
- data/test/dummy/.gitignore +3 -0
- data/test/dummy/.postcssrc.yml +3 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/config/manifest.js +3 -0
- data/test/dummy/app/assets/images/.keep +0 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/assets/stylesheets/scaffold.css +80 -0
- data/test/dummy/app/channels/application_cable/channel.rb +4 -0
- data/test/dummy/app/channels/application_cable/connection.rb +4 -0
- data/test/dummy/app/controllers/application_controller.rb +2 -0
- data/test/dummy/app/controllers/concerns/.keep +0 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/javascript/packs/application.js +0 -0
- data/test/dummy/app/jobs/application_job.rb +2 -0
- data/test/dummy/app/mailboxes/application_mailbox.rb +2 -0
- data/test/dummy/app/mailboxes/messages_mailbox.rb +4 -0
- data/test/dummy/app/mailers/application_mailer.rb +4 -0
- data/test/dummy/app/models/application_record.rb +3 -0
- data/test/dummy/app/models/concerns/.keep +0 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
- data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/bin/setup +36 -0
- data/test/dummy/bin/update +31 -0
- data/test/dummy/bin/yarn +11 -0
- data/test/dummy/config.ru +5 -0
- data/test/dummy/config/application.rb +19 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/cable.yml +10 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +63 -0
- data/test/dummy/config/environments/production.rb +96 -0
- data/test/dummy/config/environments/test.rb +46 -0
- data/test/dummy/config/initializers/application_controller_renderer.rb +8 -0
- data/test/dummy/config/initializers/assets.rb +14 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/content_security_policy.rb +22 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +33 -0
- data/test/dummy/config/puma.rb +34 -0
- data/test/dummy/config/routes.rb +4 -0
- data/test/dummy/config/spring.rb +6 -0
- data/test/dummy/config/storage.yml +35 -0
- data/test/dummy/config/webpack/development.js +3 -0
- data/test/dummy/config/webpack/environment.js +3 -0
- data/test/dummy/config/webpack/production.js +3 -0
- data/test/dummy/config/webpack/test.js +3 -0
- data/test/dummy/config/webpacker.yml +65 -0
- data/test/dummy/db/migrate/20180208205311_create_action_mailroom_tables.rb +11 -0
- data/test/dummy/db/migrate/20180212164506_create_active_storage_tables.active_storage.rb +26 -0
- data/test/dummy/db/schema.rb +43 -0
- data/test/dummy/lib/assets/.keep +0 -0
- data/test/dummy/log/.keep +0 -0
- data/test/dummy/package.json +11 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/apple-touch-icon-precomposed.png +0 -0
- data/test/dummy/public/apple-touch-icon.png +0 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/storage/.keep +0 -0
- data/test/dummy/yarn.lock +6071 -0
- data/test/fixtures/files/welcome.eml +631 -0
- data/test/jobs/incineration_job_test.rb +17 -0
- data/test/test_helper.rb +54 -0
- data/test/unit/inbound_email/incineration_test.rb +45 -0
- data/test/unit/inbound_email/message_id_test.rb +13 -0
- data/test/unit/inbound_email_test.rb +13 -0
- data/test/unit/mail_ext/address_equality_test.rb +9 -0
- data/test/unit/mail_ext/address_wrapping_test.rb +11 -0
- data/test/unit/mail_ext/recipients_test.rb +33 -0
- data/test/unit/mailbox/bouncing_test.rb +29 -0
- data/test/unit/mailbox/callbacks_test.rb +75 -0
- data/test/unit/mailbox/routing_test.rb +30 -0
- data/test/unit/mailbox/state_test.rb +49 -0
- data/test/unit/postfix_relayer_test.rb +90 -0
- data/test/unit/router_test.rb +137 -0
- metadata +355 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require "active_support/callbacks"
|
|
2
|
+
|
|
3
|
+
module ActionMailbox
|
|
4
|
+
# Defines the callbacks related to processing.
|
|
5
|
+
module Callbacks
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
include ActiveSupport::Callbacks
|
|
8
|
+
|
|
9
|
+
TERMINATOR = ->(mailbox, chain) do
|
|
10
|
+
chain.call
|
|
11
|
+
mailbox.finished_processing?
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
included do
|
|
15
|
+
define_callbacks :process, terminator: TERMINATOR, skip_after_callbacks_if_terminated: true
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
module ClassMethods
|
|
19
|
+
def before_processing(*methods, &block)
|
|
20
|
+
set_callback(:process, :before, *methods, &block)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def after_processing(*methods, &block)
|
|
24
|
+
set_callback(:process, :after, *methods, &block)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def around_processing(*methods, &block)
|
|
28
|
+
set_callback(:process, :around, *methods, &block)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require "rails/engine"
|
|
2
|
+
|
|
3
|
+
module ActionMailbox
|
|
4
|
+
class Engine < Rails::Engine
|
|
5
|
+
isolate_namespace ActionMailbox
|
|
6
|
+
config.eager_load_namespaces << ActionMailbox
|
|
7
|
+
|
|
8
|
+
config.action_mailbox = ActiveSupport::OrderedOptions.new
|
|
9
|
+
config.action_mailbox.incinerate_after = 30.days
|
|
10
|
+
|
|
11
|
+
config.action_mailbox.queues = ActiveSupport::InheritableOptions.new \
|
|
12
|
+
incineration: :action_mailbox_incineration, routing: :action_mailbox_routing
|
|
13
|
+
|
|
14
|
+
initializer "action_mailbox.config" do
|
|
15
|
+
config.after_initialize do |app|
|
|
16
|
+
ActionMailbox.logger = app.config.action_mailbox.logger || Rails.logger
|
|
17
|
+
ActionMailbox.incinerate_after = app.config.action_mailbox.incinerate_after || 30.days
|
|
18
|
+
ActionMailbox.queues = app.config.action_mailbox.queues || {}
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
initializer "action_mailbox.ingress" do
|
|
23
|
+
config.after_initialize do |app|
|
|
24
|
+
if ActionMailbox.ingress = app.config.action_mailbox.ingress.presence
|
|
25
|
+
config.to_prepare do
|
|
26
|
+
if ingress_controller_class = "ActionMailbox::Ingresses::#{ActionMailbox.ingress.to_s.classify}::InboundEmailsController".safe_constantize
|
|
27
|
+
ingress_controller_class.prepare
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
class Mail::Message
|
|
2
|
+
def from_address
|
|
3
|
+
header[:from]&.address_list&.addresses&.first
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def recipients_addresses
|
|
7
|
+
to_addresses + cc_addresses + bcc_addresses + x_original_to_addresses
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def to_addresses
|
|
11
|
+
Array(header[:to]&.address_list&.addresses)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def cc_addresses
|
|
15
|
+
Array(header[:cc]&.address_list&.addresses)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def bcc_addresses
|
|
19
|
+
Array(header[:bcc]&.address_list&.addresses)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def x_original_to_addresses
|
|
23
|
+
Array(header[:x_original_to]).collect { |header| Mail::Address.new header.to_s }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "action_mailbox/version"
|
|
4
|
+
require "net/http"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
module ActionMailbox
|
|
8
|
+
class PostfixRelayer
|
|
9
|
+
class Result < Struct.new(:output)
|
|
10
|
+
def success?
|
|
11
|
+
!failure?
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def failure?
|
|
15
|
+
output.match?(/\A[45]\.\d{1,3}\.\d{1,3}(\s|\z)/)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
CONTENT_TYPE = "message/rfc822"
|
|
20
|
+
USER_AGENT = "Action Mailbox Postfix relayer v#{ActionMailbox::VERSION}"
|
|
21
|
+
|
|
22
|
+
attr_reader :uri, :username, :password
|
|
23
|
+
|
|
24
|
+
def initialize(url:, username: "actionmailbox", password:)
|
|
25
|
+
@uri, @username, @password = URI(url), username, password
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def relay(source)
|
|
29
|
+
case response = post(source)
|
|
30
|
+
when Net::HTTPSuccess
|
|
31
|
+
Result.new "2.0.0 Successfully relayed message to Postfix ingress"
|
|
32
|
+
when Net::HTTPUnauthorized
|
|
33
|
+
Result.new "4.7.0 Invalid credentials for Postfix ingress"
|
|
34
|
+
else
|
|
35
|
+
Result.new "4.0.0 HTTP #{response.code}"
|
|
36
|
+
end
|
|
37
|
+
rescue IOError, SocketError, SystemCallError => error
|
|
38
|
+
Result.new "4.4.2 Network error relaying to Postfix ingress: #{error.message}"
|
|
39
|
+
rescue Timeout::Error
|
|
40
|
+
Result.new "4.4.2 Timed out relaying to Postfix ingress"
|
|
41
|
+
rescue => error
|
|
42
|
+
Result.new "4.0.0 Error relaying to Postfix ingress: #{error.message}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
def post(source)
|
|
47
|
+
client.post uri, source,
|
|
48
|
+
"Content-Type" => CONTENT_TYPE,
|
|
49
|
+
"User-Agent" => USER_AGENT,
|
|
50
|
+
"Authorization" => "Basic #{Base64.strict_encode64(username + ":" + password)}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def client
|
|
54
|
+
@client ||= Net::HTTP.new(uri.host, uri.port).tap do |connection|
|
|
55
|
+
if uri.scheme == "https"
|
|
56
|
+
require "openssl"
|
|
57
|
+
|
|
58
|
+
connection.use_ssl = true
|
|
59
|
+
connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
connection.open_timeout = 1
|
|
63
|
+
connection.read_timeout = 10
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Encapsulates the routes that live on the ApplicationMailbox and performs the actual routing when
|
|
2
|
+
# an inbound_email is received.
|
|
3
|
+
class ActionMailbox::Router
|
|
4
|
+
class RoutingError < StandardError; end
|
|
5
|
+
|
|
6
|
+
def initialize
|
|
7
|
+
@routes = []
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def add_routes(routes)
|
|
11
|
+
routes.each do |(address, mailbox_name)|
|
|
12
|
+
add_route address, to: mailbox_name
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def add_route(address, to:)
|
|
17
|
+
routes.append Route.new(address, to: to)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def route(inbound_email)
|
|
21
|
+
if mailbox = match_to_mailbox(inbound_email)
|
|
22
|
+
mailbox.receive(inbound_email)
|
|
23
|
+
else
|
|
24
|
+
inbound_email.bounced!
|
|
25
|
+
|
|
26
|
+
raise RoutingError
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
attr_reader :routes
|
|
32
|
+
|
|
33
|
+
def match_to_mailbox(inbound_email)
|
|
34
|
+
routes.detect { |route| route.match?(inbound_email) }.try(:mailbox_class)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
require "action_mailbox/router/route"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Encapsulates a route, which can then be matched against an inbound_email and provide a lookup of the matching
|
|
2
|
+
# mailbox class. See examples for the different route addresses and how to use them in the `ActionMailbox::Base`
|
|
3
|
+
# documentation.
|
|
4
|
+
class ActionMailbox::Router::Route
|
|
5
|
+
attr_reader :address, :mailbox_name
|
|
6
|
+
|
|
7
|
+
def initialize(address, to:)
|
|
8
|
+
@address, @mailbox_name = address, to
|
|
9
|
+
|
|
10
|
+
ensure_valid_address
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def match?(inbound_email)
|
|
14
|
+
case address
|
|
15
|
+
when :all
|
|
16
|
+
true
|
|
17
|
+
when String
|
|
18
|
+
inbound_email.mail.recipients.any? { |recipient| address.casecmp?(recipient) }
|
|
19
|
+
when Regexp
|
|
20
|
+
inbound_email.mail.recipients.any? { |recipient| address.match?(recipient) }
|
|
21
|
+
when Proc
|
|
22
|
+
address.call(inbound_email)
|
|
23
|
+
else
|
|
24
|
+
address.match?(inbound_email)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def mailbox_class
|
|
29
|
+
"#{mailbox_name.to_s.camelize}Mailbox".constantize
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
def ensure_valid_address
|
|
34
|
+
unless [ Symbol, String, Regexp, Proc ].any? { |klass| address.is_a?(klass) } || address.respond_to?(:match?)
|
|
35
|
+
raise ArgumentError, "Expected a Symbol, String, Regexp, Proc, or matchable, got #{address.inspect}"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module ActionMailbox
|
|
2
|
+
# See `ActionMailbox::Base` for how to specify routing.
|
|
3
|
+
module Routing
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
cattr_accessor :router, default: ActionMailbox::Router.new
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class_methods do
|
|
11
|
+
def routing(routes)
|
|
12
|
+
router.add_routes(routes)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def route(inbound_email)
|
|
16
|
+
router.route(inbound_email)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require "mail"
|
|
2
|
+
|
|
3
|
+
module ActionMailbox
|
|
4
|
+
module TestHelper
|
|
5
|
+
# Create an `InboundEmail` record using an eml fixture in the format of message/rfc822
|
|
6
|
+
# referenced with +fixture_name+ located in +test/fixtures/files/fixture_name+.
|
|
7
|
+
def create_inbound_email_from_fixture(fixture_name, status: :processing)
|
|
8
|
+
create_inbound_email_from_source file_fixture(fixture_name).read, status: status
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Create an `InboundEmail` by specifying it using `Mail.new` options. Example:
|
|
12
|
+
#
|
|
13
|
+
# create_inbound_email_from_mail(from: "david@loudthinking.com", subject: "Hello!")
|
|
14
|
+
def create_inbound_email_from_mail(status: :processing, **mail_options)
|
|
15
|
+
create_inbound_email_from_source Mail.new(mail_options).to_s, status: status
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Create an `InboundEmail` using the raw rfc822 `source` as text.
|
|
19
|
+
def create_inbound_email_from_source(source, status: :processing)
|
|
20
|
+
ActionMailbox::InboundEmail.create_and_extract_message_id! source, status: status
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Create an `InboundEmail` from fixture using the same arguments as `create_inbound_email_from_fixture`
|
|
25
|
+
# and immediately route it to processing.
|
|
26
|
+
def receive_inbound_email_from_fixture(*args)
|
|
27
|
+
create_inbound_email_from_fixture(*args).tap(&:route)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Create an `InboundEmail` from fixture using the same arguments as `create_inbound_email_from_mail`
|
|
31
|
+
# and immediately route it to processing.
|
|
32
|
+
def receive_inbound_email_from_mail(**kwargs)
|
|
33
|
+
create_inbound_email_from_mail(**kwargs).tap(&:route)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Create an `InboundEmail` from fixture using the same arguments as `create_inbound_email_from_source`
|
|
37
|
+
# and immediately route it to processing.
|
|
38
|
+
def receive_inbound_email_from_source(**kwargs)
|
|
39
|
+
create_inbound_email_from_source(**kwargs).tap(&:route)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :action_mailbox do
|
|
4
|
+
namespace :ingress do
|
|
5
|
+
desc "Pipe an inbound email from STDIN to the Postfix ingress (URL and INGRESS_PASSWORD required)"
|
|
6
|
+
task :postfix do
|
|
7
|
+
require "active_support"
|
|
8
|
+
require "active_support/core_ext/object/blank"
|
|
9
|
+
require "action_mailbox/postfix_relayer"
|
|
10
|
+
|
|
11
|
+
url, password = ENV.values_at("URL", "INGRESS_PASSWORD")
|
|
12
|
+
|
|
13
|
+
if url.blank? || password.blank?
|
|
14
|
+
print "4.3.5 URL and INGRESS_PASSWORD are required"
|
|
15
|
+
exit 1
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
ActionMailbox::PostfixRelayer.new(url: url, password: password).relay(STDIN.read).tap do |result|
|
|
19
|
+
print result.output
|
|
20
|
+
exit result.success?
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :action_mailbox do
|
|
4
|
+
# Prevent migration installation task from showing up twice.
|
|
5
|
+
Rake::Task["install:migrations"].clear_comments
|
|
6
|
+
|
|
7
|
+
desc "Copy over the migration"
|
|
8
|
+
task install: %w[ environment run_installer copy_migrations ]
|
|
9
|
+
|
|
10
|
+
task :run_installer do
|
|
11
|
+
installer_template = File.expand_path("../templates/installer.rb", __dir__)
|
|
12
|
+
system "#{RbConfig.ruby} ./bin/rails app:template LOCATION=#{installer_template}"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
task :copy_migrations do
|
|
16
|
+
Rake::Task["active_storage:install:migrations"].invoke
|
|
17
|
+
Rake::Task["railties:install:migrations"].reenable # Otherwise you can't run 2 migration copy tasks in one invocation
|
|
18
|
+
Rake::Task["action_mailbox:install:migrations"].invoke
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
say "Copying application_mailbox.rb to app/mailboxes"
|
|
2
|
+
copy_file "#{__dir__}/mailboxes/application_mailbox.rb", "app/mailboxes/application_mailbox.rb"
|
|
3
|
+
|
|
4
|
+
environment "# Prepare the ingress controller used to receive mail\n# config.action_mailbox.ingress = :amazon\n\n", env: 'production'
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
|
|
3
|
+
ActionMailbox::Ingresses::Amazon::InboundEmailsController.verifier =
|
|
4
|
+
Module.new { def self.authentic?(message); true; end }
|
|
5
|
+
|
|
6
|
+
class ActionMailbox::Ingresses::Amazon::InboundEmailsControllerTest < ActionDispatch::IntegrationTest
|
|
7
|
+
setup { ActionMailbox.ingress = :amazon }
|
|
8
|
+
|
|
9
|
+
test "receiving an inbound email from Amazon" do
|
|
10
|
+
assert_difference -> { ActionMailbox::InboundEmail.count }, +1 do
|
|
11
|
+
post rails_amazon_inbound_emails_url, params: { content: file_fixture("../files/welcome.eml").read }, as: :json
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
assert_response :no_content
|
|
15
|
+
|
|
16
|
+
inbound_email = ActionMailbox::InboundEmail.last
|
|
17
|
+
assert_equal file_fixture("../files/welcome.eml").read, inbound_email.raw_email.download
|
|
18
|
+
assert_equal "0CB459E0-0336-41DA-BC88-E6E28C697DDB@37signals.com", inbound_email.message_id
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
|
|
3
|
+
ENV["MAILGUN_INGRESS_API_KEY"] = "tbsy84uSV1Kt3ZJZELY2TmShPRs91E3yL4tzf96297vBCkDWgL"
|
|
4
|
+
|
|
5
|
+
class ActionMailbox::Ingresses::Mailgun::InboundEmailsControllerTest < ActionDispatch::IntegrationTest
|
|
6
|
+
setup { ActionMailbox.ingress = :mailgun }
|
|
7
|
+
|
|
8
|
+
test "receiving an inbound email from Mailgun" do
|
|
9
|
+
assert_difference -> { ActionMailbox::InboundEmail.count }, +1 do
|
|
10
|
+
travel_to "2018-10-09 15:15:00 EDT"
|
|
11
|
+
post rails_mailgun_inbound_emails_url, params: {
|
|
12
|
+
timestamp: 1539112500,
|
|
13
|
+
token: "7VwW7k6Ak7zcTwoSoNm7aTtbk1g67MKAnsYLfUB7PdszbgR5Xi",
|
|
14
|
+
signature: "ef24c5225322217bb065b80bb54eb4f9206d764e3e16abab07f0a64d1cf477cc",
|
|
15
|
+
"body-mime" => file_fixture("../files/welcome.eml").read
|
|
16
|
+
}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
assert_response :no_content
|
|
20
|
+
|
|
21
|
+
inbound_email = ActionMailbox::InboundEmail.last
|
|
22
|
+
assert_equal file_fixture("../files/welcome.eml").read, inbound_email.raw_email.download
|
|
23
|
+
assert_equal "0CB459E0-0336-41DA-BC88-E6E28C697DDB@37signals.com", inbound_email.message_id
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
test "rejecting a delayed inbound email from Mailgun" do
|
|
27
|
+
assert_no_difference -> { ActionMailbox::InboundEmail.count } do
|
|
28
|
+
travel_to "2018-10-09 15:26:00 EDT"
|
|
29
|
+
post rails_mailgun_inbound_emails_url, params: {
|
|
30
|
+
timestamp: 1539112500,
|
|
31
|
+
token: "7VwW7k6Ak7zcTwoSoNm7aTtbk1g67MKAnsYLfUB7PdszbgR5Xi",
|
|
32
|
+
signature: "ef24c5225322217bb065b80bb54eb4f9206d764e3e16abab07f0a64d1cf477cc",
|
|
33
|
+
"body-mime" => file_fixture("../files/welcome.eml").read
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
assert_response :unauthorized
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
test "rejecting a forged inbound email from Mailgun" do
|
|
41
|
+
assert_no_difference -> { ActionMailbox::InboundEmail.count } do
|
|
42
|
+
travel_to "2018-10-09 15:15:00 EDT"
|
|
43
|
+
post rails_mailgun_inbound_emails_url, params: {
|
|
44
|
+
timestamp: 1539112500,
|
|
45
|
+
token: "Zx8mJBiGmiiyyfWnho3zKyjCg2pxLARoCuBM7X9AKCioShGiMX",
|
|
46
|
+
signature: "ef24c5225322217bb065b80bb54eb4f9206d764e3e16abab07f0a64d1cf477cc",
|
|
47
|
+
"body-mime" => file_fixture("../files/welcome.eml").read
|
|
48
|
+
}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
assert_response :unauthorized
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
test "raising when the configured Mailgun API key is nil" do
|
|
55
|
+
switch_key_to nil do
|
|
56
|
+
assert_raises ArgumentError do
|
|
57
|
+
travel_to "2018-10-09 15:15:00 EDT"
|
|
58
|
+
post rails_mailgun_inbound_emails_url, params: {
|
|
59
|
+
timestamp: 1539112500,
|
|
60
|
+
token: "7VwW7k6Ak7zcTwoSoNm7aTtbk1g67MKAnsYLfUB7PdszbgR5Xi",
|
|
61
|
+
signature: "ef24c5225322217bb065b80bb54eb4f9206d764e3e16abab07f0a64d1cf477cc",
|
|
62
|
+
"body-mime" => file_fixture("../files/welcome.eml").read
|
|
63
|
+
}
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
test "raising when the configured Mailgun API key is blank" do
|
|
69
|
+
switch_key_to "" do
|
|
70
|
+
assert_raises ArgumentError do
|
|
71
|
+
travel_to "2018-10-09 15:15:00 EDT"
|
|
72
|
+
post rails_mailgun_inbound_emails_url, params: {
|
|
73
|
+
timestamp: 1539112500,
|
|
74
|
+
token: "7VwW7k6Ak7zcTwoSoNm7aTtbk1g67MKAnsYLfUB7PdszbgR5Xi",
|
|
75
|
+
signature: "ef24c5225322217bb065b80bb54eb4f9206d764e3e16abab07f0a64d1cf477cc",
|
|
76
|
+
"body-mime" => file_fixture("../files/welcome.eml").read
|
|
77
|
+
}
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
def switch_key_to(new_key)
|
|
84
|
+
previous_key, ENV["MAILGUN_INGRESS_API_KEY"] = ENV["MAILGUN_INGRESS_API_KEY"], new_key
|
|
85
|
+
yield
|
|
86
|
+
ensure
|
|
87
|
+
ENV["MAILGUN_INGRESS_API_KEY"] = previous_key
|
|
88
|
+
end
|
|
89
|
+
end
|