postmark 1.8.1 → 1.21.3
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 +5 -5
- data/.gitignore +2 -0
- data/.travis.yml +8 -5
- data/CHANGELOG.rdoc +86 -0
- data/CONTRIBUTING.md +18 -0
- data/Gemfile +6 -5
- data/LICENSE +1 -1
- data/README.md +34 -607
- data/RELEASE.md +12 -0
- data/VERSION +1 -1
- data/gemfiles/Gemfile.legacy +5 -4
- data/lib/postmark.rb +1 -18
- data/lib/postmark/account_api_client.rb +55 -1
- data/lib/postmark/api_client.rb +145 -17
- data/lib/postmark/bounce.rb +0 -4
- data/lib/postmark/client.rb +12 -4
- data/lib/postmark/error.rb +127 -0
- data/lib/postmark/handlers/mail.rb +10 -4
- data/lib/postmark/helpers/message_helper.rb +4 -0
- data/lib/postmark/http_client.rb +20 -32
- data/lib/postmark/mail_message_converter.rb +18 -5
- data/lib/postmark/message_extensions/mail.rb +83 -8
- data/lib/postmark/version.rb +1 -1
- data/postmark.gemspec +1 -1
- data/postmark.png +0 -0
- data/spec/integration/account_api_client_spec.rb +42 -10
- data/spec/integration/api_client_hashes_spec.rb +32 -49
- data/spec/integration/api_client_messages_spec.rb +33 -52
- data/spec/integration/api_client_resources_spec.rb +12 -44
- data/spec/integration/mail_delivery_method_spec.rb +21 -23
- data/spec/spec_helper.rb +4 -7
- data/spec/support/custom_matchers.rb +44 -0
- data/spec/support/shared_examples.rb +16 -16
- data/spec/unit/postmark/account_api_client_spec.rb +239 -45
- data/spec/unit/postmark/api_client_spec.rb +792 -406
- data/spec/unit/postmark/bounce_spec.rb +40 -62
- data/spec/unit/postmark/client_spec.rb +0 -6
- data/spec/unit/postmark/error_spec.rb +231 -0
- data/spec/unit/postmark/handlers/mail_spec.rb +59 -27
- data/spec/unit/postmark/helpers/hash_helper_spec.rb +5 -6
- data/spec/unit/postmark/helpers/message_helper_spec.rb +60 -11
- data/spec/unit/postmark/http_client_spec.rb +76 -61
- data/spec/unit/postmark/inbound_spec.rb +34 -34
- data/spec/unit/postmark/inflector_spec.rb +11 -13
- data/spec/unit/postmark/json_spec.rb +2 -2
- data/spec/unit/postmark/mail_message_converter_spec.rb +250 -81
- data/spec/unit/postmark/message_extensions/mail_spec.rb +249 -38
- data/spec/unit/postmark_spec.rb +37 -37
- metadata +41 -11
data/RELEASE.md
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
New versions of the gem are cut by the Postmark team, this is a quick guide to ensuring a smooth release.
|
2
|
+
|
3
|
+
1. Verify all builds are passing on Travis for your branch.
|
4
|
+
1. Merge in your branch to master.
|
5
|
+
1. Update VERSION and lib/postmark/version.rb with the new version.
|
6
|
+
1. Update CHANGELOG.rdoc with a brief description of the changes.
|
7
|
+
1. Commit to git with a comment of "Bump version to x.y.z".
|
8
|
+
1. run `rake release` - This will push to github(with the version tag) and rubygems with the version in lib/postmark/version.rb.
|
9
|
+
*Note that if you're on Bundler 1.17 there's a bug that hides the prompt for your OTP. If it hangs after adding the tag then it's asking for your OTP, enter your OTP and press Enter. Bundler 2.x and beyond resolved this issue. *
|
10
|
+
1. Verify the new version is on [github](https://github.com/wildbit/postmark-gem) and [rubygems](https://rubygems.org/gems/postmark).
|
11
|
+
1. Create a new release for the version on [Github releases](https://github.com/wildbit/postmark-gem/releases).
|
12
|
+
1. Add or update any related content to the [wiki](https://github.com/wildbit/postmark-gem/wiki).
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.21.3
|
data/gemfiles/Gemfile.legacy
CHANGED
@@ -6,11 +6,12 @@ gem 'rake', '< 11.0.0'
|
|
6
6
|
gem 'json', '< 2.0.0'
|
7
7
|
|
8
8
|
group :test do
|
9
|
-
gem 'rspec', '~>
|
10
|
-
gem '
|
9
|
+
gem 'rspec', '~> 3.7'
|
10
|
+
gem 'rspec-its', '~> 1.2'
|
11
|
+
gem 'fakeweb', :git => 'https://github.com/chrisk/fakeweb.git'
|
11
12
|
gem 'fakeweb-matcher'
|
12
13
|
gem 'mime-types', '~> 1.25.1'
|
13
14
|
gem 'activesupport', '~> 3.2.0'
|
14
15
|
gem 'i18n', '~> 0.6.0'
|
15
|
-
gem 'yajl-ruby', '~> 1.0', :platforms => [:mingw, :mswin, :ruby]
|
16
|
-
end
|
16
|
+
gem 'yajl-ruby', '~> 1.0', '< 1.4.0', :platforms => [:mingw, :mswin, :ruby]
|
17
|
+
end
|
data/lib/postmark.rb
CHANGED
@@ -10,6 +10,7 @@ require 'postmark/mail_message_converter'
|
|
10
10
|
require 'postmark/bounce'
|
11
11
|
require 'postmark/inbound'
|
12
12
|
require 'postmark/json'
|
13
|
+
require 'postmark/error'
|
13
14
|
require 'postmark/http_client'
|
14
15
|
require 'postmark/client'
|
15
16
|
require 'postmark/api_client'
|
@@ -18,24 +19,6 @@ require 'postmark/message_extensions/mail'
|
|
18
19
|
require 'postmark/handlers/mail'
|
19
20
|
|
20
21
|
module Postmark
|
21
|
-
|
22
|
-
class DeliveryError < StandardError
|
23
|
-
attr_accessor :error_code, :full_response
|
24
|
-
|
25
|
-
def initialize(message = nil, error_code = nil, full_response = nil)
|
26
|
-
super(message)
|
27
|
-
self.error_code = error_code
|
28
|
-
self.full_response = full_response
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
class UnknownError < DeliveryError; end
|
33
|
-
class InvalidApiKeyError < DeliveryError; end
|
34
|
-
class InvalidMessageError < DeliveryError; end
|
35
|
-
class InternalServerError < DeliveryError; end
|
36
|
-
class UnknownMessageType < DeliveryError; end
|
37
|
-
class TimeoutError < DeliveryError; end
|
38
|
-
|
39
22
|
module ResponseParsers
|
40
23
|
autoload :Json, 'postmark/response_parsers/json'
|
41
24
|
autoload :ActiveSupport, 'postmark/response_parsers/active_support'
|
@@ -61,6 +61,54 @@ module Postmark
|
|
61
61
|
end
|
62
62
|
alias_method :delete_signature, :delete_sender
|
63
63
|
|
64
|
+
def domains(options = {})
|
65
|
+
find_each('domains', 'Domains', options)
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_domains(options = {})
|
69
|
+
load_batch('domains', 'Domains', options).last
|
70
|
+
end
|
71
|
+
|
72
|
+
def get_domains_count(options = {})
|
73
|
+
get_resource_count('domains', options)
|
74
|
+
end
|
75
|
+
|
76
|
+
def get_domain(id)
|
77
|
+
format_response http_client.get("domains/#{id.to_i}")
|
78
|
+
end
|
79
|
+
|
80
|
+
def create_domain(attributes = {})
|
81
|
+
data = serialize(HashHelper.to_postmark(attributes))
|
82
|
+
|
83
|
+
format_response http_client.post('domains', data)
|
84
|
+
end
|
85
|
+
|
86
|
+
def update_domain(id, attributes = {})
|
87
|
+
data = serialize(HashHelper.to_postmark(attributes))
|
88
|
+
|
89
|
+
format_response http_client.put("domains/#{id.to_i}", data)
|
90
|
+
end
|
91
|
+
|
92
|
+
def verify_domain_dkim(id)
|
93
|
+
format_response http_client.put("domains/#{id.to_i}/verifydkim")
|
94
|
+
end
|
95
|
+
|
96
|
+
def verify_domain_return_path(id)
|
97
|
+
format_response http_client.put("domains/#{id.to_i}/verifyreturnpath")
|
98
|
+
end
|
99
|
+
|
100
|
+
def verified_domain_spf?(id)
|
101
|
+
!!http_client.post("domains/#{id.to_i}/verifyspf")['SPFVerified']
|
102
|
+
end
|
103
|
+
|
104
|
+
def rotate_domain_dkim(id)
|
105
|
+
format_response http_client.post("domains/#{id.to_i}/rotatedkim")
|
106
|
+
end
|
107
|
+
|
108
|
+
def delete_domain(id)
|
109
|
+
format_response http_client.delete("domains/#{id.to_i}")
|
110
|
+
end
|
111
|
+
|
64
112
|
def servers(options = {})
|
65
113
|
find_each('servers', 'Servers', options)
|
66
114
|
end
|
@@ -91,6 +139,12 @@ module Postmark
|
|
91
139
|
format_response http_client.delete("servers/#{id.to_i}")
|
92
140
|
end
|
93
141
|
|
142
|
+
def push_templates(attributes = {})
|
143
|
+
data = serialize(HashHelper.to_postmark(attributes))
|
144
|
+
_, batch = format_batch_response(http_client.put('templates/push', data), "Templates")
|
145
|
+
batch
|
146
|
+
end
|
147
|
+
|
94
148
|
end
|
95
149
|
|
96
|
-
end
|
150
|
+
end
|
data/lib/postmark/api_client.rb
CHANGED
@@ -27,6 +27,11 @@ module Postmark
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def deliver_message(message)
|
30
|
+
if message.templated?
|
31
|
+
raise ArgumentError,
|
32
|
+
"Please use #{self.class}#deliver_message_with_template to deliver messages with templates."
|
33
|
+
end
|
34
|
+
|
30
35
|
data = serialize(message.to_postmark_hash)
|
31
36
|
|
32
37
|
with_retries do
|
@@ -37,7 +42,26 @@ module Postmark
|
|
37
42
|
end
|
38
43
|
end
|
39
44
|
|
45
|
+
def deliver_message_with_template(message)
|
46
|
+
raise ArgumentError, 'Templated delivery requested, but the template is missing.' unless message.templated?
|
47
|
+
|
48
|
+
data = serialize(message.to_postmark_hash)
|
49
|
+
|
50
|
+
with_retries do
|
51
|
+
response, error = take_response_of { http_client.post("email/withTemplate", data) }
|
52
|
+
update_message(message, response)
|
53
|
+
raise error if error
|
54
|
+
format_response(response, true)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
40
58
|
def deliver_messages(messages)
|
59
|
+
if messages.any? { |m| m.templated? }
|
60
|
+
raise ArgumentError,
|
61
|
+
"Some of the provided messages have templates. Please use " \
|
62
|
+
"#{self.class}#deliver_messages_with_templates to deliver those."
|
63
|
+
end
|
64
|
+
|
41
65
|
in_batches(messages) do |batch, offset|
|
42
66
|
data = serialize(batch.map { |m| m.to_postmark_hash })
|
43
67
|
|
@@ -51,6 +75,24 @@ module Postmark
|
|
51
75
|
end
|
52
76
|
end
|
53
77
|
|
78
|
+
def deliver_messages_with_templates(messages)
|
79
|
+
unless messages.all? { |m| m.templated? }
|
80
|
+
raise ArgumentError, 'Templated delivery requested, but one or more messages lack templates.'
|
81
|
+
end
|
82
|
+
|
83
|
+
in_batches(messages) do |batch, offset|
|
84
|
+
data = serialize(batch.map { |m| m.to_postmark_hash })
|
85
|
+
|
86
|
+
with_retries do
|
87
|
+
http_client.post("email/batchWithTemplates", data).tap do |response|
|
88
|
+
response.each_with_index do |r, i|
|
89
|
+
update_message(messages[offset + i], r)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
54
96
|
def delivery_stats
|
55
97
|
response = format_response(http_client.get("deliverystats"), true)
|
56
98
|
|
@@ -93,10 +135,6 @@ module Postmark
|
|
93
135
|
batch
|
94
136
|
end
|
95
137
|
|
96
|
-
def get_bounced_tags
|
97
|
-
http_client.get("bounces/tags")
|
98
|
-
end
|
99
|
-
|
100
138
|
def get_bounce(id)
|
101
139
|
format_response http_client.get("bounces/#{id}")
|
102
140
|
end
|
@@ -113,23 +151,44 @@ module Postmark
|
|
113
151
|
find_each('messages/outbound/opens', 'Opens', options)
|
114
152
|
end
|
115
153
|
|
154
|
+
def clicks(options = {})
|
155
|
+
find_each('messages/outbound/clicks', 'Clicks', options)
|
156
|
+
end
|
157
|
+
|
116
158
|
def get_opens(options = {})
|
117
159
|
_, batch = load_batch('messages/outbound/opens', 'Opens', options)
|
118
160
|
batch
|
119
161
|
end
|
120
162
|
|
121
|
-
def
|
163
|
+
def get_clicks(options = {})
|
164
|
+
_, batch = load_batch('messages/outbound/clicks', 'Clicks', options)
|
165
|
+
batch
|
166
|
+
end
|
167
|
+
|
168
|
+
def get_opens_by_message_id(message_id, options = {})
|
122
169
|
_, batch = load_batch("messages/outbound/opens/#{message_id}",
|
123
170
|
'Opens',
|
124
171
|
options)
|
125
172
|
batch
|
126
173
|
end
|
127
174
|
|
175
|
+
def get_clicks_by_message_id(message_id, options = {})
|
176
|
+
_, batch = load_batch("messages/outbound/clicks/#{message_id}",
|
177
|
+
'Clicks',
|
178
|
+
options)
|
179
|
+
batch
|
180
|
+
end
|
181
|
+
|
128
182
|
def opens_by_message_id(message_id, options = {})
|
129
183
|
find_each("messages/outbound/opens/#{message_id}", 'Opens', options)
|
130
184
|
end
|
131
185
|
|
186
|
+
def clicks_by_message_id(message_id, options = {})
|
187
|
+
find_each("messages/outbound/clicks/#{message_id}", 'Clicks', options)
|
188
|
+
end
|
189
|
+
|
132
190
|
def create_trigger(type, options)
|
191
|
+
type = Postmark::Inflector.to_postmark(type).downcase
|
133
192
|
data = serialize(HashHelper.to_postmark(options))
|
134
193
|
format_response http_client.post("triggers/#{type}", data)
|
135
194
|
end
|
@@ -138,22 +197,20 @@ module Postmark
|
|
138
197
|
format_response http_client.get("triggers/#{type}/#{id}")
|
139
198
|
end
|
140
199
|
|
141
|
-
def update_trigger(type, id, options)
|
142
|
-
data = serialize(HashHelper.to_postmark(options))
|
143
|
-
format_response http_client.put("triggers/#{type}/#{id}", data)
|
144
|
-
end
|
145
|
-
|
146
200
|
def delete_trigger(type, id)
|
201
|
+
type = Postmark::Inflector.to_postmark(type).downcase
|
147
202
|
format_response http_client.delete("triggers/#{type}/#{id}")
|
148
203
|
end
|
149
204
|
|
150
205
|
def get_triggers(type, options = {})
|
151
|
-
|
206
|
+
type = Postmark::Inflector.to_postmark(type)
|
207
|
+
_, batch = load_batch("triggers/#{type.downcase}", type, options)
|
152
208
|
batch
|
153
209
|
end
|
154
210
|
|
155
211
|
def triggers(type, options = {})
|
156
|
-
|
212
|
+
type = Postmark::Inflector.to_postmark(type)
|
213
|
+
find_each("triggers/#{type.downcase}", type, options)
|
157
214
|
end
|
158
215
|
|
159
216
|
def server_info
|
@@ -221,22 +278,94 @@ module Postmark
|
|
221
278
|
end
|
222
279
|
end
|
223
280
|
|
281
|
+
def deliver_in_batches_with_templates(message_hashes)
|
282
|
+
in_batches(message_hashes) do |batch, offset|
|
283
|
+
mapped = batch.map { |h| MessageHelper.to_postmark(h) }
|
284
|
+
data = serialize(:Messages => mapped)
|
285
|
+
|
286
|
+
with_retries do
|
287
|
+
http_client.post('email/batchWithTemplates', data)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
224
292
|
def get_stats_totals(options = {})
|
225
293
|
format_response(http_client.get('stats/outbound', options))
|
226
294
|
end
|
227
295
|
|
228
296
|
def get_stats_counts(stat, options = {})
|
229
297
|
url = "stats/outbound/#{stat}"
|
230
|
-
|
231
298
|
url << "/#{options[:type]}" if options.has_key?(:type)
|
232
299
|
|
233
300
|
response = format_response(http_client.get(url, options))
|
234
|
-
|
235
301
|
response[:days].map! { |d| HashHelper.to_ruby(d) }
|
236
|
-
|
237
302
|
response
|
238
303
|
end
|
239
304
|
|
305
|
+
def get_webhooks(options = {})
|
306
|
+
options = HashHelper.to_postmark(options)
|
307
|
+
_, batch = load_batch('webhooks', 'Webhooks', options)
|
308
|
+
batch
|
309
|
+
end
|
310
|
+
|
311
|
+
def get_webhook(id)
|
312
|
+
format_response http_client.get("webhooks/#{id}")
|
313
|
+
end
|
314
|
+
|
315
|
+
def create_webhook(attributes = {})
|
316
|
+
data = serialize(HashHelper.to_postmark(attributes))
|
317
|
+
|
318
|
+
format_response http_client.post('webhooks', data)
|
319
|
+
end
|
320
|
+
|
321
|
+
def update_webhook(id, attributes = {})
|
322
|
+
data = serialize(HashHelper.to_postmark(attributes))
|
323
|
+
|
324
|
+
format_response http_client.put("webhooks/#{id}", data)
|
325
|
+
end
|
326
|
+
|
327
|
+
def delete_webhook(id)
|
328
|
+
format_response http_client.delete("webhooks/#{id}")
|
329
|
+
end
|
330
|
+
|
331
|
+
def get_message_streams(options = {})
|
332
|
+
_, batch = load_batch('message-streams', 'MessageStreams', options)
|
333
|
+
batch
|
334
|
+
end
|
335
|
+
|
336
|
+
def message_streams(options = {})
|
337
|
+
find_each('message-streams', 'MessageStreams', options)
|
338
|
+
end
|
339
|
+
|
340
|
+
def get_message_stream(id)
|
341
|
+
format_response(http_client.get("message-streams/#{id}"))
|
342
|
+
end
|
343
|
+
|
344
|
+
def create_message_stream(attributes = {})
|
345
|
+
data = serialize(HashHelper.to_postmark(attributes))
|
346
|
+
format_response(http_client.post('message-streams', data))
|
347
|
+
end
|
348
|
+
|
349
|
+
def update_message_stream(id, attributes)
|
350
|
+
data = serialize(HashHelper.to_postmark(attributes))
|
351
|
+
format_response(http_client.patch("message-streams/#{id}", data))
|
352
|
+
end
|
353
|
+
|
354
|
+
def dump_suppressions(stream_id, options = {})
|
355
|
+
_, batch = load_batch("message-streams/#{stream_id}/suppressions/dump", 'Suppressions', options)
|
356
|
+
batch
|
357
|
+
end
|
358
|
+
|
359
|
+
def create_suppressions(stream_id, email_addresses)
|
360
|
+
data = serialize(:Suppressions => Array(email_addresses).map { |e| HashHelper.to_postmark(:email_address => e) })
|
361
|
+
format_response(http_client.post("message-streams/#{stream_id}/suppressions", data))
|
362
|
+
end
|
363
|
+
|
364
|
+
def delete_suppressions(stream_id, email_addresses)
|
365
|
+
data = serialize(:Suppressions => Array(email_addresses).map { |e| HashHelper.to_postmark(:email_address => e) })
|
366
|
+
format_response(http_client.post("message-streams/#{stream_id}/suppressions/delete", data))
|
367
|
+
end
|
368
|
+
|
240
369
|
protected
|
241
370
|
|
242
371
|
def in_batches(messages)
|
@@ -249,7 +378,7 @@ module Postmark
|
|
249
378
|
|
250
379
|
def update_message(message, response)
|
251
380
|
response ||= {}
|
252
|
-
message['Message-
|
381
|
+
message['X-PM-Message-Id'] = response['MessageID']
|
253
382
|
message.delivered = response['ErrorCode'] && response['ErrorCode'].zero?
|
254
383
|
message.postmark_response = response
|
255
384
|
end
|
@@ -265,6 +394,5 @@ module Postmark
|
|
265
394
|
path = options.delete(:inbound) ? 'messages/inbound' : 'messages/outbound'
|
266
395
|
[path, messages_key, options]
|
267
396
|
end
|
268
|
-
|
269
397
|
end
|
270
398
|
end
|
data/lib/postmark/bounce.rb
CHANGED
data/lib/postmark/client.rb
CHANGED
@@ -37,12 +37,16 @@ module Postmark
|
|
37
37
|
|
38
38
|
def with_retries
|
39
39
|
yield
|
40
|
-
rescue
|
40
|
+
rescue HttpServerError, HttpClientError, TimeoutError, Errno::EINVAL,
|
41
|
+
Errno::ECONNRESET, Errno::ECONNREFUSED, EOFError,
|
42
|
+
Net::ProtocolError, SocketError => e
|
41
43
|
retries = retries ? retries + 1 : 1
|
42
|
-
|
44
|
+
retriable = !e.respond_to?(:retry?) || e.retry?
|
45
|
+
|
46
|
+
if retriable && retries < self.max_retries
|
43
47
|
retry
|
44
48
|
else
|
45
|
-
raise
|
49
|
+
raise e
|
46
50
|
end
|
47
51
|
end
|
48
52
|
|
@@ -52,7 +56,7 @@ module Postmark
|
|
52
56
|
|
53
57
|
def take_response_of
|
54
58
|
[yield, nil]
|
55
|
-
rescue
|
59
|
+
rescue HttpServerError => e
|
56
60
|
[e.full_response || {}, e]
|
57
61
|
end
|
58
62
|
|
@@ -76,6 +80,10 @@ module Postmark
|
|
76
80
|
options[:offset] ||= 0
|
77
81
|
options[:count] ||= 30
|
78
82
|
response = http_client.get(path, options)
|
83
|
+
format_batch_response(response, name)
|
84
|
+
end
|
85
|
+
|
86
|
+
def format_batch_response(response, name)
|
79
87
|
[response['TotalCount'], format_response(response[name])]
|
80
88
|
end
|
81
89
|
|