postageapp 1.0.24 → 1.2.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/.travis.yml +13 -12
- data/Gemfile +6 -1
- data/LICENSE +1 -1
- data/README.md +122 -70
- data/Rakefile +19 -4
- data/generators/postageapp/postageapp_generator.rb +5 -7
- data/lib/generators/postageapp/postageapp_generator.rb +15 -9
- data/lib/postageapp.rb +42 -36
- data/lib/postageapp/configuration.rb +34 -21
- data/lib/postageapp/failed_request.rb +60 -36
- data/lib/postageapp/logger.rb +6 -7
- data/lib/postageapp/mail.rb +3 -0
- data/lib/postageapp/mail/arguments.rb +75 -0
- data/lib/postageapp/mail/delivery_method.rb +32 -0
- data/lib/postageapp/mail/extensions.rb +21 -0
- data/lib/postageapp/mailer.rb +72 -20
- data/lib/postageapp/mailer/mailer_2.rb +65 -22
- data/lib/postageapp/mailer/mailer_3.rb +45 -28
- data/lib/postageapp/mailer/mailer_4.rb +72 -40
- data/lib/postageapp/rails/rails.rb +17 -7
- data/lib/postageapp/rails/railtie.rb +22 -7
- data/lib/postageapp/request.rb +67 -43
- data/lib/postageapp/response.rb +11 -8
- data/lib/postageapp/utils.rb +11 -3
- data/lib/postageapp/version.rb +2 -2
- data/postageapp.gemspec +13 -11
- data/rails/init.rb +1 -1
- data/test/configuration_test.rb +35 -38
- data/test/failed_request_test.rb +33 -18
- data/test/gemfiles/Gemfile.rails-2.3.x +4 -1
- data/test/gemfiles/Gemfile.rails-3.0.x +4 -1
- data/test/gemfiles/Gemfile.rails-3.1.x +4 -1
- data/test/gemfiles/Gemfile.rails-3.2.x +4 -1
- data/test/gemfiles/Gemfile.rails-4.0.x +4 -1
- data/test/gemfiles/Gemfile.rails-4.1.x +12 -0
- data/test/gemfiles/Gemfile.rails-4.2.x +12 -0
- data/test/gemfiles/Gemfile.ruby +11 -0
- data/test/helper.rb +52 -33
- data/test/live_test.rb +11 -8
- data/test/mail_delivery_method_test.rb +161 -0
- data/test/mailer/action_mailer_2/notifier.rb +37 -28
- data/test/mailer/action_mailer_3/notifier.rb +28 -22
- data/test/mailer_2_test.rb +20 -16
- data/test/mailer_3_test.rb +16 -22
- data/test/mailer_4_test.rb +28 -28
- data/test/mailer_helper_methods_test.rb +17 -14
- data/test/postageapp_test.rb +8 -9
- data/test/rails_initialization_test.rb +14 -14
- data/test/request_test.rb +35 -35
- data/test/response_test.rb +20 -19
- data/test/travis_test.rb +168 -0
- data/test/with_environment.rb +27 -0
- metadata +23 -17
data/lib/postageapp.rb
CHANGED
@@ -13,48 +13,54 @@ require 'postageapp/logger'
|
|
13
13
|
require 'postageapp/request'
|
14
14
|
require 'postageapp/failed_request'
|
15
15
|
require 'postageapp/response'
|
16
|
-
require 'postageapp/
|
16
|
+
require 'postageapp/mail'
|
17
|
+
require 'postageapp/mail/delivery_method'
|
18
|
+
|
19
|
+
require 'postageapp/rails/railtie' if (defined?(Rails::Railtie))
|
17
20
|
|
18
21
|
module PostageApp
|
19
|
-
|
20
22
|
class Error < StandardError ; end
|
21
23
|
|
24
|
+
# Call this method to modify your configuration
|
25
|
+
# Example:
|
26
|
+
# PostageApp.configure do |config|
|
27
|
+
# config.api_key = '1234567890abcdef'
|
28
|
+
# config.recipient_override = 'test@test.test' if Rails.env.staging?
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# If you do not want/need to initialize the gem in this way, you can use the environment
|
32
|
+
# variable POSTAGEAPP_API_KEY to set up your key.
|
33
|
+
|
34
|
+
def self.configure
|
35
|
+
yield(configuration)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Accessor for the PostageApp::Configuration object
|
39
|
+
# Example use:
|
40
|
+
# PostageApp.configuration.api_key = '1234567890abcdef'
|
41
|
+
def self.configuration
|
42
|
+
@configuration ||= Configuration.new
|
43
|
+
end
|
44
|
+
|
22
45
|
class << self
|
23
|
-
|
24
|
-
# Call this method to modify your configuration
|
25
|
-
# Example:
|
26
|
-
# PostageApp.configure do |config|
|
27
|
-
# config.api_key = '1234567890abcdef'
|
28
|
-
# config.recipient_override = 'test@test.test' if Rails.env.staging?
|
29
|
-
# end
|
30
|
-
#
|
31
|
-
# If you do not want/need to initialize the gem in this way, you can use the environment
|
32
|
-
# variable POSTAGEAPP_API_KEY to set up your key.
|
33
|
-
|
34
|
-
def configure
|
35
|
-
yield configuration
|
36
|
-
end
|
37
|
-
|
38
|
-
# Accessor for the PostageApp::Configuration object
|
39
|
-
# Example use:
|
40
|
-
# PostageApp.configuration.api_key = '1234567890abcdef'
|
41
|
-
def configuration
|
42
|
-
@configuration ||= Configuration.new
|
43
|
-
end
|
44
46
|
alias :config :configuration
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
47
|
+
end
|
48
|
+
|
49
|
+
# Logger for the plugin
|
50
|
+
def self.logger
|
51
|
+
@logger ||= begin
|
52
|
+
configuration.logger || PostageApp::Logger.new(
|
53
|
+
if (configuration.project_root)
|
54
|
+
FileUtils.mkdir_p(File.join(File.expand_path(configuration.project_root), 'log'))
|
55
|
+
File.join(configuration.project_root, "log/postageapp_#{configuration.environment}.log")
|
56
|
+
else
|
57
|
+
STDOUT
|
58
|
+
end
|
59
|
+
)
|
58
60
|
end
|
59
61
|
end
|
60
62
|
end
|
63
|
+
|
64
|
+
require 'postageapp/mail/extensions'
|
65
|
+
|
66
|
+
PostageApp::Mail::Extensions.install!
|
@@ -1,5 +1,4 @@
|
|
1
1
|
class PostageApp::Configuration
|
2
|
-
|
3
2
|
# +true+ for https, +false+ for http connections (default: is +true+)
|
4
3
|
attr_accessor :secure
|
5
4
|
|
@@ -12,7 +11,7 @@ class PostageApp::Configuration
|
|
12
11
|
|
13
12
|
# The port on which PostageApp service runs (default: 443 for secure, 80 for
|
14
13
|
# insecure connections)
|
15
|
-
|
14
|
+
attr_writer :port
|
16
15
|
|
17
16
|
# The hostname of the proxy server (if using a proxy)
|
18
17
|
attr_accessor :proxy_host
|
@@ -54,43 +53,57 @@ class PostageApp::Configuration
|
|
54
53
|
attr_accessor :logger
|
55
54
|
|
56
55
|
def initialize
|
57
|
-
@secure
|
58
|
-
@host
|
59
|
-
@http_open_timeout
|
60
|
-
@http_read_timeout
|
56
|
+
@secure = true
|
57
|
+
@host = 'api.postageapp.com'
|
58
|
+
@http_open_timeout = 5
|
59
|
+
@http_read_timeout = 10
|
61
60
|
@requests_to_resend = %w( send_message )
|
62
|
-
@framework
|
63
|
-
@environment
|
61
|
+
@framework = 'undefined framework'
|
62
|
+
@environment = 'production'
|
64
63
|
end
|
65
64
|
|
66
65
|
alias_method :secure?, :secure
|
67
66
|
|
67
|
+
# Assign which API key is used to make API calls. Can also be specified
|
68
|
+
# using the `POSTAGEAPP_API_KEY` environment variable.
|
68
69
|
def api_key=(key)
|
69
70
|
@api_key = key
|
70
71
|
end
|
71
72
|
|
73
|
+
# Returns the API key used to make API calls. Can be specified as the
|
74
|
+
# `POSTAGEAPP_API_KEY` environment variable.
|
72
75
|
def api_key
|
73
|
-
@api_key
|
76
|
+
@api_key ||= ENV['POSTAGEAPP_API_KEY']
|
74
77
|
end
|
75
78
|
|
79
|
+
# Returns the HTTP protocol used to make API calls
|
76
80
|
def protocol
|
77
|
-
@protocol
|
78
|
-
'https'
|
79
|
-
else
|
80
|
-
'http'
|
81
|
-
end
|
81
|
+
@protocol ||= (secure? ? 'https' : 'http')
|
82
82
|
end
|
83
83
|
|
84
|
+
# Returns the port used to make API calls
|
84
85
|
def port
|
85
|
-
@port
|
86
|
-
443
|
87
|
-
else
|
88
|
-
80
|
89
|
-
end
|
86
|
+
@port ||= (secure? ? 443 : 80)
|
90
87
|
end
|
91
88
|
|
89
|
+
# Returns the endpoint URL to make API calls
|
92
90
|
def url
|
93
91
|
"#{self.protocol}://#{self.host}:#{self.port}"
|
94
92
|
end
|
95
|
-
|
96
|
-
|
93
|
+
|
94
|
+
# Returns a properly config
|
95
|
+
def http
|
96
|
+
http = Net::HTTP::Proxy(
|
97
|
+
self.proxy_host,
|
98
|
+
self.proxy_port,
|
99
|
+
self.proxy_user,
|
100
|
+
self.proxy_pass
|
101
|
+
).new(self.host, self.port)
|
102
|
+
|
103
|
+
http.read_timeout = self.http_read_timeout
|
104
|
+
http.open_timeout = self.http_open_timeout
|
105
|
+
http.use_ssl = self.secure?
|
106
|
+
|
107
|
+
http
|
108
|
+
end
|
109
|
+
end
|
@@ -1,75 +1,99 @@
|
|
1
1
|
module PostageApp::FailedRequest
|
2
|
-
|
3
2
|
# Stores request object into a file for future re-send
|
4
3
|
# returns true if stored, false if not (due to undefined project path)
|
5
4
|
def self.store(request)
|
6
|
-
return false
|
5
|
+
return false unless (self.store_path)
|
6
|
+
return false unless (PostageApp.configuration.requests_to_resend.member?(request.method.to_s))
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
unless (File.exists?(file_path(request.uid)))
|
9
|
+
open(file_path(request.uid), 'wb') do |f|
|
10
|
+
f.write(Marshal.dump(request))
|
11
|
+
end
|
12
|
+
end
|
11
13
|
|
12
|
-
PostageApp.logger.info
|
14
|
+
PostageApp.logger.info("STORING FAILED REQUEST [#{request.uid}]")
|
13
15
|
|
14
16
|
true
|
15
17
|
end
|
18
|
+
|
19
|
+
def self.force_delete!(path)
|
20
|
+
File.delete(path)
|
21
|
+
|
22
|
+
rescue
|
23
|
+
nil
|
24
|
+
end
|
16
25
|
|
17
26
|
# Attempting to resend failed requests
|
18
27
|
def self.resend_all
|
19
|
-
return false
|
28
|
+
return false unless (self.store_path)
|
20
29
|
|
21
30
|
Dir.foreach(store_path) do |filename|
|
22
|
-
next
|
31
|
+
next unless (filename.match(/^\w{40}$/))
|
23
32
|
|
24
33
|
request = initialize_request(filename)
|
25
34
|
|
26
|
-
receipt_response = PostageApp::Request.new(
|
27
|
-
|
35
|
+
receipt_response = PostageApp::Request.new(
|
36
|
+
:get_message_receipt,
|
37
|
+
:uid => filename
|
38
|
+
).send(true)
|
39
|
+
|
40
|
+
if (receipt_response.fail?)
|
28
41
|
return
|
29
|
-
elsif receipt_response.ok?
|
30
|
-
PostageApp.logger.info
|
31
|
-
|
32
|
-
|
33
|
-
elsif receipt_response.not_found?
|
34
|
-
PostageApp.logger.info
|
42
|
+
elsif (receipt_response.ok?)
|
43
|
+
PostageApp.logger.info("Skipping failed request (already sent) [#{filename}]")
|
44
|
+
|
45
|
+
force_delete!(file_path(filename))
|
46
|
+
elsif (receipt_response.not_found?)
|
47
|
+
PostageApp.logger.info("Retrying failed request [#{filename}]")
|
48
|
+
|
35
49
|
response = request.send(true)
|
36
50
|
|
37
51
|
# Not a fail, so we can remove this file, if it was then
|
38
52
|
# there will be another attempt to resend
|
39
|
-
|
53
|
+
|
54
|
+
unless (response.fail?)
|
55
|
+
force_delete!(file_path(filename))
|
56
|
+
end
|
40
57
|
else
|
41
|
-
PostageApp.logger.info
|
42
|
-
|
58
|
+
PostageApp.logger.info("Skipping failed request (non-replayable request type) [#{filename}]")
|
59
|
+
|
60
|
+
force_delete!(file_path(filename))
|
43
61
|
end
|
44
62
|
end
|
63
|
+
|
45
64
|
return
|
46
65
|
end
|
47
66
|
|
48
67
|
# Initializing PostageApp::Request object from the file
|
49
68
|
def self.initialize_request(uid)
|
50
|
-
return false
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
end
|
69
|
+
return false unless (self.store_path)
|
70
|
+
return false unless (File.exists?(file_path(uid)))
|
71
|
+
|
72
|
+
Marshal.load(File.read(file_path(uid)))
|
73
|
+
|
74
|
+
rescue
|
75
|
+
force_delete!(file_path(uid))
|
76
|
+
|
77
|
+
false
|
60
78
|
end
|
61
79
|
|
62
80
|
protected
|
63
|
-
|
64
81
|
def self.store_path
|
65
|
-
return
|
66
|
-
|
67
|
-
|
68
|
-
|
82
|
+
return unless (PostageApp.configuration.project_root)
|
83
|
+
|
84
|
+
dir = File.join(
|
85
|
+
File.expand_path(PostageApp.configuration.project_root),
|
86
|
+
'tmp/postageapp_failed_requests'
|
87
|
+
)
|
88
|
+
|
89
|
+
unless (File.exists?(dir))
|
90
|
+
FileUtils.mkdir_p(dir)
|
91
|
+
end
|
92
|
+
|
93
|
+
dir
|
69
94
|
end
|
70
95
|
|
71
96
|
def self.file_path(uid)
|
72
97
|
File.join(store_path, uid)
|
73
98
|
end
|
74
|
-
|
75
|
-
end
|
99
|
+
end
|
data/lib/postageapp/logger.rb
CHANGED
@@ -1,16 +1,15 @@
|
|
1
1
|
class PostageApp::Logger < ::Logger
|
2
|
-
|
3
2
|
def format_message(severity, datetime, progname, msg)
|
4
|
-
|
5
|
-
|
3
|
+
"[%s] %s\n" % [
|
4
|
+
datetime.strftime('%m/%d/%Y %H:%M:%S %Z'),
|
5
|
+
case (msg)
|
6
6
|
when PostageApp::Request
|
7
7
|
"REQUEST [#{msg.url}]\n #{msg.arguments_to_send.to_json}"
|
8
8
|
when PostageApp::Response
|
9
9
|
"RESPONSE [#{msg.status} #{msg.uid} #{msg.message}]\n #{msg.data.to_json}"
|
10
10
|
else
|
11
11
|
msg
|
12
|
-
|
13
|
-
|
12
|
+
end
|
13
|
+
]
|
14
14
|
end
|
15
|
-
|
16
|
-
end
|
15
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
# This class decomposes a Mail::Message into a PostageApp API call that
|
4
|
+
# produces the same result when sent.
|
5
|
+
|
6
|
+
class PostageApp::Mail::Arguments
|
7
|
+
# Certain headers need to be ignored since they are generated internally.
|
8
|
+
HEADERS_IGNORED = Set.new(%w[
|
9
|
+
Content-Type
|
10
|
+
To
|
11
|
+
]).freeze
|
12
|
+
|
13
|
+
# Creates a new instance with the given Mail::Message binding.
|
14
|
+
def initialize(mail)
|
15
|
+
@mail = mail
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns the extracted arguments. If a pre-existing arguments has is
|
19
|
+
# supplied, arguments are injected into that.
|
20
|
+
def extract(arguments = nil)
|
21
|
+
arguments ||= { }
|
22
|
+
arguments['content'] ||= { }
|
23
|
+
arguments['headers'] ||= { }
|
24
|
+
|
25
|
+
arguments['recipients'] = @mail.to
|
26
|
+
|
27
|
+
if (@mail.multipart?)
|
28
|
+
@mail.parts.each do |part|
|
29
|
+
if (part.content_disposition)
|
30
|
+
add_attachment(arguments, part)
|
31
|
+
else
|
32
|
+
add_part(arguments, part)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
else
|
36
|
+
add_part(arguments, @mail)
|
37
|
+
end
|
38
|
+
|
39
|
+
_headers = arguments['headers']
|
40
|
+
@mail.header.fields.each do |field|
|
41
|
+
next if (HEADERS_IGNORED.include?(field.name))
|
42
|
+
|
43
|
+
_headers[field.name] = field.value
|
44
|
+
end
|
45
|
+
|
46
|
+
if (@mail.has_attachments?)
|
47
|
+
@mail.attachments.each do |attachment|
|
48
|
+
add_attachment(arguments, attachment)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
[ :send_message, arguments ]
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
def add_part(arguments, part)
|
57
|
+
case (part.content_type.to_s.split(/\s*;/).first)
|
58
|
+
when 'text/html'
|
59
|
+
arguments['content']['text/html'] = part.body.to_s
|
60
|
+
when 'text/plain', nil
|
61
|
+
arguments['content']['text/plain'] = part.body.to_s
|
62
|
+
else
|
63
|
+
# Unknown type.
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def add_attachment(arguments, part)
|
68
|
+
arguments['attachments'] ||= { }
|
69
|
+
|
70
|
+
arguments['attachments'][part.filename] = {
|
71
|
+
'content' => Base64.encode64(part.body.to_s),
|
72
|
+
'content_type' => part.content_type
|
73
|
+
}
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class PostageApp::Mail::DeliveryMethod
|
2
|
+
def self.deliveries
|
3
|
+
@deliveries ||= [ ]
|
4
|
+
end
|
5
|
+
|
6
|
+
# Creates a new DeliveryMethod instance with the supplied options.
|
7
|
+
def initialize(options)
|
8
|
+
@options = options.dup
|
9
|
+
end
|
10
|
+
|
11
|
+
# Delivers a given Mail::Message through PostageApp using the configuration
|
12
|
+
# specified through Mail defaults or settings applied to ActionMailer.
|
13
|
+
def deliver!(mail)
|
14
|
+
api_method, arguments = PostageApp::Mail::Arguments.new(mail).extract
|
15
|
+
|
16
|
+
case (@options[:api_key])
|
17
|
+
when false, :test
|
18
|
+
# In testing mode, just capture the calls that would have been made so
|
19
|
+
# they can be inspected later using the deliveries class method.
|
20
|
+
self.class.deliveries << [ api_method, arguments ]
|
21
|
+
when nil
|
22
|
+
# If the API key is not defined, raise an error providing a hint as to
|
23
|
+
# how to set that correctly.
|
24
|
+
raise PostageApp::Error,
|
25
|
+
"PostageApp API key not defined: Add :api_key to config.action_mailer.postageapp_settings to config/application.rb"
|
26
|
+
else
|
27
|
+
arguments['api_key'] ||= @options[:api_key]
|
28
|
+
|
29
|
+
PostageApp::Request.new(api_method, arguments).send
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|