postmark 1.14.0 → 1.15.0

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.14.0
1
+ 1.15.0
@@ -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
 
@@ -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
- settings = self.settings.dup
12
- api_token = settings.delete(:api_token) || settings.delete(:api_key)
13
- api_client = ::Postmark::ApiClient.new(api_token, settings)
14
- response = api_client.deliver_message(mail)
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
- protected
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
 
@@ -1,3 +1,3 @@
1
1
  module Postmark
2
- VERSION = '1.14.0'
2
+ VERSION = '1.15.0'
3
3
  end
@@ -33,8 +33,8 @@ Gem::Specification.new do |s|
33
33
 
34
34
  s.required_rubygems_version = ">= 1.3.7"
35
35
 
36
- s.add_dependency "rake"
37
36
  s.add_dependency "json"
38
37
 
39
38
  s.add_development_dependency "mail"
39
+ s.add_development_dependency "rake"
40
40
  end
Binary file
@@ -28,3 +28,7 @@ RSpec::Matchers.define :a_postmark_json do |string|
28
28
  postmark_json?(actual)
29
29
  end
30
30
  end
31
+
32
+ RSpec::Matchers.define :json_representation_of do |x|
33
+ match { |actual| Postmark::Json.decode(actual) == x }
34
+ end
@@ -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
- expected_json = {'Name' => 'template name'}.to_json
720
-
721
- expect(http_client).to receive(:post).with('templates', expected_json).and_return(response)
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
- expected_json = {'Name' => 'template name'}.to_json
742
-
743
- expect(http_client).to receive(:put).with('templates/123', expected_json).and_return(response)
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
- expected_template_json = {
800
- 'HtmlBody' => '{{MyName}}',
801
- 'TextBody' => '{{MyName}}',
802
- 'Subject' => '{{MyName}}'
803
- }.to_json
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
- expected_template_json = {
849
- 'HtmlBody' => '{{#each}}',
850
- 'TextBody' => '{{MyName}}',
851
- 'Subject' => '{{MyName}}'
852
- }.to_json
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', email_json) {response}
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', email_json) {response}
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