postmark 1.14.0 → 1.15.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.travis.yml +0 -3
- data/CHANGELOG.rdoc +6 -0
- data/README.md +34 -781
- data/VERSION +1 -1
- data/lib/postmark/api_client.rb +42 -0
- data/lib/postmark/error.rb +10 -0
- data/lib/postmark/handlers/mail.rb +10 -4
- data/lib/postmark/mail_message_converter.rb +15 -5
- data/lib/postmark/message_extensions/mail.rb +45 -0
- data/lib/postmark/version.rb +1 -1
- data/postmark.gemspec +1 -1
- data/postmark.png +0 -0
- data/spec/support/custom_matchers.rb +4 -0
- data/spec/unit/postmark/api_client_spec.rb +103 -23
- data/spec/unit/postmark/error_spec.rb +13 -0
- data/spec/unit/postmark/handlers/mail_spec.rb +59 -27
- data/spec/unit/postmark/mail_message_converter_spec.rb +17 -0
- data/spec/unit/postmark/message_extensions/mail_spec.rb +114 -0
- metadata +8 -7
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.15.0
|
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
|
|
data/lib/postmark/error.rb
CHANGED
@@ -99,12 +99,22 @@ module Postmark
|
|
99
99
|
end
|
100
100
|
end
|
101
101
|
|
102
|
+
class InvalidTemplateError < Error
|
103
|
+
attr_reader :postmark_response
|
104
|
+
|
105
|
+
def initialize(response)
|
106
|
+
@postmark_response = response
|
107
|
+
super('Failed to render the template. Please check #postmark_response on this error for details.')
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
102
111
|
class TimeoutError < Error
|
103
112
|
def retry?
|
104
113
|
true
|
105
114
|
end
|
106
115
|
end
|
107
116
|
|
117
|
+
class MailAdapterError < Postmark::Error; end
|
108
118
|
class UnknownMessageType < Error; end
|
109
119
|
class InvalidApiKeyError < HttpServerError; end
|
110
120
|
class InternalServerError < HttpServerError; end
|
@@ -8,10 +8,11 @@ module Mail
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def deliver!(mail)
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
response = if mail.templated?
|
12
|
+
api_client.deliver_message_with_template(mail)
|
13
|
+
else
|
14
|
+
api_client.deliver_message(mail)
|
15
|
+
end
|
15
16
|
|
16
17
|
if settings[:return_response]
|
17
18
|
response
|
@@ -20,5 +21,10 @@ module Mail
|
|
20
21
|
end
|
21
22
|
end
|
22
23
|
|
24
|
+
def api_client
|
25
|
+
settings = self.settings.dup
|
26
|
+
api_token = settings.delete(:api_token) || settings.delete(:api_key)
|
27
|
+
::Postmark::ApiClient.new(api_token, settings)
|
28
|
+
end
|
23
29
|
end
|
24
30
|
end
|
@@ -7,10 +7,10 @@ module Postmark
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def run
|
10
|
-
delete_blank_fields(convert)
|
10
|
+
delete_blank_fields(pick_fields(convert, @message.templated?))
|
11
11
|
end
|
12
12
|
|
13
|
-
|
13
|
+
private
|
14
14
|
|
15
15
|
def convert
|
16
16
|
headers_part.merge(content_part)
|
@@ -32,10 +32,22 @@ module Postmark
|
|
32
32
|
'Tag' => @message.tag.to_s,
|
33
33
|
'TrackOpens' => (cast_to_bool(@message.track_opens) unless @message.track_opens.empty?),
|
34
34
|
'TrackLinks' => (::Postmark::Inflector.to_postmark(@message.track_links) unless @message.track_links.empty?),
|
35
|
-
'Metadata' => @message.metadata
|
35
|
+
'Metadata' => @message.metadata,
|
36
|
+
'TemplateAlias' => @message.template_alias,
|
37
|
+
'TemplateModel' => @message.template_model
|
36
38
|
}
|
37
39
|
end
|
38
40
|
|
41
|
+
def pick_fields(message_hash, templated = false)
|
42
|
+
fields = if templated
|
43
|
+
%w(Subject HtmlBody TextBody)
|
44
|
+
else
|
45
|
+
%w(TemplateAlias TemplateModel)
|
46
|
+
end
|
47
|
+
fields.each { |key| message_hash.delete(key) }
|
48
|
+
message_hash
|
49
|
+
end
|
50
|
+
|
39
51
|
def content_part
|
40
52
|
{
|
41
53
|
'Attachments' => @message.export_attachments,
|
@@ -44,8 +56,6 @@ module Postmark
|
|
44
56
|
}
|
45
57
|
end
|
46
58
|
|
47
|
-
protected
|
48
|
-
|
49
59
|
def cast_to_bool(val)
|
50
60
|
if val.is_a?(TrueClass) || val.is_a?(FalseClass)
|
51
61
|
val
|
@@ -61,6 +61,50 @@ module Mail
|
|
61
61
|
::Postmark::MessageHelper.attachments_to_postmark(@_attachments)
|
62
62
|
end
|
63
63
|
|
64
|
+
def template_alias(val = nil)
|
65
|
+
return self[:postmark_template_alias] && self[:postmark_template_alias].to_s if val.nil?
|
66
|
+
self[:postmark_template_alias] = val
|
67
|
+
end
|
68
|
+
|
69
|
+
attr_writer :template_model
|
70
|
+
def template_model(model = nil)
|
71
|
+
return @template_model if model.nil?
|
72
|
+
@template_model = model
|
73
|
+
end
|
74
|
+
|
75
|
+
def templated?
|
76
|
+
!!template_alias
|
77
|
+
end
|
78
|
+
|
79
|
+
def prerender
|
80
|
+
raise ::Postmark::Error, 'Cannot prerender a message without an associated template alias' unless templated?
|
81
|
+
|
82
|
+
unless delivery_method.is_a?(::Mail::Postmark)
|
83
|
+
raise ::Postmark::MailAdapterError, "Cannot render templates via #{delivery_method.class} adapter."
|
84
|
+
end
|
85
|
+
|
86
|
+
client = delivery_method.api_client
|
87
|
+
template = client.get_template(template_alias)
|
88
|
+
response = client.validate_template(template.merge(:test_render_model => template_model || {}))
|
89
|
+
|
90
|
+
raise ::Postmark::InvalidTemplateError, response unless response[:all_content_is_valid]
|
91
|
+
|
92
|
+
self.body = nil
|
93
|
+
|
94
|
+
subject response[:subject][:rendered_content]
|
95
|
+
|
96
|
+
text_part do
|
97
|
+
body response[:text_body][:rendered_content]
|
98
|
+
end
|
99
|
+
|
100
|
+
html_part do
|
101
|
+
content_type 'text/html; charset=UTF-8'
|
102
|
+
body response[:html_body][:rendered_content]
|
103
|
+
end
|
104
|
+
|
105
|
+
self
|
106
|
+
end
|
107
|
+
|
64
108
|
def text?
|
65
109
|
if defined?(super)
|
66
110
|
super
|
@@ -139,6 +183,7 @@ module Mail
|
|
139
183
|
subject tag
|
140
184
|
attachment to
|
141
185
|
track-opens track-links
|
186
|
+
postmark-template-alias
|
142
187
|
]
|
143
188
|
end
|
144
189
|
|
data/lib/postmark/version.rb
CHANGED
data/postmark.gemspec
CHANGED
data/postmark.png
ADDED
Binary file
|
@@ -10,6 +10,14 @@ describe Postmark::ApiClient do
|
|
10
10
|
delivery_method Mail::Postmark
|
11
11
|
end
|
12
12
|
}
|
13
|
+
let(:templated_message) do
|
14
|
+
Mail.new do
|
15
|
+
from "sheldon@bigbangtheory.com"
|
16
|
+
to "lenard@bigbangtheory.com"
|
17
|
+
template_alias "hello"
|
18
|
+
template_model :name => "Sheldon"
|
19
|
+
end
|
20
|
+
end
|
13
21
|
|
14
22
|
let(:api_client) {Postmark::ApiClient.new(api_token)}
|
15
23
|
subject {api_client}
|
@@ -95,6 +103,11 @@ describe Postmark::ApiClient do
|
|
95
103
|
let(:email_json) {Postmark::Json.encode(email)}
|
96
104
|
let(:http_client) {subject.http_client}
|
97
105
|
|
106
|
+
it 'raises an error when given a templated message' do
|
107
|
+
expect { subject.deliver_message(templated_message) }.
|
108
|
+
to raise_error(ArgumentError, /Please use Postmark::ApiClient\#deliver_message_with_template/)
|
109
|
+
end
|
110
|
+
|
98
111
|
it 'turns message into a JSON document and posts it to /email' do
|
99
112
|
expect(http_client).to receive(:post).with('email', email_json)
|
100
113
|
subject.deliver_message(message)
|
@@ -118,7 +131,41 @@ describe Postmark::ApiClient do
|
|
118
131
|
allow(http_client).to receive(:post).and_raise(Postmark::TimeoutError)
|
119
132
|
expect {subject.deliver_message(message)}.to raise_error(Postmark::TimeoutError)
|
120
133
|
end
|
134
|
+
end
|
121
135
|
|
136
|
+
describe "#deliver_message_with_template" do
|
137
|
+
let(:email) {templated_message.to_postmark_hash}
|
138
|
+
let(:email_json) {Postmark::Json.encode(email)}
|
139
|
+
let(:http_client) {subject.http_client}
|
140
|
+
|
141
|
+
it 'raises an error when given a non-templated message' do
|
142
|
+
expect { subject.deliver_message_with_template(message) }.
|
143
|
+
to raise_error(ArgumentError, 'Templated delivery requested, but the template is missing.')
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'turns message into a JSON document and posts it to /email' do
|
147
|
+
expect(http_client).to receive(:post).with('email/withTemplate', email_json)
|
148
|
+
subject.deliver_message_with_template(templated_message)
|
149
|
+
end
|
150
|
+
|
151
|
+
it "retries 3 times" do
|
152
|
+
2.times do
|
153
|
+
expect(http_client).to receive(:post).and_raise(Postmark::InternalServerError)
|
154
|
+
end
|
155
|
+
expect(http_client).to receive(:post)
|
156
|
+
expect {subject.deliver_message_with_template(templated_message)}.not_to raise_error
|
157
|
+
end
|
158
|
+
|
159
|
+
it "retries on timeout" do
|
160
|
+
expect(http_client).to receive(:post).and_raise(Postmark::TimeoutError)
|
161
|
+
expect(http_client).to receive(:post)
|
162
|
+
expect {subject.deliver_message_with_template(templated_message)}.not_to raise_error
|
163
|
+
end
|
164
|
+
|
165
|
+
it "proxies errors" do
|
166
|
+
allow(http_client).to receive(:post).and_raise(Postmark::TimeoutError)
|
167
|
+
expect {subject.deliver_message_with_template(templated_message)}.to raise_error(Postmark::TimeoutError)
|
168
|
+
end
|
122
169
|
end
|
123
170
|
|
124
171
|
describe "#deliver_messages" do
|
@@ -128,6 +175,11 @@ describe Postmark::ApiClient do
|
|
128
175
|
let(:http_client) {subject.http_client}
|
129
176
|
let(:response) {[{}, {}, {}]}
|
130
177
|
|
178
|
+
it 'raises an error when given a templated message' do
|
179
|
+
expect { subject.deliver_messages([templated_message]) }.
|
180
|
+
to raise_error(ArgumentError, /Please use Postmark::ApiClient\#deliver_messages_with_templates/)
|
181
|
+
end
|
182
|
+
|
131
183
|
it 'turns array of messages into a JSON document and posts it to /email/batch' do
|
132
184
|
expect(http_client).to receive(:post).with('email/batch', emails_json) {response}
|
133
185
|
subject.deliver_messages([message, message, message])
|
@@ -146,7 +198,39 @@ describe Postmark::ApiClient do
|
|
146
198
|
expect(http_client).to receive(:post) {response}
|
147
199
|
expect {subject.deliver_messages([message, message, message])}.not_to raise_error
|
148
200
|
end
|
201
|
+
end
|
202
|
+
|
203
|
+
describe "#deliver_messages_with_templates" do
|
204
|
+
let(:email) {templated_message.to_postmark_hash}
|
205
|
+
let(:emails) {[email, email, email]}
|
206
|
+
let(:emails_json) {Postmark::Json.encode(emails)}
|
207
|
+
let(:http_client) {subject.http_client}
|
208
|
+
let(:response) {[{}, {}, {}]}
|
209
|
+
let(:messages) { Array.new(3) { templated_message } }
|
149
210
|
|
211
|
+
it 'raises an error when given a templated message' do
|
212
|
+
expect { subject.deliver_messages_with_templates([message]) }.
|
213
|
+
to raise_error(ArgumentError, 'Templated delivery requested, but one or more messages lack templates.')
|
214
|
+
end
|
215
|
+
|
216
|
+
it 'turns array of messages into a JSON document and posts it to /email/batch' do
|
217
|
+
expect(http_client).to receive(:post).with('email/batchWithTemplates', emails_json) {response}
|
218
|
+
subject.deliver_messages_with_templates(messages)
|
219
|
+
end
|
220
|
+
|
221
|
+
it "should retry 3 times" do
|
222
|
+
2.times do
|
223
|
+
expect(http_client).to receive(:post).and_raise(Postmark::InternalServerError)
|
224
|
+
end
|
225
|
+
expect(http_client).to receive(:post) {response}
|
226
|
+
expect {subject.deliver_messages_with_templates(messages)}.not_to raise_error
|
227
|
+
end
|
228
|
+
|
229
|
+
it "should retry on timeout" do
|
230
|
+
expect(http_client).to receive(:post).and_raise(Postmark::TimeoutError)
|
231
|
+
expect(http_client).to receive(:post) {response}
|
232
|
+
expect {subject.deliver_messages_with_templates(messages)}.not_to raise_error
|
233
|
+
end
|
150
234
|
end
|
151
235
|
|
152
236
|
describe "#delivery_stats" do
|
@@ -716,9 +800,9 @@ describe Postmark::ApiClient do
|
|
716
800
|
end
|
717
801
|
|
718
802
|
it 'performs a POST request to /templates with the given attributes' do
|
719
|
-
|
720
|
-
|
721
|
-
|
803
|
+
expect(http_client).to receive(:post).
|
804
|
+
with('templates', json_representation_of('Name' => 'template name')).
|
805
|
+
and_return(response)
|
722
806
|
|
723
807
|
template = subject.create_template(:name => 'template name')
|
724
808
|
|
@@ -738,9 +822,9 @@ describe Postmark::ApiClient do
|
|
738
822
|
end
|
739
823
|
|
740
824
|
it 'performs a PUT request to /templates with the given attributes' do
|
741
|
-
|
742
|
-
|
743
|
-
|
825
|
+
expect(http_client).to receive(:put).
|
826
|
+
with('templates/123', json_representation_of('Name' => 'template name')).
|
827
|
+
and_return(response)
|
744
828
|
|
745
829
|
template = subject.update_template(123, :name => 'template name')
|
746
830
|
|
@@ -796,13 +880,12 @@ describe Postmark::ApiClient do
|
|
796
880
|
end
|
797
881
|
|
798
882
|
it 'performs a POST request and returns unmodified suggested template model' do
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
expect(http_client).to receive(:post).with('templates/validate', expected_template_json).and_return(response)
|
883
|
+
expect(http_client).to receive(:post).
|
884
|
+
with('templates/validate',
|
885
|
+
json_representation_of('HtmlBody' => '{{MyName}}',
|
886
|
+
'TextBody' => '{{MyName}}',
|
887
|
+
'Subject' => '{{MyName}}')).
|
888
|
+
and_return(response)
|
806
889
|
|
807
890
|
resp = subject.validate_template(:html_body => '{{MyName}}',
|
808
891
|
:text_body => '{{MyName}}',
|
@@ -845,13 +928,11 @@ describe Postmark::ApiClient do
|
|
845
928
|
end
|
846
929
|
|
847
930
|
it 'performs a POST request and returns validation errors' do
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
expect(http_client).to receive(:post).with('templates/validate', expected_template_json).and_return(response)
|
931
|
+
expect(http_client).
|
932
|
+
to receive(:post).with('templates/validate',
|
933
|
+
json_representation_of('HtmlBody' => '{{#each}}',
|
934
|
+
'TextBody' => '{{MyName}}',
|
935
|
+
'Subject' => '{{MyName}}')).and_return(response)
|
855
936
|
|
856
937
|
resp = subject.validate_template(:html_body => '{{#each}}',
|
857
938
|
:text_body => '{{MyName}}',
|
@@ -868,12 +949,11 @@ describe Postmark::ApiClient do
|
|
868
949
|
|
869
950
|
describe "#deliver_with_template" do
|
870
951
|
let(:email) {Postmark::MessageHelper.to_postmark(message_hash)}
|
871
|
-
let(:email_json) {Postmark::Json.encode(email)}
|
872
952
|
let(:http_client) {subject.http_client}
|
873
953
|
let(:response) {{"MessageID" => 42}}
|
874
954
|
|
875
955
|
it 'converts message hash to Postmark format and posts it to /email/withTemplate' do
|
876
|
-
expect(http_client).to receive(:post).with('email/withTemplate',
|
956
|
+
expect(http_client).to receive(:post).with('email/withTemplate', json_representation_of(email)) {response}
|
877
957
|
subject.deliver_with_template(message_hash)
|
878
958
|
end
|
879
959
|
|
@@ -886,7 +966,7 @@ describe Postmark::ApiClient do
|
|
886
966
|
end
|
887
967
|
|
888
968
|
it 'converts response to ruby format' do
|
889
|
-
expect(http_client).to receive(:post).with('email/withTemplate',
|
969
|
+
expect(http_client).to receive(:post).with('email/withTemplate', json_representation_of(email)) {response}
|
890
970
|
r = subject.deliver_with_template(message_hash)
|
891
971
|
r.should have_key(:message_id)
|
892
972
|
end
|