postmark 1.1.2 → 1.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dea79dd86f6f199d56c6d3845f164961be3950c5
4
- data.tar.gz: 0b112a7f4412ccc38276947f4513ffbaaee4164a
3
+ metadata.gz: 2bdedf5c9c85549872f00c732d6415a597f40267
4
+ data.tar.gz: fb1aa95ee6adedf96a0a39c91223cfb54ce804a2
5
5
  SHA512:
6
- metadata.gz: a578a7aede0e3dd604b67bbe4ea922cf3459b52fc10ee46cf80dcdf681283bd8a09f0d17927e261ee9c17c3070dc8251788fd01c537b1975c1e230a4bb4083e9
7
- data.tar.gz: 933f64251c5bd09c29ee044c23ea84637e589ceb060155d0da2cfd9d75590cab622ace4e140a89956a1b908762fe284119fb09b2eaf4a8337324549402c16516
6
+ metadata.gz: 03e03d105cfc7dbcea13c2f3c5966af5d1e8519a30eba3473da82e46b59bdb48e7599e3cd294e0c576f1f4f0f6d01e6f245b52e087e4b51e2dec04bd7029027f
7
+ data.tar.gz: e5bb214b73f426f7dae362ac16a44976097e6c878033b6b58ff6718093b67cef2b60c39788940d7fc51760e34f38d9c3aca1fd3bc444acd9f8e7db3f6b333c36
data/.travis.yml CHANGED
@@ -1,6 +1,8 @@
1
1
  env:
2
2
  global:
3
- secure: MV+nKlzuLJ5CEoEfCmzwRMlnBOudgB1fTxBYI6aDV9vUkwYRzkcpCPS+Ik6jdCzNEFK+L2dTfv/IfDOvxnK3cZpMi1sp0po04m3Pg+ZhzYoANS1KY6V4sFIFaPtx+x2DVStxjFch4WQV8ktzdoUI0POlCdJrD6GYXLh1aTgbAGU=
3
+ - secure: "fo5ce1ABn12rZODstLzMnM14Ma21CUpYkcn9EnRtsPlvpFt/71hTPtP5pYrhnTTGUxVk6JPYGSUC58fXMa9zJKyMZv1PKokiGuGw4ktcsn+fX/pvEYxmsuQx0e+GbZ6psCHneIbaADEjUeqR/XJUBJDFpDoXKGwZuJW05b8Hxbc="
4
+ - secure: "YGu/kwvIbTKQ9/ox2MGJ3qVLU78ccqi+HLgkXYBxhjfOFTgTemFizMgZYRDmwGKnOB1XVnKj0mZUOuMb66DjZjRn9MK6ifxp6NjPb82AKkLALFkMKUm0rUqz2PeiUYSPDyta3pQfyTZTvN/XjFXjKvInVz9onKsoPUmPGh2AxNw="
5
+ - secure: "Cb54rQW9L8XAOPLe8x+DNTsF0g13QjSRhzCUq3U8vHlM7UmsHzIRA0GvAomFCq5bp5wrUVVQKD+6rQpqGLBdTG6VxKvPbomS+nVIukjQiOqnqtjaZHcSuK5RnJ+4mJSKHV/W1Wq7NbdvmQxpzURXTJuuLqib5Lwd4Jt5cWqX05w="
4
6
  language: ruby
5
7
  rvm:
6
8
  - 1.8.7
data/CHANGELOG.rdoc CHANGED
@@ -1,5 +1,10 @@
1
1
  = Changelog
2
2
 
3
+ == 1.2.0
4
+
5
+ * Added support for the Postmark Account API.
6
+ * Added #bounces and #messages methods to Postmark::ApiClient returning Ruby enumerators.
7
+
3
8
  == 1.1.2
4
9
 
5
10
  * Fixed HTTP verb used to update server info from POST to PUT to support the breaking change in the API.
data/Gemfile CHANGED
@@ -4,7 +4,7 @@ source "http://rubygems.org"
4
4
  gemspec
5
5
 
6
6
  group :test do
7
- gem 'rspec', '~> 2.13.0'
7
+ gem 'rspec', '~> 2.14.0'
8
8
  gem 'fakeweb'
9
9
  gem 'fakeweb-matcher'
10
10
  end
data/README.md CHANGED
@@ -252,6 +252,13 @@ client.dump_message('41f03342-xxxx-xxxx-xxxx-558caedb5e82')
252
252
  # => {:body=>"..."}
253
253
  ```
254
254
 
255
+ There is also a handy `#messages` enumerator allowing you to easily manipulate big data arrays.
256
+
257
+ ``` ruby
258
+ client.messages.lazy.select { |m| DateTime.parse(m[:received_at]).day.even? }.first(5)
259
+ # => [{...}, {...}]
260
+ ```
261
+
255
262
  You can get more details about the underlying endpoints and parameters they
256
263
  accept in [Postmark Developer Docs](http://developer.postmarkapp.com/developer-messages.html).
257
264
 
@@ -286,6 +293,13 @@ client.dump_bounce(654714902)
286
293
  # => {:body=>"Return-Path: <>\r\nReceived: from m1.mtasv.net (74.205.19.136) by sc-ord-mail2.mtasv.net id hcjov61jk5ko for <pm_bounces@pm.mtasv.net>; Wed, 10 Apr 2013 01:00:35 -0400 (envelope-from <>)\r\nDate: Wed, 10 Apr 2013 01:00:48 -0400\r\nFrom: postmaster@m1.mtasv.net\r\n..."}
287
294
  ```
288
295
 
296
+ There is a `#bounces` enumerator to take the underlying complexity off of your shoulders. Use it to iterate over all of your bounces.
297
+
298
+ ``` ruby
299
+ client.bounces.first(5)
300
+ # => [{...}, {...}]
301
+ ```
302
+
289
303
  You can activate email addresses that were disabled due to a hard bounce by using `#activate_bounce`:
290
304
 
291
305
  ``` ruby
@@ -547,7 +561,13 @@ message.message_id
547
561
 
548
562
  # Exploring Other Gem Features
549
563
 
550
- To provide an interface similar to ActiveRecord for bounces, the Postmark gem adds
564
+ ## The Account API Support
565
+
566
+ Postmark allows you to automatically scale your sending infrastructure with the Account API. Learn how in the [Account API Support](https://github.com/wildbit/postmark-gem/wiki/The-Account-API-Support) guide.
567
+
568
+ ## ActiveModel-like Interface For Bounces
569
+
570
+ To provide an interface similar to ActiveModel for bounces, the Postmark gem adds
551
571
  `Postmark::Bounce` class. This class uses the shared `Postmark::ApiClient` instance
552
572
  configured through the Postmark module.
553
573
 
@@ -595,8 +615,7 @@ Postmark.response_parser_class = :Json # :ActiveSupport or :Yajl are also suppor
595
615
 
596
616
  * Fork the project.
597
617
  * Make your feature addition or bug fix.
598
- * Add tests for it. This is important so I don't break it in a
599
- future version unintentionally.
618
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
600
619
  * Commit, do not mess with rakefile, version, or history.
601
620
  * Send me a pull request. Bonus points for topic branches.
602
621
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.2
1
+ 1.2.0
@@ -0,0 +1,96 @@
1
+ module Postmark
2
+
3
+ class AccountApiClient < Client
4
+
5
+ def initialize(api_key, options = {})
6
+ options[:auth_header_name] = 'X-Postmark-Account-Token'
7
+ super
8
+ end
9
+
10
+ def senders(options = {})
11
+ find_each('senders', 'SenderSignatures', options)
12
+ end
13
+ alias_method :signatures, :senders
14
+
15
+ def get_senders(options = {})
16
+ load_batch('senders', 'SenderSignatures', options).last
17
+ end
18
+ alias_method :get_signatures, :get_senders
19
+
20
+ def get_senders_count(options = {})
21
+ get_resource_count('senders', options)
22
+ end
23
+ alias_method :get_signatures_count, :get_senders_count
24
+
25
+ def get_sender(id)
26
+ format_response http_client.get("senders/#{id.to_i}")
27
+ end
28
+ alias_method :get_signature, :get_sender
29
+
30
+ def create_sender(attributes = {})
31
+ data = serialize(HashHelper.to_postmark(attributes))
32
+
33
+ format_response http_client.post('senders', data)
34
+ end
35
+ alias_method :create_signature, :create_sender
36
+
37
+ def update_sender(id, attributes = {})
38
+ data = serialize(HashHelper.to_postmark(attributes))
39
+
40
+ format_response http_client.put("senders/#{id.to_i}", data)
41
+ end
42
+ alias_method :update_signature, :update_sender
43
+
44
+ def resend_sender_confirmation(id)
45
+ format_response http_client.post("senders/#{id.to_i}/resend")
46
+ end
47
+ alias_method :resend_signature_confirmation, :resend_sender_confirmation
48
+
49
+ def verified_sender_spf?(id)
50
+ !!http_client.post("senders/#{id.to_i}/verifyspf")['SPFVerified']
51
+ end
52
+ alias_method :verified_signature_spf?, :verified_sender_spf?
53
+
54
+ def request_new_sender_dkim(id)
55
+ format_response http_client.post("senders/#{id.to_i}/requestnewdkim")
56
+ end
57
+ alias_method :request_new_signature_dkim, :request_new_sender_dkim
58
+
59
+ def delete_sender(id)
60
+ format_response http_client.delete("senders/#{id.to_i}")
61
+ end
62
+ alias_method :delete_signature, :delete_sender
63
+
64
+ def servers(options = {})
65
+ find_each('servers', 'Servers', options)
66
+ end
67
+
68
+ def get_servers(options = {})
69
+ load_batch('servers', 'Servers', options).last
70
+ end
71
+
72
+ def get_servers_count(options = {})
73
+ get_resource_count('servers', options)
74
+ end
75
+
76
+ def get_server(id)
77
+ format_response http_client.get("servers/#{id.to_i}")
78
+ end
79
+
80
+ def create_server(attributes = {})
81
+ data = serialize(HashHelper.to_postmark(attributes))
82
+ format_response http_client.post('servers', data)
83
+ end
84
+
85
+ def update_server(id, attributes = {})
86
+ data = serialize(HashHelper.to_postmark(attributes))
87
+ format_response http_client.put("servers/#{id.to_i}", data)
88
+ end
89
+
90
+ def delete_server(id)
91
+ format_response http_client.delete("servers/#{id.to_i}")
92
+ end
93
+
94
+ end
95
+
96
+ end
@@ -1,15 +1,11 @@
1
1
  module Postmark
2
- class ApiClient
3
- attr_reader :http_client, :max_retries
4
- attr_writer :max_batch_size
2
+ class ApiClient < Client
3
+ attr_accessor :max_batch_size
5
4
 
6
5
  def initialize(api_key, options = {})
7
- @max_retries = options.delete(:max_retries) || 3
8
- @http_client = HttpClient.new(api_key, options)
9
- end
10
-
11
- def api_key=(api_key)
12
- http_client.api_key = api_key
6
+ options = options.dup
7
+ @max_batch_size = options.delete(:max_batch_size) || 500
8
+ super
13
9
  end
14
10
 
15
11
  def deliver(message_hash = {})
@@ -65,12 +61,19 @@ module Postmark
65
61
  response
66
62
  end
67
63
 
64
+ def messages(options = {})
65
+ path, name, params = extract_messages_path_and_params(options)
66
+ find_each(path, name, params)
67
+ end
68
+
68
69
  def get_messages(options = {})
69
- path, params = extract_messages_path_and_params(options)
70
- params[:offset] ||= 0
71
- params[:count] ||= 50
72
- messages_key = options[:inbound] ? 'InboundMessages' : 'Messages'
73
- format_response http_client.get(path, params)[messages_key]
70
+ path, name, params = extract_messages_path_and_params(options)
71
+ load_batch(path, name, params).last
72
+ end
73
+
74
+ def get_messages_count(options = {})
75
+ path, _, params = extract_messages_path_and_params(options)
76
+ get_resource_count(path, params)
74
77
  end
75
78
 
76
79
  def get_message(id, options = {})
@@ -81,8 +84,12 @@ module Postmark
81
84
  get_for_message('dump', id, options)
82
85
  end
83
86
 
87
+ def bounces(options = {})
88
+ find_each('bounces', 'Bounces', options)
89
+ end
90
+
84
91
  def get_bounces(options = {})
85
- format_response http_client.get("bounces", options)["Bounces"]
92
+ load_batch('bounces', 'Bounces', options)
86
93
  end
87
94
 
88
95
  def get_bounced_tags
@@ -110,23 +117,8 @@ module Postmark
110
117
  format_response http_client.put("server", serialize(data))
111
118
  end
112
119
 
113
- def max_batch_size
114
- @max_batch_size ||= 500
115
- end
116
-
117
120
  protected
118
121
 
119
- def with_retries
120
- yield
121
- rescue DeliveryError
122
- retries = retries ? retries + 1 : 1
123
- if retries < self.max_retries
124
- retry
125
- else
126
- raise
127
- end
128
- end
129
-
130
122
  def in_batches(messages)
131
123
  r = messages.each_slice(max_batch_size).each_with_index.map do |batch, i|
132
124
  yield batch, i * max_batch_size
@@ -142,35 +134,16 @@ module Postmark
142
134
  message.postmark_response = response
143
135
  end
144
136
 
145
- def serialize(data)
146
- Postmark::Json.encode(data)
147
- end
148
-
149
- def take_response_of
150
- [yield, nil]
151
- rescue DeliveryError => e
152
- [e.full_response || {}, e]
153
- end
154
-
155
137
  def get_for_message(action, id, options = {})
156
- path, params = extract_messages_path_and_params(options)
138
+ path, _, params = extract_messages_path_and_params(options)
157
139
  format_response http_client.get("#{path}/#{id}/#{action}", params)
158
140
  end
159
141
 
160
- def format_response(response, compatible = false)
161
- return {} unless response
162
-
163
- if response.kind_of? Array
164
- response.map { |entry| Postmark::HashHelper.to_ruby(entry, compatible) }
165
- else
166
- Postmark::HashHelper.to_ruby(response, compatible)
167
- end
168
- end
169
-
170
142
  def extract_messages_path_and_params(options = {})
171
143
  options = options.dup
144
+ messages_key = options[:inbound] ? 'InboundMessages' : 'Messages'
172
145
  path = options.delete(:inbound) ? 'messages/inbound' : 'messages/outbound'
173
- [path, options]
146
+ [path, messages_key, options]
174
147
  end
175
148
 
176
149
  end
@@ -0,0 +1,82 @@
1
+ require 'enumerator'
2
+
3
+ module Postmark
4
+ class Client
5
+ attr_reader :http_client, :max_retries
6
+
7
+ def initialize(api_key, options = {})
8
+ options = options.dup
9
+ @max_retries = options.delete(:max_retries) || 3
10
+ @http_client = HttpClient.new(api_key, options)
11
+ end
12
+
13
+ def api_key=(api_key)
14
+ http_client.api_key = api_key
15
+ end
16
+
17
+ def find_each(path, name, options = {})
18
+ if block_given?
19
+ options = options.dup
20
+ i, total_count = [0, 1]
21
+
22
+ while i < total_count
23
+ options[:offset] = i
24
+ total_count, collection = load_batch(path, name, options)
25
+ collection.each { |e| yield e }
26
+ i += collection.size
27
+ end
28
+ else
29
+ enum_for(:find_each, path, name, options) do
30
+ get_resource_count(path, options)
31
+ end
32
+ end
33
+ end
34
+
35
+ protected
36
+
37
+ def with_retries
38
+ yield
39
+ rescue DeliveryError
40
+ retries = retries ? retries + 1 : 1
41
+ if retries < self.max_retries
42
+ retry
43
+ else
44
+ raise
45
+ end
46
+ end
47
+
48
+ def serialize(data)
49
+ Postmark::Json.encode(data)
50
+ end
51
+
52
+ def take_response_of
53
+ [yield, nil]
54
+ rescue DeliveryError => e
55
+ [e.full_response || {}, e]
56
+ end
57
+
58
+ def format_response(response, compatible = false)
59
+ return {} unless response
60
+
61
+ if response.kind_of? Array
62
+ response.map { |entry| Postmark::HashHelper.to_ruby(entry, compatible) }
63
+ else
64
+ Postmark::HashHelper.to_ruby(response, compatible)
65
+ end
66
+ end
67
+
68
+ def get_resource_count(path, options = {})
69
+ # At this point Postmark API returns 0 as total if you request 0 documents
70
+ total_count, _ = load_batch(path, nil, options.merge(:count => 1))
71
+ total_count
72
+ end
73
+
74
+ def load_batch(path, name, options)
75
+ options[:offset] ||= 0
76
+ options[:count] ||= 30
77
+ response = http_client.get(path, options)
78
+ [response['TotalCount'], format_response(response[name])]
79
+ end
80
+
81
+ end
82
+ end
@@ -6,9 +6,10 @@ module Postmark
6
6
  attr_accessor :api_key
7
7
  attr_reader :http, :secure, :proxy_host, :proxy_port, :proxy_user,
8
8
  :proxy_pass, :host, :port, :path_prefix,
9
- :http_open_timeout, :http_read_timeout
9
+ :http_open_timeout, :http_read_timeout, :auth_header_name
10
10
 
11
11
  DEFAULTS = {
12
+ :auth_header_name => 'X-Postmark-Server-Token',
12
13
  :host => 'api.postmarkapp.com',
13
14
  :secure => false,
14
15
  :path_prefix => '/',
@@ -35,6 +36,10 @@ module Postmark
35
36
  do_request { |client| client.get(url_path(path + to_query_string(query)), headers) }
36
37
  end
37
38
 
39
+ def delete(path, query = {})
40
+ do_request { |client| client.delete(url_path(path + to_query_string(query)), headers) }
41
+ end
42
+
38
43
  protected
39
44
 
40
45
  def apply_options(options = {})
@@ -74,7 +79,7 @@ module Postmark
74
79
  end
75
80
 
76
81
  def headers
77
- HEADERS.merge({ "X-Postmark-Server-Token" => self.api_key.to_s })
82
+ HEADERS.merge(self.auth_header_name => self.api_key.to_s)
78
83
  end
79
84
 
80
85
  def url_path(path)
@@ -1,3 +1,3 @@
1
1
  module Postmark
2
- VERSION = "1.1.2"
2
+ VERSION = "1.2.0"
3
3
  end
data/lib/postmark.rb CHANGED
@@ -10,7 +10,9 @@ require 'postmark/bounce'
10
10
  require 'postmark/inbound'
11
11
  require 'postmark/json'
12
12
  require 'postmark/http_client'
13
+ require 'postmark/client'
13
14
  require 'postmark/api_client'
15
+ require 'postmark/account_api_client'
14
16
  require 'postmark/message_extensions/shared'
15
17
  require 'postmark/message_extensions/mail'
16
18
  require 'postmark/handlers/mail'
@@ -0,0 +1,78 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Account API client usage' do
4
+
5
+ subject { Postmark::AccountApiClient.new(ENV['POSTMARK_ACCOUNT_API_KEY'],
6
+ :http_open_timeout => 15,
7
+ :http_read_timeout => 15) }
8
+ let(:unique_token) { rand(36**32).to_s(36) }
9
+ let(:unique_from_email) { ENV['POSTMARK_CI_RECIPIENT'].gsub(/(\+.+)?@/, "+#{unique_token}@") }
10
+
11
+ it 'can be used to manage senders' do
12
+ new_sender = nil
13
+
14
+ # create & count
15
+ expect {
16
+ new_sender = subject.create_sender(:name => 'Integration Test',
17
+ :from_email => unique_from_email)
18
+ }.to change { subject.get_senders_count }.by(1)
19
+
20
+ # get
21
+ expect(subject.get_sender(new_sender[:id])[:id]).to eq(new_sender[:id])
22
+
23
+ # list
24
+ senders = subject.get_senders(:count => 50)
25
+ expect(senders.map { |s| s[:id] }).to include(new_sender[:id])
26
+
27
+ # collection
28
+ expect(subject.senders.map { |s| s[:id] }).to include(new_sender[:id])
29
+
30
+ # update
31
+ updated_sender = subject.update_sender(new_sender[:id], :name => 'New Name')
32
+ expect(updated_sender[:name]).to eq('New Name')
33
+ expect(updated_sender[:id]).to eq(new_sender[:id])
34
+
35
+ # spf
36
+ expect(subject.verified_sender_spf?(new_sender[:id])).to be_false
37
+
38
+ # resend
39
+ expect { subject.resend_sender_confirmation(new_sender[:id]) }.not_to raise_error
40
+
41
+ # dkim
42
+ expect { subject.request_new_sender_dkim(new_sender[:id]) }.
43
+ to raise_error(Postmark::InvalidMessageError,
44
+ 'This DKIM is already being renewed.')
45
+
46
+ # delete
47
+ expect { subject.delete_sender(new_sender[:id]) }.not_to raise_error
48
+ end
49
+
50
+ it 'can be used to manage servers' do
51
+ new_server = nil
52
+
53
+ # create & count
54
+ expect {
55
+ new_server = subject.create_server(:name => "server-#{unique_token}",
56
+ :color => 'red')
57
+ }.to change { subject.get_servers_count }.by(1)
58
+
59
+ # get
60
+ expect(subject.get_server(new_server[:id])[:id]).to eq(new_server[:id])
61
+
62
+ # list
63
+ servers = subject.get_servers(:count => 50)
64
+ expect(servers.map { |s| s[:id] }).to include(new_server[:id])
65
+
66
+ # collection
67
+ expect(subject.servers.map { |s| s[:id] }).to include(new_server[:id])
68
+
69
+ # update
70
+ updated_server = subject.update_server(new_server[:id], :color => 'blue')
71
+ expect(updated_server[:color]).to eq('blue')
72
+ expect(updated_server[:id]).to eq(new_server[:id])
73
+
74
+ # delete
75
+ expect { subject.delete_server(new_server[:id]) }.not_to raise_error
76
+ end
77
+
78
+ end
data/spec/spec_helper.rb CHANGED
@@ -27,6 +27,16 @@ RSpec.configure do |config|
27
27
  RUBY_PLATFORM.to_s =~ /^#{platform.to_s}/
28
28
  }
29
29
 
30
+ config.filter_run_excluding :skip_ruby_version => lambda { |version|
31
+ versions = [*version]
32
+ versions.any? { |v| RUBY_VERSION.to_s =~ /^#{v.to_s}/ }
33
+ }
34
+
35
+ config.filter_run_excluding :exclusive_for_ruby_version => lambda { |version|
36
+ versions = [*version]
37
+ versions.all? { |v| !(RUBY_VERSION.to_s =~ /^#{v.to_s}/) }
38
+ }
39
+
30
40
  config.before(:each) do
31
41
  %w(api_client response_parser_class secure api_key proxy_host proxy_port
32
42
  proxy_user proxy_pass host port path_prefix http_open_timeout