postmark 1.14.0 → 1.15.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.
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