postmark 0.6.0 → 0.7.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/README.rdoc CHANGED
@@ -22,6 +22,7 @@ Then
22
22
 
23
23
  message = TMail::Mail.new
24
24
  # make sure you have a sender signature with that email
25
+ # from and to also accept arrays of emails.
25
26
  message.from = "leonard@bigbangtheory.com"
26
27
  message.to = "Sheldon Cooper <sheldon@bigbangtheory.com>"
27
28
  message.subject = "Hi Sheldon!"
@@ -29,11 +30,29 @@ Then
29
30
  message.body = "Hello my friend!"
30
31
  # set custom headers at will.
31
32
  message["CUSTOM-HEADER"] = "my custom header value"
33
+ # tag message
34
+ message.tag = "my-tracking-tag"
32
35
  # set reply to if you need; also, you can pass array of emails.
33
36
  message.reply_to = "penny@bigbangtheory.com"
34
37
 
35
38
  p Postmark.send_through_postmark(message).body
36
39
 
40
+ You can retrieve various information about your server state using the "Public bounces API":http://developer.postmarkapp.com/bounces
41
+
42
+ # get delivery stats
43
+ Postmark.delivery_stats # json
44
+ Postmark::Bounce.all # [ bounce, bounce ... ]
45
+ bounce = Postmark::Bounce.find(bounce_id) # Bounce
46
+ bounce.dump # string, containing raw SMTP data
47
+ bounce.reactivate # reactivate hard bounce
48
+
49
+ == Encryption
50
+
51
+ To use SSL encryption when sending email configure the library as follows:
52
+
53
+ Postmark.secure = true
54
+
55
+
37
56
  == Requirements
38
57
 
39
58
  The gem relies on TMail for building the message. You will also need postmark account, server and sender signature set up to use it.
@@ -46,7 +65,7 @@ You can also explicitly specify which one to be used, using
46
65
 
47
66
  == Limitations
48
67
 
49
- Currently postmark API does not support multiple recipients, or attachments. For more information, check the docs at:
68
+ Currently postmark API does not support attachments. For more information, check the docs at:
50
69
 
51
70
  http://developer.postmarkapp.com
52
71
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.6.0
1
+ 0.7.0
data/lib/postmark.rb CHANGED
@@ -3,6 +3,9 @@ require 'net/https'
3
3
  require 'rubygems'
4
4
  require 'tmail'
5
5
  require 'postmark/tmail_mail_extension'
6
+ require 'postmark/bounce'
7
+ require 'postmark/json'
8
+ require 'postmark/http_client'
6
9
 
7
10
  module Postmark
8
11
 
@@ -25,7 +28,7 @@ module Postmark
25
28
  MAX_RETRIES = 2
26
29
 
27
30
  class << self
28
- attr_accessor :host, :host_path, :port, :secure, :api_key, :http_open_timeout, :http_read_timeout,
31
+ attr_accessor :host, :path_prefix, :port, :secure, :api_key, :http_open_timeout, :http_read_timeout,
29
32
  :proxy_host, :proxy_port, :proxy_user, :proxy_pass, :max_retries, :sleep_between_retries
30
33
 
31
34
  attr_writer :response_parser_class
@@ -45,8 +48,8 @@ module Postmark
45
48
  end
46
49
 
47
50
  # The path of the listener
48
- def host_path
49
- @host_path ||= 'email'
51
+ def path_prefix
52
+ @path_prefix ||= '/'
50
53
  end
51
54
 
52
55
  def http_open_timeout
@@ -69,18 +72,10 @@ module Postmark
69
72
  yield self
70
73
  end
71
74
 
72
- def protocol #:nodoc:
73
- secure ? "https" : "http"
74
- end
75
-
76
- def url #:nodoc:
77
- URI.parse("#{protocol}://#{host}:#{port}/#{host_path}/")
78
- end
79
-
80
75
  def send_through_postmark(message) #:nodoc:
81
76
  @retries = 0
82
77
  begin
83
- attempt_sending(message)
78
+ HttpClient.post("email", Postmark::Json.encode(convert_tmail(message)))
84
79
  rescue Exception => e
85
80
  if @retries < max_retries
86
81
  @retries += 1
@@ -91,46 +86,11 @@ module Postmark
91
86
  end
92
87
  end
93
88
 
94
- def attempt_sending(message)
95
- ResponseParsers.const_get(response_parser_class) # loads JSON lib, defining #to_json
96
- http = Net::HTTP::Proxy(proxy_host,
97
- proxy_port,
98
- proxy_user,
99
- proxy_pass).new(url.host, url.port)
100
-
101
- http.read_timeout = http_read_timeout
102
- http.open_timeout = http_open_timeout
103
- http.use_ssl = !!secure
104
-
105
- headers = HEADERS.merge({ "X-Postmark-Server-Token" => api_key.to_s })
106
-
107
- response = http.post(url.path, convert_tmail(message).to_json, headers)
108
-
109
- case response.code.to_i
110
- when 200
111
- return response
112
- when 401
113
- raise InvalidApiKeyError, error_message(response.body)
114
- when 422
115
- raise InvalidMessageError, error_message(response.body)
116
- when 500
117
- raise InternalServerError, response.body
118
- else
119
- raise UnknownError, response
120
- end
89
+ def delivery_stats
90
+ HttpClient.get("deliverystats")
121
91
  end
122
92
 
123
- def error_message(response_body)
124
- decode_json(response_body)["Message"]
125
- end
126
-
127
- def decode_json(data)
128
- ResponseParsers.const_get(response_parser_class).decode(data)
129
- end
130
-
131
- def encode_json(data)
132
- ResponseParsers.const_get(response_parser_class).encode(data)
133
- end
93
+ protected
134
94
 
135
95
  def convert_tmail(message)
136
96
  options = { "From" => message['from'].to_s, "To" => message['to'].to_s, "Subject" => message.subject }
@@ -0,0 +1,56 @@
1
+ module Postmark
2
+ class Bounce
3
+
4
+ attr_reader :email, :bounced_at, :type, :details, :name, :id, :server_id, :tag
5
+
6
+ def initialize(values = {})
7
+ @id = values["ID"]
8
+ @email = values["Email"]
9
+ @bounced_at = Time.parse(values["BouncedAt"])
10
+ @type = values["Type"]
11
+ @name = values["Name"]
12
+ @details = values["Details"]
13
+ @tag = values["Tag"]
14
+ @dump_available = values["DumpAvailable"]
15
+ @inactive = values["Inactive"]
16
+ @can_activate = values["CanActivate"]
17
+ end
18
+
19
+ def inactive?
20
+ !!@inactive
21
+ end
22
+
23
+ def can_activate?
24
+ !!@can_activate
25
+ end
26
+
27
+ def dump
28
+ Postmark::HttpClient.get("bounces/#{id}/dump")["Body"]
29
+ end
30
+
31
+ def activate
32
+ Bounce.new(Postmark::HttpClient.put("bounces/#{id}/activate")["Bounce"])
33
+ end
34
+
35
+ def dump_available?
36
+ !!@dump_available
37
+ end
38
+
39
+ class << self
40
+ def find(id)
41
+ Bounce.new(Postmark::HttpClient.get("bounces/#{id}"))
42
+ end
43
+
44
+ def all(options = {})
45
+ options[:count] ||= 30
46
+ options[:offset] ||= 0
47
+ Postmark::HttpClient.get("bounces", options).map { |bounce_json| Bounce.new(bounce_json) }
48
+ end
49
+
50
+ def tags
51
+ Postmark::HttpClient.get("bounces/tags")
52
+ end
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,77 @@
1
+ require 'cgi'
2
+
3
+ module Postmark
4
+ module HttpClient
5
+ class << self
6
+ def post(path, data = '')
7
+ handle_response(http.post(url_path(path), data, headers))
8
+ end
9
+
10
+ def put(path, data = '')
11
+ handle_response(http.put(url_path(path), data, headers))
12
+ end
13
+
14
+ def get(path, query = {})
15
+ handle_response(http.get(url_path(path + to_query_string(query)), headers))
16
+ end
17
+
18
+ protected
19
+
20
+ def to_query_string(hash)
21
+ return "" if hash.empty?
22
+ "?" + hash.map { |key, value| "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}" }.join("&")
23
+ end
24
+
25
+ def protocol
26
+ Postmark.secure ? "https" : "http"
27
+ end
28
+
29
+ def url
30
+ URI.parse("#{protocol}://#{Postmark.host}:#{Postmark.port}/")
31
+ end
32
+
33
+ def handle_response(response)
34
+ case response.code.to_i
35
+ when 200
36
+ return Postmark::Json.decode(response.body)
37
+ when 401
38
+ raise InvalidApiKeyError, error_message(response.body)
39
+ when 422
40
+ raise InvalidMessageError, error_message(response.body)
41
+ when 500
42
+ raise InternalServerError, response.body
43
+ else
44
+ raise UnknownError, response
45
+ end
46
+ end
47
+
48
+ def headers
49
+ @headers ||= HEADERS.merge({ "X-Postmark-Server-Token" => Postmark.api_key.to_s })
50
+ end
51
+
52
+ def url_path(path)
53
+ Postmark.path_prefix + path
54
+ end
55
+
56
+ def http
57
+ @http ||= build_http
58
+ end
59
+
60
+ def build_http
61
+ http = Net::HTTP::Proxy(Postmark.proxy_host,
62
+ Postmark.proxy_port,
63
+ Postmark.proxy_user,
64
+ Postmark.proxy_pass).new(url.host, url.port)
65
+
66
+ http.read_timeout = Postmark.http_read_timeout
67
+ http.open_timeout = Postmark.http_open_timeout
68
+ http.use_ssl = !!Postmark.secure
69
+ http
70
+ end
71
+
72
+ def error_message(response_body)
73
+ Postmark::Json.decode(response_body)["Message"]
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,22 @@
1
+ module Postmark
2
+ module Json
3
+
4
+ class << self
5
+ def encode(data)
6
+ json_parser
7
+ data.to_json
8
+ end
9
+
10
+ def decode(data)
11
+ json_parser.decode(data)
12
+ end
13
+
14
+ private
15
+
16
+ def json_parser
17
+ ResponseParsers.const_get(Postmark.response_parser_class)
18
+ end
19
+
20
+ end
21
+ end
22
+ end
data/postmark.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{postmark}
8
- s.version = "0.6.0"
8
+ s.version = "0.7.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Petyo Ivanov"]
12
- s.date = %q{2010-03-18}
12
+ s.date = %q{2010-04-12}
13
13
  s.description = %q{Ruby gem for sending emails through http://postmarkapp.com HTTP API. It relieas on TMail::Mail for message construction.}
14
14
  s.email = %q{underlog@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -28,11 +28,15 @@ Gem::Specification.new do |s|
28
28
  "features/step_definitions/postmark_steps.rb",
29
29
  "features/support/env.rb",
30
30
  "lib/postmark.rb",
31
+ "lib/postmark/bounce.rb",
32
+ "lib/postmark/http_client.rb",
33
+ "lib/postmark/json.rb",
31
34
  "lib/postmark/response_parsers/active_support.rb",
32
35
  "lib/postmark/response_parsers/json.rb",
33
36
  "lib/postmark/response_parsers/yajl.rb",
34
37
  "lib/postmark/tmail_mail_extension.rb",
35
38
  "postmark.gemspec",
39
+ "spec/bounce_spec.rb",
36
40
  "spec/postmark_spec.rb",
37
41
  "spec/spec.opts",
38
42
  "spec/spec_helper.rb"
@@ -49,7 +53,8 @@ Gem::Specification.new do |s|
49
53
  s.rubygems_version = %q{1.3.6}
50
54
  s.summary = %q{Ruby gem for sending emails through http://postmarkapp.com HTTP API}
51
55
  s.test_files = [
52
- "spec/postmark_spec.rb",
56
+ "spec/bounce_spec.rb",
57
+ "spec/postmark_spec.rb",
53
58
  "spec/spec_helper.rb"
54
59
  ]
55
60
 
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Bounce" do
4
+ let(:bounce_json) { %{{"Type":"HardBounce","TypeCode":1,"Details":"test bounce","Email":"jim@test.com","BouncedAt":"#{Time.now.to_s}","DumpAvailable":true,"Inactive":false,"CanActivate":true,"ID":12}} }
5
+ let(:bounces_json) { "[#{bounce_json},#{bounce_json}]" }
6
+
7
+ context "single bounce" do
8
+ let(:bounce) { Postmark::Bounce.find(12) }
9
+
10
+ before do
11
+ Timecop.freeze
12
+ FakeWeb.register_uri(:get, "http://api.postmarkapp.com/bounces/12", { :body => bounce_json })
13
+ end
14
+
15
+ after do
16
+ Timecop.return
17
+ end
18
+
19
+ it "should retrieve and parce bounce correctly" do
20
+ bounce.type.should == "HardBounce"
21
+ bounce.bounced_at.should == Time.now
22
+ bounce.details.should == "test bounce"
23
+ bounce.email.should == "jim@test.com"
24
+ end
25
+
26
+ it "should retrieve bounce dump" do
27
+ FakeWeb.register_uri(:get, "http://api.postmarkapp.com/bounces/12/dump", { :body => %{{"Body": "Some SMTP gibberish"}} } )
28
+ bounce.dump.should == "Some SMTP gibberish"
29
+ end
30
+
31
+ it "should activate inactive bounce" do
32
+ FakeWeb.register_uri(:put, "http://api.postmarkapp.com/bounces/12/activate", { :body => %{{"Message":"OK","Bounce":#{bounce_json}}} } )
33
+ bounce.activate.should be_a(Postmark::Bounce)
34
+ end
35
+
36
+ end
37
+
38
+ it "should retrieve bounces" do
39
+ FakeWeb.register_uri(:get, "http://api.postmarkapp.com/bounces?count=30&offset=0", { :body => bounces_json })
40
+ bounces = Postmark::Bounce.all
41
+ bounces.should have(2).entries
42
+ bounces[0].should be_a(Postmark::Bounce)
43
+ end
44
+
45
+ it "should retrieve bounce tags" do
46
+ FakeWeb.register_uri(:get, "http://api.postmarkapp.com/bounces/tags", { :body => '["Signup","Commit Notification"]' })
47
+ tags = Postmark::Bounce.tags
48
+ tags.should have(2).entries
49
+ tags.first.should == "Signup"
50
+ end
51
+ end
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
1
+ require 'spec_helper'
2
2
 
3
3
  describe "Postmark" do
4
4
 
@@ -23,38 +23,38 @@ describe "Postmark" do
23
23
 
24
24
  context "service call" do
25
25
 
26
- before do
26
+ before(:all) do
27
27
  Postmark.sleep_between_retries = 0
28
28
  end
29
29
 
30
30
  it "should send email successfully" do
31
- FakeWeb.register_uri(:post, "http://api.postmarkapp.com/email/", {})
31
+ FakeWeb.register_uri(:post, "http://api.postmarkapp.com/email", {})
32
32
  Postmark.send_through_postmark(message)
33
- FakeWeb.should have_requested(:post, "http://api.postmarkapp.com/email/")
33
+ FakeWeb.should have_requested(:post, "http://api.postmarkapp.com/email")
34
34
  end
35
35
 
36
36
  it "should warn when header is invalid" do
37
- FakeWeb.register_uri(:post, "http://api.postmarkapp.com/email/", {:status => [ "401", "Unauthorized" ], :body => "Missing API token"})
37
+ FakeWeb.register_uri(:post, "http://api.postmarkapp.com/email", {:status => [ "401", "Unauthorized" ], :body => "Missing API token"})
38
38
  lambda { Postmark.send_through_postmark(message) }.should raise_error(Postmark::InvalidApiKeyError)
39
39
  end
40
40
 
41
41
  it "should warn when json is not ok" do
42
- FakeWeb.register_uri(:post, "http://api.postmarkapp.com/email/", {:status => [ "422", "Invalid" ], :body => "Invalid JSON"})
42
+ FakeWeb.register_uri(:post, "http://api.postmarkapp.com/email", {:status => [ "422", "Invalid" ], :body => "Invalid JSON"})
43
43
  lambda { Postmark.send_through_postmark(message) }.should raise_error(Postmark::InvalidMessageError)
44
44
  end
45
45
 
46
46
  it "should warn when server fails" do
47
- FakeWeb.register_uri(:post, "http://api.postmarkapp.com/email/", {:status => [ "500", "Internal Server Error" ]})
47
+ FakeWeb.register_uri(:post, "http://api.postmarkapp.com/email", {:status => [ "500", "Internal Server Error" ]})
48
48
  lambda { Postmark.send_through_postmark(message) }.should raise_error(Postmark::InternalServerError)
49
49
  end
50
50
 
51
51
  it "should warn when unknown stuff fails" do
52
- FakeWeb.register_uri(:post, "http://api.postmarkapp.com/email/", {:status => [ "485", "Custom HTTP response status" ]})
52
+ FakeWeb.register_uri(:post, "http://api.postmarkapp.com/email", {:status => [ "485", "Custom HTTP response status" ]})
53
53
  lambda { Postmark.send_through_postmark(message) }.should raise_error(Postmark::UnknownError)
54
54
  end
55
55
 
56
56
  it "should retry 3 times" do
57
- FakeWeb.register_uri(:post, "http://api.postmarkapp.com/email/",
57
+ FakeWeb.register_uri(:post, "http://api.postmarkapp.com/email",
58
58
  [ { :status => [ 500, "Internal Server Error" ] },
59
59
  { :status => [ 500, "Internal Server Error" ] },
60
60
  { } ]
@@ -63,19 +63,32 @@ describe "Postmark" do
63
63
  end
64
64
  end
65
65
 
66
+ context "delivery stats" do
67
+ let(:response_body) { %{{"InactiveMails":1,"Bounces":[{"TypeCode":0,"Name":"All","Count":2},{"Type":"HardBounce","TypeCode":1,"Name":"Hard bounce","Count":1},{"Type":"SoftBounce","TypeCode":4096,"Name":"Soft bounce","Count":1}]}} }
68
+
69
+ it "should query the service for delivery stats" do
70
+ FakeWeb.register_uri(:get, "http://api.postmarkapp.com/deliverystats", { :body => response_body })
71
+ results = Postmark.delivery_stats
72
+ results["InactiveMails"].should == 1
73
+ results["Bounces"].should be_an(Array)
74
+ results["Bounces"].should have(3).entries
75
+ FakeWeb.should have_requested(:get, "http://api.postmarkapp.com/deliverystats")
76
+ end
77
+ end
78
+
66
79
  context "tmail parse" do
67
80
  it "should set text body for plain message" do
68
- Postmark.convert_tmail(message)['TextBody'].should_not be_nil
81
+ Postmark.send(:convert_tmail, message)['TextBody'].should_not be_nil
69
82
  end
70
83
 
71
84
  it "should set html body for html message" do
72
- Postmark.convert_tmail(html_message)['HtmlBody'].should_not be_nil
85
+ Postmark.send(:convert_tmail, html_message)['HtmlBody'].should_not be_nil
73
86
  end
74
87
  end
75
88
 
76
89
  def be_serialized_to(json)
77
90
  simple_matcher "be serialized to #{json}" do |message|
78
- Postmark.convert_tmail(message).should == JSON.parse(json)
91
+ Postmark.send(:convert_tmail, message).should == JSON.parse(json)
79
92
  end
80
93
  end
81
94
 
@@ -111,7 +124,7 @@ describe "Postmark" do
111
124
 
112
125
  it "decodes json with #{lib}" do
113
126
  Postmark.response_parser_class = lib
114
- Postmark.decode_json(%({"Message":"OK"})).should == { "Message" => "OK" }
127
+ Postmark::Json.decode(%({"Message":"OK"})).should == { "Message" => "OK" }
115
128
  end
116
129
 
117
130
  Postmark.response_parser_class = original_parser_class
data/spec/spec_helper.rb CHANGED
@@ -7,6 +7,7 @@ require 'json'
7
7
  require 'ruby-debug'
8
8
  require 'fakeweb'
9
9
  require 'fakeweb_matcher'
10
+ require 'timecop'
10
11
  require 'spec'
11
12
  require 'spec/autorun'
12
13
 
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 6
7
+ - 7
8
8
  - 0
9
- version: 0.6.0
9
+ version: 0.7.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Petyo Ivanov
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-03-18 00:00:00 +02:00
17
+ date: 2010-04-12 00:00:00 +03:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -74,11 +74,15 @@ files:
74
74
  - features/step_definitions/postmark_steps.rb
75
75
  - features/support/env.rb
76
76
  - lib/postmark.rb
77
+ - lib/postmark/bounce.rb
78
+ - lib/postmark/http_client.rb
79
+ - lib/postmark/json.rb
77
80
  - lib/postmark/response_parsers/active_support.rb
78
81
  - lib/postmark/response_parsers/json.rb
79
82
  - lib/postmark/response_parsers/yajl.rb
80
83
  - lib/postmark/tmail_mail_extension.rb
81
84
  - postmark.gemspec
85
+ - spec/bounce_spec.rb
82
86
  - spec/postmark_spec.rb
83
87
  - spec/spec.opts
84
88
  - spec/spec_helper.rb
@@ -113,5 +117,6 @@ signing_key:
113
117
  specification_version: 3
114
118
  summary: Ruby gem for sending emails through http://postmarkapp.com HTTP API
115
119
  test_files:
120
+ - spec/bounce_spec.rb
116
121
  - spec/postmark_spec.rb
117
122
  - spec/spec_helper.rb