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.
Files changed (144) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/Gemfile +8 -0
  4. data/Gemfile.lock +159 -0
  5. data/LICENSE +21 -0
  6. data/README.md +278 -0
  7. data/Rakefile +27 -0
  8. data/actionmailbox.gemspec +27 -0
  9. data/app/controllers/action_mailbox/base_controller.rb +43 -0
  10. data/app/controllers/action_mailbox/ingresses/amazon/inbound_emails_controller.rb +50 -0
  11. data/app/controllers/action_mailbox/ingresses/mailgun/inbound_emails_controller.rb +99 -0
  12. data/app/controllers/action_mailbox/ingresses/mandrill/inbound_emails_controller.rb +78 -0
  13. data/app/controllers/action_mailbox/ingresses/postfix/inbound_emails_controller.rb +55 -0
  14. data/app/controllers/action_mailbox/ingresses/sendgrid/inbound_emails_controller.rb +50 -0
  15. data/app/controllers/rails/conductor/action_mailbox/inbound_emails_controller.rb +27 -0
  16. data/app/controllers/rails/conductor/action_mailbox/reroutes_controller.rb +15 -0
  17. data/app/controllers/rails/conductor/base_controller.rb +10 -0
  18. data/app/jobs/action_mailbox/incineration_job.rb +18 -0
  19. data/app/jobs/action_mailbox/routing_job.rb +9 -0
  20. data/app/models/action_mailbox/inbound_email.rb +43 -0
  21. data/app/models/action_mailbox/inbound_email/incineratable.rb +18 -0
  22. data/app/models/action_mailbox/inbound_email/incineratable/incineration.rb +22 -0
  23. data/app/models/action_mailbox/inbound_email/message_id.rb +36 -0
  24. data/app/models/action_mailbox/inbound_email/routable.rb +22 -0
  25. data/app/views/layouts/rails/conductor.html.erb +7 -0
  26. data/app/views/rails/conductor/action_mailbox/inbound_emails/index.html.erb +15 -0
  27. data/app/views/rails/conductor/action_mailbox/inbound_emails/new.html.erb +42 -0
  28. data/app/views/rails/conductor/action_mailbox/inbound_emails/show.html.erb +15 -0
  29. data/bin/test +5 -0
  30. data/config/routes.rb +19 -0
  31. data/db/migrate/20180917164000_create_action_mailbox_tables.rb +11 -0
  32. data/lib/action_mailbox.rb +15 -0
  33. data/lib/action_mailbox/base.rb +111 -0
  34. data/lib/action_mailbox/callbacks.rb +32 -0
  35. data/lib/action_mailbox/engine.rb +34 -0
  36. data/lib/action_mailbox/mail_ext.rb +4 -0
  37. data/lib/action_mailbox/mail_ext/address_equality.rb +5 -0
  38. data/lib/action_mailbox/mail_ext/address_wrapping.rb +5 -0
  39. data/lib/action_mailbox/mail_ext/addresses.rb +25 -0
  40. data/lib/action_mailbox/mail_ext/from_source.rb +5 -0
  41. data/lib/action_mailbox/mail_ext/recipients.rb +5 -0
  42. data/lib/action_mailbox/postfix_relayer.rb +67 -0
  43. data/lib/action_mailbox/router.rb +38 -0
  44. data/lib/action_mailbox/router/route.rb +38 -0
  45. data/lib/action_mailbox/routing.rb +20 -0
  46. data/lib/action_mailbox/test_case.rb +8 -0
  47. data/lib/action_mailbox/test_helper.rb +42 -0
  48. data/lib/action_mailbox/version.rb +3 -0
  49. data/lib/tasks/ingress.rake +24 -0
  50. data/lib/tasks/install.rake +20 -0
  51. data/lib/templates/installer.rb +4 -0
  52. data/lib/templates/mailboxes/application_mailbox.rb +3 -0
  53. data/test/controllers/ingresses/amazon/inbound_emails_controller_test.rb +20 -0
  54. data/test/controllers/ingresses/mailgun/inbound_emails_controller_test.rb +89 -0
  55. data/test/controllers/ingresses/mandrill/inbound_emails_controller_test.rb +58 -0
  56. data/test/controllers/ingresses/postfix/inbound_emails_controller_test.rb +54 -0
  57. data/test/controllers/ingresses/sendgrid/inbound_emails_controller_test.rb +44 -0
  58. data/test/dummy/.babelrc +18 -0
  59. data/test/dummy/.gitignore +3 -0
  60. data/test/dummy/.postcssrc.yml +3 -0
  61. data/test/dummy/Rakefile +6 -0
  62. data/test/dummy/app/assets/config/manifest.js +3 -0
  63. data/test/dummy/app/assets/images/.keep +0 -0
  64. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  65. data/test/dummy/app/assets/stylesheets/scaffold.css +80 -0
  66. data/test/dummy/app/channels/application_cable/channel.rb +4 -0
  67. data/test/dummy/app/channels/application_cable/connection.rb +4 -0
  68. data/test/dummy/app/controllers/application_controller.rb +2 -0
  69. data/test/dummy/app/controllers/concerns/.keep +0 -0
  70. data/test/dummy/app/helpers/application_helper.rb +2 -0
  71. data/test/dummy/app/javascript/packs/application.js +0 -0
  72. data/test/dummy/app/jobs/application_job.rb +2 -0
  73. data/test/dummy/app/mailboxes/application_mailbox.rb +2 -0
  74. data/test/dummy/app/mailboxes/messages_mailbox.rb +4 -0
  75. data/test/dummy/app/mailers/application_mailer.rb +4 -0
  76. data/test/dummy/app/models/application_record.rb +3 -0
  77. data/test/dummy/app/models/concerns/.keep +0 -0
  78. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  79. data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
  80. data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
  81. data/test/dummy/bin/bundle +3 -0
  82. data/test/dummy/bin/rails +4 -0
  83. data/test/dummy/bin/rake +4 -0
  84. data/test/dummy/bin/setup +36 -0
  85. data/test/dummy/bin/update +31 -0
  86. data/test/dummy/bin/yarn +11 -0
  87. data/test/dummy/config.ru +5 -0
  88. data/test/dummy/config/application.rb +19 -0
  89. data/test/dummy/config/boot.rb +5 -0
  90. data/test/dummy/config/cable.yml +10 -0
  91. data/test/dummy/config/database.yml +25 -0
  92. data/test/dummy/config/environment.rb +5 -0
  93. data/test/dummy/config/environments/development.rb +63 -0
  94. data/test/dummy/config/environments/production.rb +96 -0
  95. data/test/dummy/config/environments/test.rb +46 -0
  96. data/test/dummy/config/initializers/application_controller_renderer.rb +8 -0
  97. data/test/dummy/config/initializers/assets.rb +14 -0
  98. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  99. data/test/dummy/config/initializers/content_security_policy.rb +22 -0
  100. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  101. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  102. data/test/dummy/config/initializers/inflections.rb +16 -0
  103. data/test/dummy/config/initializers/mime_types.rb +4 -0
  104. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  105. data/test/dummy/config/locales/en.yml +33 -0
  106. data/test/dummy/config/puma.rb +34 -0
  107. data/test/dummy/config/routes.rb +4 -0
  108. data/test/dummy/config/spring.rb +6 -0
  109. data/test/dummy/config/storage.yml +35 -0
  110. data/test/dummy/config/webpack/development.js +3 -0
  111. data/test/dummy/config/webpack/environment.js +3 -0
  112. data/test/dummy/config/webpack/production.js +3 -0
  113. data/test/dummy/config/webpack/test.js +3 -0
  114. data/test/dummy/config/webpacker.yml +65 -0
  115. data/test/dummy/db/migrate/20180208205311_create_action_mailroom_tables.rb +11 -0
  116. data/test/dummy/db/migrate/20180212164506_create_active_storage_tables.active_storage.rb +26 -0
  117. data/test/dummy/db/schema.rb +43 -0
  118. data/test/dummy/lib/assets/.keep +0 -0
  119. data/test/dummy/log/.keep +0 -0
  120. data/test/dummy/package.json +11 -0
  121. data/test/dummy/public/404.html +67 -0
  122. data/test/dummy/public/422.html +67 -0
  123. data/test/dummy/public/500.html +66 -0
  124. data/test/dummy/public/apple-touch-icon-precomposed.png +0 -0
  125. data/test/dummy/public/apple-touch-icon.png +0 -0
  126. data/test/dummy/public/favicon.ico +0 -0
  127. data/test/dummy/storage/.keep +0 -0
  128. data/test/dummy/yarn.lock +6071 -0
  129. data/test/fixtures/files/welcome.eml +631 -0
  130. data/test/jobs/incineration_job_test.rb +17 -0
  131. data/test/test_helper.rb +54 -0
  132. data/test/unit/inbound_email/incineration_test.rb +45 -0
  133. data/test/unit/inbound_email/message_id_test.rb +13 -0
  134. data/test/unit/inbound_email_test.rb +13 -0
  135. data/test/unit/mail_ext/address_equality_test.rb +9 -0
  136. data/test/unit/mail_ext/address_wrapping_test.rb +11 -0
  137. data/test/unit/mail_ext/recipients_test.rb +33 -0
  138. data/test/unit/mailbox/bouncing_test.rb +29 -0
  139. data/test/unit/mailbox/callbacks_test.rb +75 -0
  140. data/test/unit/mailbox/routing_test.rb +30 -0
  141. data/test/unit/mailbox/state_test.rb +49 -0
  142. data/test/unit/postfix_relayer_test.rb +90 -0
  143. data/test/unit/router_test.rb +137 -0
  144. 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,4 @@
1
+ require "mail"
2
+
3
+ # The hope is to upstream most of these basic additions to the Mail gem's Mail object. But until then, here they lay!
4
+ Dir["#{File.expand_path(File.dirname(__FILE__))}/mail_ext/*"].each { |path| require "action_mailbox/mail_ext/#{File.basename(path)}" }
@@ -0,0 +1,5 @@
1
+ class Mail::Address
2
+ def ==(other_address)
3
+ other_address.is_a?(Mail::Address) && to_s == other_address.to_s
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Mail::Address
2
+ def self.wrap(address)
3
+ address.is_a?(Mail::Address) ? address : Mail::Address.new(address)
4
+ end
5
+ 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,5 @@
1
+ module Mail
2
+ def self.from_source(source)
3
+ Mail.new Mail::Utilities.binary_unsafe_to_crlf(source.to_s)
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Mail::Message
2
+ def recipients
3
+ Array(to) + Array(cc) + Array(bcc) + Array(header[:x_original_to]).map(&:to_s)
4
+ end
5
+ 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,8 @@
1
+ require "action_mailbox/test_helper"
2
+ require "active_support/test_case"
3
+
4
+ module ActionMailbox
5
+ class TestCase < ActiveSupport::TestCase
6
+ include ActionMailbox::TestHelper
7
+ end
8
+ 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,3 @@
1
+ module ActionMailbox
2
+ VERSION = '0.1.0'
3
+ 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,3 @@
1
+ class ApplicationMailbox < ActionMailbox::Base
2
+ # route /something/i => :somewhere
3
+ end
@@ -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