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