actionmailbox 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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