postmark 0.9.10 → 0.9.11
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.
- data/.bundle/config +2 -3
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/CHANGELOG.rdoc +7 -0
- data/Gemfile +11 -0
- data/README.rdoc +4 -1
- data/Rakefile +3 -63
- data/VERSION +1 -1
- data/lib/postmark.rb +115 -114
- data/lib/postmark/http_client.rb +55 -56
- data/lib/postmark/message_extensions/shared.rb +8 -8
- data/lib/postmark/version.rb +3 -0
- data/postmark.gemspec +39 -82
- data/spec/postmark_spec.rb +37 -115
- data/spec/shared_examples.rb +41 -0
- data/spec/spec_helper.rb +15 -4
- metadata +135 -131
- data/spec/spec.opts +0 -3
data/.bundle/config
CHANGED
@@ -1,3 +1,2 @@
|
|
1
|
-
---
|
2
|
-
BUNDLE_WITHOUT:
|
3
|
-
BUNDLE_DISABLE_SHARED_GEMS: "1"
|
1
|
+
---
|
2
|
+
BUNDLE_WITHOUT: ''
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/CHANGELOG.rdoc
CHANGED
data/Gemfile
ADDED
data/README.rdoc
CHANGED
@@ -114,7 +114,10 @@ You can also explicitly specify which one to be used, using
|
|
114
114
|
* Chris Williams
|
115
115
|
* Aitor García Rey
|
116
116
|
* James Miller
|
117
|
+
* Yury Batenko
|
118
|
+
* Pavel Maksimenko
|
119
|
+
* Anton Astashov
|
117
120
|
|
118
121
|
== Copyright
|
119
122
|
|
120
|
-
Copyright (c)
|
123
|
+
Copyright (c) 2012 Wildbit LLC. See LICENSE for details.
|
data/Rakefile
CHANGED
@@ -1,63 +1,3 @@
|
|
1
|
-
require
|
2
|
-
require '
|
3
|
-
|
4
|
-
begin
|
5
|
-
require 'jeweler'
|
6
|
-
Jeweler::Tasks.new do |gem|
|
7
|
-
gem.name = "postmark"
|
8
|
-
gem.summary = %Q{Official Postmark API wrapper.}
|
9
|
-
gem.description = %Q{Use this gem to send emails through Postmark HTTP API and retrieve info about bounces.}
|
10
|
-
gem.email = "ilya@wildbit.com"
|
11
|
-
gem.homepage = "http://postmarkapp.com"
|
12
|
-
gem.authors = ["Petyo Ivanov", "Ilya Sabanin", "Artem Chistyakov"]
|
13
|
-
|
14
|
-
gem.add_development_dependency "rspec"
|
15
|
-
gem.add_development_dependency "activesupport"
|
16
|
-
gem.add_development_dependency "json"
|
17
|
-
gem.add_development_dependency "ruby-debug"
|
18
|
-
gem.add_development_dependency "fakeweb"
|
19
|
-
gem.add_development_dependency "fakeweb-matcher"
|
20
|
-
gem.add_development_dependency "timecop"
|
21
|
-
gem.add_development_dependency "yajl-ruby"
|
22
|
-
|
23
|
-
gem.post_install_message = %q[
|
24
|
-
==================
|
25
|
-
Thanks for installing the postmark gem. If you don't have an account, please sign up at http://postmarkapp.com/.
|
26
|
-
Review the README.rdoc for implementation details and examples.
|
27
|
-
==================
|
28
|
-
]
|
29
|
-
end
|
30
|
-
Jeweler::GemcutterTasks.new
|
31
|
-
rescue LoadError
|
32
|
-
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
33
|
-
end
|
34
|
-
|
35
|
-
require 'spec/rake/spectask'
|
36
|
-
Spec::Rake::SpecTask.new(:spec) do |spec|
|
37
|
-
spec.libs << 'lib' << 'spec'
|
38
|
-
spec.spec_files = FileList['spec/**/*_spec.rb']
|
39
|
-
end
|
40
|
-
|
41
|
-
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
42
|
-
spec.libs << 'lib' << 'spec'
|
43
|
-
spec.pattern = 'spec/**/*_spec.rb'
|
44
|
-
spec.rcov = true
|
45
|
-
end
|
46
|
-
|
47
|
-
task :spec => :check_dependencies
|
48
|
-
|
49
|
-
task :default => :spec
|
50
|
-
|
51
|
-
require 'rake/rdoctask'
|
52
|
-
Rake::RDocTask.new do |rdoc|
|
53
|
-
if File.exist?('VERSION')
|
54
|
-
version = File.read('VERSION')
|
55
|
-
else
|
56
|
-
version = ""
|
57
|
-
end
|
58
|
-
|
59
|
-
rdoc.rdoc_dir = 'rdoc'
|
60
|
-
rdoc.title = "postmark #{version}"
|
61
|
-
rdoc.rdoc_files.include('README*')
|
62
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
63
|
-
end
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
RSpec::Core::RakeTask.new
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.9.
|
1
|
+
0.9.11
|
data/lib/postmark.rb
CHANGED
@@ -36,148 +36,149 @@ module Postmark
|
|
36
36
|
|
37
37
|
MAX_RETRIES = 2
|
38
38
|
|
39
|
-
|
40
|
-
attr_accessor :host, :path_prefix, :port, :secure, :api_key, :http_open_timeout, :http_read_timeout,
|
41
|
-
:proxy_host, :proxy_port, :proxy_user, :proxy_pass, :max_retries, :sleep_between_retries
|
39
|
+
extend self
|
42
40
|
|
43
|
-
|
41
|
+
attr_accessor :host, :path_prefix, :port, :secure, :api_key, :http_open_timeout, :http_read_timeout,
|
42
|
+
:proxy_host, :proxy_port, :proxy_user, :proxy_pass, :max_retries, :sleep_between_retries
|
44
43
|
|
45
|
-
|
46
|
-
@response_parser_class ||= Object.const_defined?(:ActiveSupport) ? :ActiveSupport : :Json
|
47
|
-
end
|
44
|
+
attr_writer :response_parser_class
|
48
45
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
end
|
46
|
+
def response_parser_class
|
47
|
+
@response_parser_class ||= Object.const_defined?(:ActiveSupport) ? :ActiveSupport : :Json
|
48
|
+
end
|
53
49
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
50
|
+
# The port on which your Postmark server runs.
|
51
|
+
def port
|
52
|
+
@port || (secure ? 443 : 80)
|
53
|
+
end
|
58
54
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
55
|
+
# The host to connect to.
|
56
|
+
def host
|
57
|
+
@host ||= 'api.postmarkapp.com'
|
58
|
+
end
|
63
59
|
|
64
|
-
|
65
|
-
|
66
|
-
|
60
|
+
# The path of the listener
|
61
|
+
def path_prefix
|
62
|
+
@path_prefix ||= '/'
|
63
|
+
end
|
67
64
|
|
68
|
-
|
69
|
-
|
70
|
-
|
65
|
+
def http_open_timeout
|
66
|
+
@http_open_timeout ||= 5
|
67
|
+
end
|
71
68
|
|
72
|
-
|
73
|
-
|
74
|
-
|
69
|
+
def http_read_timeout
|
70
|
+
@http_read_timeout ||= 15
|
71
|
+
end
|
75
72
|
|
76
|
-
|
77
|
-
|
78
|
-
|
73
|
+
def max_retries
|
74
|
+
@max_retries ||= 3
|
75
|
+
end
|
79
76
|
|
80
|
-
|
81
|
-
|
82
|
-
|
77
|
+
def sleep_between_retries
|
78
|
+
@sleep_between_retries ||= 10
|
79
|
+
end
|
83
80
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
HttpClient.post("email", Postmark::Json.encode(convert_message_to_options_hash(message)))
|
88
|
-
rescue DeliveryError => e
|
89
|
-
if @retries < max_retries
|
90
|
-
@retries += 1
|
91
|
-
retry
|
92
|
-
else
|
93
|
-
raise
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
81
|
+
def configure
|
82
|
+
yield self
|
83
|
+
end
|
97
84
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
options["Bcc"] = Array[message.bcc].flatten.join(", ") if message.bcc
|
107
|
-
options["Subject"] = message.subject
|
108
|
-
options["Attachments"] = message.postmark_attachments
|
109
|
-
options["Tag"] = message.tag.to_s if message.tag
|
110
|
-
options["Headers"] = headers if headers.size > 0
|
111
|
-
|
112
|
-
options = options.delete_if{|k,v| v.nil?}
|
113
|
-
|
114
|
-
html = message.body_html
|
115
|
-
text = message.body_text
|
116
|
-
|
117
|
-
if message.multipart?
|
118
|
-
options["HtmlBody"] = html
|
119
|
-
options["TextBody"] = text
|
120
|
-
elsif html
|
121
|
-
options["HtmlBody"] = html
|
85
|
+
def send_through_postmark(message) #:nodoc:
|
86
|
+
@retries = 0
|
87
|
+
begin
|
88
|
+
HttpClient.post("email", Postmark::Json.encode(convert_message_to_options_hash(message)))
|
89
|
+
rescue DeliveryError => e
|
90
|
+
if @retries < max_retries
|
91
|
+
@retries += 1
|
92
|
+
retry
|
122
93
|
else
|
123
|
-
|
94
|
+
raise
|
124
95
|
end
|
125
|
-
|
126
|
-
options
|
127
96
|
end
|
97
|
+
end
|
128
98
|
|
129
|
-
|
130
|
-
|
131
|
-
|
99
|
+
def convert_message_to_options_hash(message)
|
100
|
+
options = Hash.new
|
101
|
+
headers = extract_headers_according_to_message_format(message)
|
132
102
|
|
133
|
-
|
103
|
+
options["From"] = message['from'].to_s if message.from
|
104
|
+
options["ReplyTo"] = Array[message.reply_to].flatten.join(", ") if message.reply_to
|
105
|
+
options["To"] = message['to'].to_s if message.to
|
106
|
+
options["Cc"] = message['cc'].to_s if message.cc
|
107
|
+
options["Bcc"] = Array[message.bcc].flatten.join(", ") if message.bcc
|
108
|
+
options["Subject"] = message.subject
|
109
|
+
options["Attachments"] = message.postmark_attachments
|
110
|
+
options["Tag"] = message.tag.to_s if message.tag
|
111
|
+
options["Headers"] = headers if headers.size > 0
|
134
112
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
113
|
+
options = options.delete_if{|k,v| v.nil?}
|
114
|
+
|
115
|
+
html = message.body_html
|
116
|
+
text = message.body_text
|
117
|
+
|
118
|
+
if message.multipart?
|
119
|
+
options["HtmlBody"] = html
|
120
|
+
options["TextBody"] = text
|
121
|
+
elsif html
|
122
|
+
options["HtmlBody"] = html
|
123
|
+
else
|
124
|
+
options["TextBody"] = text
|
143
125
|
end
|
144
126
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
127
|
+
options
|
128
|
+
end
|
129
|
+
|
130
|
+
def delivery_stats
|
131
|
+
HttpClient.get("deliverystats")
|
132
|
+
end
|
133
|
+
|
134
|
+
protected
|
135
|
+
|
136
|
+
def extract_headers_according_to_message_format(message)
|
137
|
+
if defined?(TMail) && message.is_a?(TMail::Mail)
|
138
|
+
headers = extract_tmail_headers(message)
|
139
|
+
elsif defined?(Mail) && message.kind_of?(Mail::Message)
|
140
|
+
headers = extract_mail_headers(message)
|
141
|
+
else
|
142
|
+
raise "Can't convert message to a valid hash of API options. Unknown message format."
|
155
143
|
end
|
144
|
+
end
|
156
145
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
headers
|
146
|
+
def extract_mail_headers(message)
|
147
|
+
headers = []
|
148
|
+
message.header.fields.each do |field|
|
149
|
+
key = field.name
|
150
|
+
value = field.value
|
151
|
+
next if bogus_headers.include? key.downcase
|
152
|
+
name = key.split(/-/).map {|i| i.capitalize }.join('-')
|
153
|
+
headers << { "Name" => name, "Value" => value }
|
165
154
|
end
|
155
|
+
headers
|
156
|
+
end
|
166
157
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
cc bcc
|
174
|
-
subject tag
|
175
|
-
attachment
|
176
|
-
]
|
158
|
+
def extract_tmail_headers(message)
|
159
|
+
headers = []
|
160
|
+
message.each_header do |key, value|
|
161
|
+
next if bogus_headers.include? key.downcase
|
162
|
+
name = key.split(/-/).map {|i| i.capitalize }.join('-')
|
163
|
+
headers << { "Name" => name, "Value" => value.body }
|
177
164
|
end
|
165
|
+
headers
|
166
|
+
end
|
178
167
|
|
168
|
+
def bogus_headers
|
169
|
+
%q[
|
170
|
+
return-path x-pm-rcpt
|
171
|
+
from reply-to
|
172
|
+
sender received
|
173
|
+
date content-type
|
174
|
+
cc bcc
|
175
|
+
subject tag
|
176
|
+
attachment
|
177
|
+
]
|
179
178
|
end
|
180
179
|
|
180
|
+
|
181
|
+
|
181
182
|
self.response_parser_class = nil
|
182
183
|
|
183
184
|
end
|
data/lib/postmark/http_client.rb
CHANGED
@@ -2,76 +2,75 @@ require 'cgi'
|
|
2
2
|
|
3
3
|
module Postmark
|
4
4
|
module HttpClient
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
extend self
|
6
|
+
def post(path, data = '')
|
7
|
+
handle_response(http.post(url_path(path), data, headers))
|
8
|
+
end
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
def put(path, data = '')
|
11
|
+
handle_response(http.put(url_path(path), data, headers))
|
12
|
+
end
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
def get(path, query = {})
|
15
|
+
handle_response(http.get(url_path(path + to_query_string(query)), headers))
|
16
|
+
end
|
17
17
|
|
18
|
-
|
18
|
+
protected
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
20
|
+
def to_query_string(hash)
|
21
|
+
return "" if hash.empty?
|
22
|
+
"?" + hash.map { |key, value| "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}" }.join("&")
|
23
|
+
end
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
25
|
+
def protocol
|
26
|
+
Postmark.secure ? "https" : "http"
|
27
|
+
end
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
29
|
+
def url
|
30
|
+
URI.parse("#{protocol}://#{Postmark.host}:#{Postmark.port}/")
|
31
|
+
end
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
end
|
33
|
+
def handle_response(response)
|
34
|
+
case response.code.to_i
|
35
|
+
when 200
|
36
|
+
return Postmark::Json.decode(response.body)
|
37
|
+
when 401
|
38
|
+
raise InvalidApiKeyError, error_message(response.body)
|
39
|
+
when 422
|
40
|
+
raise InvalidMessageError, error_message(response.body)
|
41
|
+
when 500
|
42
|
+
raise InternalServerError, response.body
|
43
|
+
else
|
44
|
+
raise UnknownError, response
|
46
45
|
end
|
46
|
+
end
|
47
47
|
|
48
|
-
|
49
|
-
|
50
|
-
|
48
|
+
def headers
|
49
|
+
@headers ||= HEADERS.merge({ "X-Postmark-Server-Token" => Postmark.api_key.to_s })
|
50
|
+
end
|
51
51
|
|
52
|
-
|
53
|
-
|
54
|
-
|
52
|
+
def url_path(path)
|
53
|
+
Postmark.path_prefix + path
|
54
|
+
end
|
55
55
|
|
56
|
-
|
57
|
-
|
58
|
-
|
56
|
+
def http
|
57
|
+
@http ||= build_http
|
58
|
+
end
|
59
59
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
60
|
+
def build_http
|
61
|
+
http = Net::HTTP::Proxy(Postmark.proxy_host,
|
62
|
+
Postmark.proxy_port,
|
63
|
+
Postmark.proxy_user,
|
64
|
+
Postmark.proxy_pass).new(url.host, url.port)
|
65
65
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
66
|
+
http.read_timeout = Postmark.http_read_timeout
|
67
|
+
http.open_timeout = Postmark.http_open_timeout
|
68
|
+
http.use_ssl = !!Postmark.secure
|
69
|
+
http
|
70
|
+
end
|
71
71
|
|
72
|
-
|
73
|
-
|
74
|
-
end
|
72
|
+
def error_message(response_body)
|
73
|
+
Postmark::Json.decode(response_body)["Message"]
|
75
74
|
end
|
76
75
|
end
|
77
76
|
end
|