postmark 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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