postmark 1.10.0 → 1.11.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 +4 -4
- data/CHANGELOG.rdoc +7 -0
- data/CONTRIBUTING.md +18 -0
- data/Gemfile +6 -5
- data/LICENSE +1 -1
- data/README.md +62 -9
- data/VERSION +1 -1
- data/gemfiles/Gemfile.legacy +4 -3
- data/lib/postmark.rb +1 -18
- data/lib/postmark/api_client.rb +32 -1
- data/lib/postmark/client.rb +8 -4
- data/lib/postmark/error.rb +117 -0
- data/lib/postmark/http_client.rb +7 -25
- data/lib/postmark/mail_message_converter.rb +1 -1
- data/lib/postmark/version.rb +1 -1
- data/spec/integration/account_api_client_spec.rb +2 -2
- data/spec/integration/api_client_messages_spec.rb +3 -3
- data/spec/spec_helper.rb +4 -1
- data/spec/support/custom_matchers.rb +30 -0
- data/spec/unit/postmark/account_api_client_spec.rb +22 -22
- data/spec/unit/postmark/api_client_spec.rb +164 -54
- data/spec/unit/postmark/bounce_spec.rb +10 -10
- data/spec/unit/postmark/error_spec.rb +218 -0
- data/spec/unit/postmark/handlers/mail_spec.rb +5 -5
- data/spec/unit/postmark/http_client_spec.rb +4 -5
- data/spec/unit/postmark/mail_message_converter_spec.rb +6 -0
- data/spec/unit/postmark/message_extensions/mail_spec.rb +6 -6
- data/spec/unit/postmark_spec.rb +5 -5
- metadata +33 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b07a5971093718b77a48cb6a5917167ba39d2d6e
|
4
|
+
data.tar.gz: a3f227ff7b37009da8a05fef39ae9a971fefe990
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1dc9c40d2e9c7410c371d681cebcf90d27b25898c932a729d6dabb8f9391e9060cfa38d8a36b9abde7218ca0be45d8d048af36e91bfeb456980c9690fe8f3ee3
|
7
|
+
data.tar.gz: ef0e6f6cb70ba23b0ad301eac397a2f26287b66131986c2bce421d0b9a5dffc66538d4f53c644db54791de0b0689777234436048d2e5a11f473bdb2480d47221
|
data/CHANGELOG.rdoc
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
= Changelog
|
2
2
|
|
3
|
+
== 1.11.0
|
4
|
+
|
5
|
+
* New, improved, and backwards-compatible gem errors (see README).
|
6
|
+
* Added support for retrieving message clicks using the Messages API.
|
7
|
+
* Added support for sending templated message in batches.
|
8
|
+
* Added support for assigning link tracking mode via `Mail::Message` headers.
|
9
|
+
|
3
10
|
== 1.10.0
|
4
11
|
|
5
12
|
* Fix a bug when open tracking flag is set to false by default, when open tracking flag is not set by a user.
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# Before you report an issue or submit a pull request
|
2
|
+
|
3
|
+
*If you are blocked or need help with Postmark, please [contact
|
4
|
+
Postmark Support](https://postmarkapp.com/contact)*. For other, non-urgent
|
5
|
+
cases you’re welcome to report a bug and/or contribute to this project. We will
|
6
|
+
make our best effort to review your contributions and triage any bug reports in
|
7
|
+
a timely fashion.
|
8
|
+
|
9
|
+
If you’d like to submit a pull request:
|
10
|
+
|
11
|
+
* Fork the project.
|
12
|
+
* Make your feature addition or bug fix.
|
13
|
+
* Add tests for it. This is important to prevent future regressions.
|
14
|
+
* Do not mess with rakefile, version, or history.
|
15
|
+
* Update the CHANGELOG, list your changes under Unreleased.
|
16
|
+
* Update the README if necessary.
|
17
|
+
* Write short, descriptive commit messages, following the format used in therepo.
|
18
|
+
* Send a pull request. Bonus points for topic branches.
|
data/Gemfile
CHANGED
@@ -4,11 +4,12 @@ source "http://rubygems.org"
|
|
4
4
|
gemspec
|
5
5
|
|
6
6
|
group :test do
|
7
|
-
gem 'rspec', '~>
|
8
|
-
gem '
|
7
|
+
gem 'rspec', '~> 3.7'
|
8
|
+
gem 'rspec-its', '~> 1.2'
|
9
|
+
gem 'fakeweb', :git => 'https://github.com/chrisk/fakeweb.git'
|
9
10
|
gem 'fakeweb-matcher'
|
10
|
-
gem 'mime-types'
|
11
|
-
gem 'activesupport'
|
11
|
+
gem 'mime-types'
|
12
|
+
gem 'activesupport'
|
12
13
|
gem 'i18n', '~> 0.6.0'
|
13
14
|
gem 'yajl-ruby', '~> 1.0', :platforms => [:mingw, :mswin, :ruby]
|
14
|
-
end
|
15
|
+
end
|
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -660,6 +660,66 @@ bounce.activate # reactivate hard bounce
|
|
660
660
|
# => #<Postmark::Bounce:0x007ff09c04ae18 @id=580516117, @email="sheldon@bigbangtheory.com", @bounced_at=2012-10-21 00:01:56 +0800, @type="HardBounce", @name=nil, @details="smtp;550 5.1.1 The email account that you tried to reach does not exist. Please try double-checking the recipient's email address for typos or unnecessary spaces. Learn more at http://support.google.com/mail/bin/answer.py?answer=6596 c13si5382730vcw.23", @tag=nil, @dump_available=false, @inactive=true, @can_activate=true, @message_id="876d40fe-ab2a-4925-9d6f-8d5e4f4926f5", @subject="Re: What, to you, is a large crowd?">
|
661
661
|
```
|
662
662
|
|
663
|
+
## Error handling
|
664
|
+
|
665
|
+
For the gem version `1.11.0` and above, use the following template to handle the errors you care about:
|
666
|
+
|
667
|
+
``` ruby
|
668
|
+
def handle_postmark_errors
|
669
|
+
# Any Postmark request
|
670
|
+
yield
|
671
|
+
error
|
672
|
+
rescue Postmark::InvalidApiKeyError => error
|
673
|
+
# Authentication error
|
674
|
+
# TODO: Make sure your API token is correct
|
675
|
+
puts error
|
676
|
+
error
|
677
|
+
rescue Postmark::TimeoutError => error
|
678
|
+
# Network timeout, auto-retried :max_retries times
|
679
|
+
# TODO: Save message locally, try again once the network issues are resolved
|
680
|
+
# Consider increasing `http_open_timeout` and `http_read_timeout`.
|
681
|
+
puts error
|
682
|
+
error
|
683
|
+
rescue Postmark::InternalServerError => error
|
684
|
+
# Postmark server error, auto-retried :max_retries times
|
685
|
+
# TODO: Save message locally, try again later.
|
686
|
+
puts error
|
687
|
+
error
|
688
|
+
rescue Postmark::HttpClientError => error
|
689
|
+
# Corrupted response from Postmark, auto-retried :max_retries times
|
690
|
+
# TODO: Save message locally, try again later.
|
691
|
+
puts error
|
692
|
+
error
|
693
|
+
rescue Postmark::InactiveRecipientError => error
|
694
|
+
# You tried to send to one or more recipients marked as inactive in
|
695
|
+
# Postmark
|
696
|
+
# TODO: Mark listed recipients as inactive in your local db or reactivate
|
697
|
+
# using the Bounces API
|
698
|
+
puts "Inactive recipients: #{error.recipients.join(', ')}"
|
699
|
+
puts error
|
700
|
+
error
|
701
|
+
rescue Postmark::ApiInputError => error
|
702
|
+
# Postmark rejected your request as invalid
|
703
|
+
# TODO: Look up the error code and resolve the problem in your app
|
704
|
+
# List of supported error codes:
|
705
|
+
# https://postmarkapp.com/developer/api/overview#error-codes
|
706
|
+
puts "#{error.error_code} #{error.message}"
|
707
|
+
puts error
|
708
|
+
error
|
709
|
+
rescue Postmark::Error => error
|
710
|
+
# All other Postmark errors
|
711
|
+
# TODO: Log and review as needed
|
712
|
+
puts error
|
713
|
+
error
|
714
|
+
rescue Errno::EINVAL, Errno::ECONNRESET, Errno::ECONNREFUSED,
|
715
|
+
EOFError, Net::ProtocolError, SocketError => error
|
716
|
+
# Standard Ruby network errors, auto-retried :max_reties times
|
717
|
+
# TODO: Save message locally, resolve network issues, try again.
|
718
|
+
puts error
|
719
|
+
error
|
720
|
+
end
|
721
|
+
```
|
722
|
+
|
663
723
|
## Requirements
|
664
724
|
|
665
725
|
You will need a Postmark account, server and sender signature set up to use it.
|
@@ -678,15 +738,8 @@ Postmark.response_parser_class = :Json # :ActiveSupport or :Yajl are also suppor
|
|
678
738
|
|
679
739
|
## Note on Patches/Pull Requests
|
680
740
|
|
681
|
-
|
682
|
-
* Make your feature addition or bug fix.
|
683
|
-
* Add tests for it. This is important to prevent future regressions.
|
684
|
-
* Do not mess with rakefile, version, or history
|
685
|
-
* Update the CHANGELOG, list your changes under Unreleased.
|
686
|
-
* Update the README if necessary.
|
687
|
-
* Write short, descriptive commit messages, following the format used in the repo.
|
688
|
-
* Send a pull request. Bonus points for topic branches.
|
741
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
689
742
|
|
690
743
|
## Copyright
|
691
744
|
|
692
|
-
Copyright ©
|
745
|
+
Copyright © 2018 Wildbit LLC. See LICENSE for details.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.11.0
|
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
16
|
gem 'yajl-ruby', '~> 1.0', :platforms => [:mingw, :mswin, :ruby]
|
16
|
-
end
|
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'
|
data/lib/postmark/api_client.rb
CHANGED
@@ -113,22 +113,42 @@ module Postmark
|
|
113
113
|
find_each('messages/outbound/opens', 'Opens', options)
|
114
114
|
end
|
115
115
|
|
116
|
+
def clicks(options = {})
|
117
|
+
find_each('messages/outbound/clicks', 'Clicks', options)
|
118
|
+
end
|
119
|
+
|
116
120
|
def get_opens(options = {})
|
117
121
|
_, batch = load_batch('messages/outbound/opens', 'Opens', options)
|
118
122
|
batch
|
119
123
|
end
|
120
124
|
|
121
|
-
def
|
125
|
+
def get_clicks(options = {})
|
126
|
+
_, batch = load_batch('messages/outbound/clicks', 'Clicks', options)
|
127
|
+
batch
|
128
|
+
end
|
129
|
+
|
130
|
+
def get_opens_by_message_id(message_id, options = {})
|
122
131
|
_, batch = load_batch("messages/outbound/opens/#{message_id}",
|
123
132
|
'Opens',
|
124
133
|
options)
|
125
134
|
batch
|
126
135
|
end
|
127
136
|
|
137
|
+
def get_clicks_by_message_id(message_id, options = {})
|
138
|
+
_, batch = load_batch("messages/outbound/clicks/#{message_id}",
|
139
|
+
'Clicks',
|
140
|
+
options)
|
141
|
+
batch
|
142
|
+
end
|
143
|
+
|
128
144
|
def opens_by_message_id(message_id, options = {})
|
129
145
|
find_each("messages/outbound/opens/#{message_id}", 'Opens', options)
|
130
146
|
end
|
131
147
|
|
148
|
+
def clicks_by_message_id(message_id, options = {})
|
149
|
+
find_each("messages/outbound/clicks/#{message_id}", 'Clicks', options)
|
150
|
+
end
|
151
|
+
|
132
152
|
def create_trigger(type, options)
|
133
153
|
data = serialize(HashHelper.to_postmark(options))
|
134
154
|
format_response http_client.post("triggers/#{type}", data)
|
@@ -221,6 +241,17 @@ module Postmark
|
|
221
241
|
end
|
222
242
|
end
|
223
243
|
|
244
|
+
def deliver_in_batches_with_templates(message_hashes)
|
245
|
+
in_batches(message_hashes) do |batch, offset|
|
246
|
+
mapped = batch.map { |h| MessageHelper.to_postmark(h) }
|
247
|
+
data = serialize(:Messages => mapped)
|
248
|
+
|
249
|
+
with_retries do
|
250
|
+
http_client.post('email/batchWithTemplates', data)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
224
255
|
def get_stats_totals(options = {})
|
225
256
|
format_response(http_client.get('stats/outbound', options))
|
226
257
|
end
|
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
|
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module Postmark
|
2
|
+
class Error < ::StandardError; end
|
3
|
+
|
4
|
+
class HttpClientError < Error
|
5
|
+
def retry?
|
6
|
+
true
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class HttpServerError < Error
|
11
|
+
attr_accessor :status_code, :parsed_body, :body
|
12
|
+
|
13
|
+
alias_method :full_response, :parsed_body
|
14
|
+
|
15
|
+
def self.build(status_code, body)
|
16
|
+
parsed_body = Postmark::Json.decode(body) rescue {}
|
17
|
+
|
18
|
+
case status_code
|
19
|
+
when '401'
|
20
|
+
InvalidApiKeyError.new(401, body, parsed_body)
|
21
|
+
when '422'
|
22
|
+
ApiInputError.build(body, parsed_body)
|
23
|
+
when '500'
|
24
|
+
InternalServerError.new(500, body, parsed_body)
|
25
|
+
else
|
26
|
+
UnexpectedHttpResponseError.new(status_code, body, parsed_body)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(status_code = 500, body = '', parsed_body = {})
|
31
|
+
self.parsed_body = parsed_body
|
32
|
+
self.status_code = status_code.to_i
|
33
|
+
message = parsed_body.fetch(
|
34
|
+
'Message',
|
35
|
+
"The Postmark API responded with HTTP status #{status_code}.")
|
36
|
+
|
37
|
+
super(message)
|
38
|
+
end
|
39
|
+
|
40
|
+
def retry?
|
41
|
+
5 == status_code / 100
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class ApiInputError < HttpServerError
|
46
|
+
INACTIVE_RECIPIENT = 406
|
47
|
+
|
48
|
+
attr_accessor :error_code
|
49
|
+
|
50
|
+
def self.build(body, parsed_body)
|
51
|
+
error_code = parsed_body['ErrorCode'].to_i
|
52
|
+
|
53
|
+
case error_code
|
54
|
+
when INACTIVE_RECIPIENT
|
55
|
+
InactiveRecipientError.new(INACTIVE_RECIPIENT, body, parsed_body)
|
56
|
+
else
|
57
|
+
new(error_code, body, parsed_body)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def initialize(error_code = nil, body = '', parsed_body = {})
|
62
|
+
self.error_code = error_code.to_i
|
63
|
+
super(422, body, parsed_body)
|
64
|
+
end
|
65
|
+
|
66
|
+
def retry?
|
67
|
+
false
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class InactiveRecipientError < ApiInputError
|
72
|
+
attr_reader :recipients
|
73
|
+
|
74
|
+
PATTERNS = [/^Found inactive addresses: (.+?)\.$/.freeze,
|
75
|
+
/^Found inactive addresses: (.+?)\.$/.freeze,
|
76
|
+
/these inactive addresses: (.+?)\. Inactive/.freeze].freeze
|
77
|
+
|
78
|
+
def self.parse_recipients(message)
|
79
|
+
PATTERNS.each do |p|
|
80
|
+
_, recipients = p.match(message).to_a
|
81
|
+
next unless recipients
|
82
|
+
return recipients.split(', ')
|
83
|
+
end
|
84
|
+
|
85
|
+
[]
|
86
|
+
end
|
87
|
+
|
88
|
+
def initialize(*args)
|
89
|
+
super
|
90
|
+
@recipients = parse_recipients || []
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def parse_recipients
|
96
|
+
return unless parsed_body && !parsed_body.empty?
|
97
|
+
|
98
|
+
self.class.parse_recipients(parsed_body['Message'])
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
class TimeoutError < Error
|
103
|
+
def retry?
|
104
|
+
true
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class UnknownMessageType < Error; end
|
109
|
+
class InvalidApiKeyError < HttpServerError; end
|
110
|
+
class InternalServerError < HttpServerError; end
|
111
|
+
class UnexpectedHttpResponseError < HttpServerError; end
|
112
|
+
|
113
|
+
# Backwards compatible aliases
|
114
|
+
DeliveryError = Error
|
115
|
+
InvalidMessageError = ApiInputError
|
116
|
+
UnknownError = UnexpectedHttpResponseError
|
117
|
+
end
|
data/lib/postmark/http_client.rb
CHANGED
@@ -66,17 +66,10 @@ module Postmark
|
|
66
66
|
end
|
67
67
|
|
68
68
|
def handle_response(response)
|
69
|
-
|
70
|
-
|
71
|
-
return Postmark::Json.decode(response.body)
|
72
|
-
when 401
|
73
|
-
raise error(InvalidApiKeyError, response.body)
|
74
|
-
when 422
|
75
|
-
raise error(InvalidMessageError, response.body)
|
76
|
-
when 500
|
77
|
-
raise error(InternalServerError, response.body)
|
69
|
+
if response.code.to_i == 200
|
70
|
+
Postmark::Json.decode(response.body)
|
78
71
|
else
|
79
|
-
raise
|
72
|
+
raise HttpServerError.build(response.code, response.body)
|
80
73
|
end
|
81
74
|
end
|
82
75
|
|
@@ -92,8 +85,10 @@ module Postmark
|
|
92
85
|
@request_mutex.synchronize do
|
93
86
|
handle_response(yield(http))
|
94
87
|
end
|
95
|
-
rescue Timeout::Error
|
96
|
-
raise TimeoutError.new(
|
88
|
+
rescue Timeout::Error => e
|
89
|
+
raise TimeoutError.new(e)
|
90
|
+
rescue Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError => e
|
91
|
+
raise HttpClientError.new(e.message)
|
97
92
|
end
|
98
93
|
|
99
94
|
def build_http
|
@@ -108,18 +103,5 @@ module Postmark
|
|
108
103
|
http.ssl_version = :TLSv1 if http.respond_to?(:ssl_version=)
|
109
104
|
http
|
110
105
|
end
|
111
|
-
|
112
|
-
def error_message(response_body)
|
113
|
-
Postmark::Json.decode(response_body)["Message"]
|
114
|
-
end
|
115
|
-
|
116
|
-
def error_message_and_code(response_body)
|
117
|
-
reply = Postmark::Json.decode(response_body)
|
118
|
-
[reply["Message"], reply["ErrorCode"], reply]
|
119
|
-
end
|
120
|
-
|
121
|
-
def error(clazz, response_body)
|
122
|
-
clazz.send(:new, *error_message_and_code(response_body))
|
123
|
-
end
|
124
106
|
end
|
125
107
|
end
|