postmark 0.9.19 → 1.0.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/.travis.yml +8 -0
- data/CHANGELOG.rdoc +20 -0
- data/Gemfile +6 -0
- data/README.md +351 -91
- data/VERSION +1 -1
- data/lib/postmark.rb +40 -132
- data/lib/postmark/api_client.rb +162 -0
- data/lib/postmark/bounce.rb +20 -17
- data/lib/postmark/handlers/mail.rb +10 -3
- data/lib/postmark/helpers/hash_helper.rb +35 -0
- data/lib/postmark/helpers/message_helper.rb +62 -0
- data/lib/postmark/http_client.rb +44 -28
- data/lib/postmark/inbound.rb +21 -0
- data/lib/postmark/inflector.rb +28 -0
- data/lib/postmark/message_extensions/mail.rb +50 -5
- data/lib/postmark/message_extensions/shared.rb +23 -28
- data/lib/postmark/version.rb +1 -1
- data/postmark.gemspec +4 -7
- data/spec/data/empty.gif +0 -0
- data/spec/integration/api_client_hashes_spec.rb +101 -0
- data/spec/integration/api_client_messages_spec.rb +127 -0
- data/spec/integration/mail_delivery_method_spec.rb +80 -0
- data/spec/spec_helper.rb +15 -5
- data/spec/support/helpers.rb +11 -0
- data/spec/{shared_examples.rb → support/shared_examples.rb} +0 -0
- data/spec/unit/postmark/api_client_spec.rb +246 -0
- data/spec/unit/postmark/bounce_spec.rb +142 -0
- data/spec/unit/postmark/handlers/mail_spec.rb +39 -0
- data/spec/unit/postmark/helpers/hash_helper_spec.rb +34 -0
- data/spec/unit/postmark/helpers/message_helper_spec.rb +115 -0
- data/spec/unit/postmark/http_client_spec.rb +204 -0
- data/spec/unit/postmark/inbound_spec.rb +88 -0
- data/spec/unit/postmark/inflector_spec.rb +35 -0
- data/spec/unit/postmark/json_spec.rb +37 -0
- data/spec/unit/postmark/message_extensions/mail_spec.rb +205 -0
- data/spec/unit/postmark_spec.rb +164 -0
- metadata +45 -93
- data/lib/postmark/attachments_fix_for_mail.rb +0 -48
- data/lib/postmark/message_extensions/tmail.rb +0 -115
- data/spec/bounce_spec.rb +0 -53
- data/spec/postmark_spec.rb +0 -253
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
1.0.0
|
data/lib/postmark.rb
CHANGED
@@ -1,23 +1,28 @@
|
|
1
1
|
require 'net/http'
|
2
2
|
require 'net/https'
|
3
|
+
require 'thread' unless defined? Mutex # For Ruby 1.8.7
|
3
4
|
|
5
|
+
require 'postmark/inflector'
|
6
|
+
require 'postmark/helpers/hash_helper'
|
7
|
+
require 'postmark/helpers/message_helper'
|
4
8
|
require 'postmark/bounce'
|
9
|
+
require 'postmark/inbound'
|
5
10
|
require 'postmark/json'
|
6
11
|
require 'postmark/http_client'
|
12
|
+
require 'postmark/api_client'
|
7
13
|
require 'postmark/message_extensions/shared'
|
8
|
-
require 'postmark/message_extensions/tmail'
|
9
14
|
require 'postmark/message_extensions/mail'
|
10
15
|
require 'postmark/handlers/mail'
|
11
|
-
require 'postmark/attachments_fix_for_mail'
|
12
16
|
|
13
17
|
module Postmark
|
14
18
|
|
15
19
|
class DeliveryError < StandardError
|
16
|
-
attr_accessor :error_code
|
20
|
+
attr_accessor :error_code, :full_response
|
17
21
|
|
18
|
-
def initialize(message = nil, error_code = nil)
|
22
|
+
def initialize(message = nil, error_code = nil, full_response = nil)
|
19
23
|
super(message)
|
20
24
|
self.error_code = error_code
|
25
|
+
self.full_response = full_response
|
21
26
|
end
|
22
27
|
end
|
23
28
|
|
@@ -41,151 +46,54 @@ module Postmark
|
|
41
46
|
|
42
47
|
extend self
|
43
48
|
|
44
|
-
|
45
|
-
:proxy_host, :proxy_port, :proxy_user, :proxy_pass, :max_retries, :sleep_between_retries
|
49
|
+
@@api_client_mutex = Mutex.new
|
46
50
|
|
47
|
-
|
51
|
+
attr_accessor :secure, :api_key, :proxy_host, :proxy_port, :proxy_user,
|
52
|
+
:proxy_pass, :host, :port, :path_prefix,
|
53
|
+
:http_open_timeout, :http_read_timeout, :max_retries
|
54
|
+
|
55
|
+
attr_writer :response_parser_class, :api_client
|
48
56
|
|
49
57
|
def response_parser_class
|
50
58
|
@response_parser_class ||= defined?(ActiveSupport::JSON) ? :ActiveSupport : :Json
|
51
59
|
end
|
52
60
|
|
53
|
-
# The port on which your Postmark server runs.
|
54
|
-
def port
|
55
|
-
@port || (secure ? 443 : 80)
|
56
|
-
end
|
57
|
-
|
58
|
-
# The host to connect to.
|
59
|
-
def host
|
60
|
-
@host ||= 'api.postmarkapp.com'
|
61
|
-
end
|
62
|
-
|
63
|
-
# The path of the listener
|
64
|
-
def path_prefix
|
65
|
-
@path_prefix ||= '/'
|
66
|
-
end
|
67
|
-
|
68
|
-
def http_open_timeout
|
69
|
-
@http_open_timeout ||= 5
|
70
|
-
end
|
71
|
-
|
72
|
-
def http_read_timeout
|
73
|
-
@http_read_timeout ||= 15
|
74
|
-
end
|
75
|
-
|
76
|
-
def max_retries
|
77
|
-
@max_retries ||= 3
|
78
|
-
end
|
79
|
-
|
80
|
-
def sleep_between_retries
|
81
|
-
@sleep_between_retries ||= 10
|
82
|
-
end
|
83
|
-
|
84
61
|
def configure
|
85
62
|
yield self
|
86
63
|
end
|
87
64
|
|
88
|
-
def
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
options["Cc"] = message['cc'].to_s if message.cc
|
105
|
-
options["Bcc"] = Array[message.bcc].flatten.join(", ") if message.bcc
|
106
|
-
options["Subject"] = message.subject
|
107
|
-
options["Attachments"] = attachments unless attachments.empty?
|
108
|
-
options["Tag"] = message.tag.to_s if message.tag
|
109
|
-
options["Headers"] = headers if headers.size > 0
|
110
|
-
|
111
|
-
options = options.delete_if { |k,v| v.nil? }
|
112
|
-
|
113
|
-
html = message.body_html
|
114
|
-
text = message.body_text
|
115
|
-
|
116
|
-
if message.multipart?
|
117
|
-
options["HtmlBody"] = html
|
118
|
-
options["TextBody"] = text
|
119
|
-
elsif html
|
120
|
-
options["HtmlBody"] = html
|
121
|
-
else
|
122
|
-
options["TextBody"] = text
|
123
|
-
end
|
124
|
-
|
125
|
-
options
|
126
|
-
end
|
127
|
-
|
128
|
-
def delivery_stats
|
129
|
-
HttpClient.get("deliverystats")
|
130
|
-
end
|
131
|
-
|
132
|
-
protected
|
133
|
-
|
134
|
-
def with_retries
|
135
|
-
yield
|
136
|
-
rescue DeliveryError, Timeout::Error
|
137
|
-
retries = retries ? retries + 1 : 0
|
138
|
-
if retries < max_retries
|
139
|
-
retry
|
140
|
-
else
|
141
|
-
raise
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
def extract_headers_according_to_message_format(message)
|
146
|
-
if defined?(TMail) && message.is_a?(TMail::Mail)
|
147
|
-
headers = extract_tmail_headers(message)
|
148
|
-
elsif defined?(Mail) && message.kind_of?(Mail::Message)
|
149
|
-
headers = extract_mail_headers(message)
|
150
|
-
else
|
151
|
-
raise "Can't convert message to a valid hash of API options. Unknown message format."
|
65
|
+
def api_client
|
66
|
+
return @api_client if @api_client
|
67
|
+
|
68
|
+
@@api_client_mutex.synchronize do
|
69
|
+
@api_client ||= Postmark::ApiClient.new(
|
70
|
+
self.api_key,
|
71
|
+
:secure => self.secure,
|
72
|
+
:proxy_host => self.proxy_host,
|
73
|
+
:proxy_port => self.proxy_port,
|
74
|
+
:proxy_user => self.proxy_user,
|
75
|
+
:proxy_pass => self.proxy_pass,
|
76
|
+
:host => self.host,
|
77
|
+
:port => self.port,
|
78
|
+
:path_prefix => self.path_prefix,
|
79
|
+
:max_retries => self.max_retries
|
80
|
+
)
|
152
81
|
end
|
153
82
|
end
|
154
83
|
|
155
|
-
def
|
156
|
-
|
157
|
-
message.header.fields.each do |field|
|
158
|
-
key = field.name
|
159
|
-
value = field.value
|
160
|
-
next if bogus_headers.include? key.downcase
|
161
|
-
name = key.split(/-/).map {|i| i.capitalize }.join('-')
|
162
|
-
headers << { "Name" => name, "Value" => value }
|
163
|
-
end
|
164
|
-
headers
|
84
|
+
def deliver_message(*args)
|
85
|
+
api_client.deliver_message(*args)
|
165
86
|
end
|
87
|
+
alias_method :send_through_postmark, :deliver_message
|
166
88
|
|
167
|
-
def
|
168
|
-
|
169
|
-
message.each_header do |key, value|
|
170
|
-
next if bogus_headers.include? key.downcase
|
171
|
-
name = key.split(/-/).map {|i| i.capitalize }.join('-')
|
172
|
-
headers << { "Name" => name, "Value" => value.body }
|
173
|
-
end
|
174
|
-
headers
|
89
|
+
def deliver_messages(*args)
|
90
|
+
api_client.deliver_messages(*args)
|
175
91
|
end
|
176
92
|
|
177
|
-
def
|
178
|
-
|
179
|
-
return-path x-pm-rcpt
|
180
|
-
from reply-to
|
181
|
-
sender received
|
182
|
-
date content-type
|
183
|
-
cc bcc
|
184
|
-
subject tag
|
185
|
-
attachment
|
186
|
-
]
|
93
|
+
def delivery_stats(*args)
|
94
|
+
api_client.delivery_stats(*args)
|
187
95
|
end
|
188
96
|
|
189
|
-
self.response_parser_class = nil
|
190
|
-
|
191
97
|
end
|
98
|
+
|
99
|
+
Postmark.response_parser_class = nil
|
@@ -0,0 +1,162 @@
|
|
1
|
+
module Postmark
|
2
|
+
class ApiClient
|
3
|
+
attr_reader :http_client, :max_retries
|
4
|
+
attr_writer :max_batch_size
|
5
|
+
|
6
|
+
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
|
13
|
+
end
|
14
|
+
|
15
|
+
def deliver(message_hash = {})
|
16
|
+
data = serialize(MessageHelper.to_postmark(message_hash))
|
17
|
+
|
18
|
+
with_retries do
|
19
|
+
format_response http_client.post("email", data)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def deliver_in_batches(message_hashes)
|
24
|
+
in_batches(message_hashes) do |batch, offset|
|
25
|
+
data = serialize(batch.map { |h| MessageHelper.to_postmark(h) })
|
26
|
+
|
27
|
+
with_retries do
|
28
|
+
http_client.post("email/batch", data)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def deliver_message(message)
|
34
|
+
data = serialize(message.to_postmark_hash)
|
35
|
+
|
36
|
+
with_retries do
|
37
|
+
take_response_of { http_client.post("email", data) }.to do |response|
|
38
|
+
update_message(message, response)
|
39
|
+
format_response response, true
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def deliver_messages(messages)
|
45
|
+
in_batches(messages) do |batch, offset|
|
46
|
+
data = serialize(batch.map { |m| m.to_postmark_hash })
|
47
|
+
|
48
|
+
with_retries do
|
49
|
+
http_client.post("email/batch", data).tap do |response|
|
50
|
+
response.each_with_index do |r, i|
|
51
|
+
update_message(messages[offset + i], r)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def delivery_stats
|
59
|
+
response = format_response(http_client.get("deliverystats"), true)
|
60
|
+
|
61
|
+
if response[:bounces]
|
62
|
+
response[:bounces] = format_response(response[:bounces])
|
63
|
+
end
|
64
|
+
|
65
|
+
response
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_bounces(options = {})
|
69
|
+
format_response http_client.get("bounces", options)["Bounces"]
|
70
|
+
end
|
71
|
+
|
72
|
+
def get_bounced_tags
|
73
|
+
http_client.get("bounces/tags")
|
74
|
+
end
|
75
|
+
|
76
|
+
def get_bounce(id)
|
77
|
+
format_response http_client.get("bounces/#{id}")
|
78
|
+
end
|
79
|
+
|
80
|
+
def dump_bounce(id)
|
81
|
+
format_response http_client.get("bounces/#{id}/dump")
|
82
|
+
end
|
83
|
+
|
84
|
+
def activate_bounce(id)
|
85
|
+
format_response http_client.put("bounces/#{id}/activate")["Bounce"]
|
86
|
+
end
|
87
|
+
|
88
|
+
def server_info
|
89
|
+
format_response http_client.get("server")
|
90
|
+
end
|
91
|
+
|
92
|
+
def update_server_info(attributes = {})
|
93
|
+
data = HashHelper.to_postmark(attributes)
|
94
|
+
format_response http_client.post("server", serialize(data))
|
95
|
+
end
|
96
|
+
|
97
|
+
def max_batch_size
|
98
|
+
@max_batch_size ||= 500
|
99
|
+
end
|
100
|
+
|
101
|
+
protected
|
102
|
+
|
103
|
+
def with_retries
|
104
|
+
yield
|
105
|
+
rescue DeliveryError
|
106
|
+
retries = retries ? retries + 1 : 1
|
107
|
+
if retries < self.max_retries
|
108
|
+
retry
|
109
|
+
else
|
110
|
+
raise
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def in_batches(messages)
|
115
|
+
r = messages.each_slice(max_batch_size).each_with_index.map do |batch, i|
|
116
|
+
yield batch, i * max_batch_size
|
117
|
+
end
|
118
|
+
|
119
|
+
format_response r.flatten
|
120
|
+
end
|
121
|
+
|
122
|
+
def update_message(message, response)
|
123
|
+
response ||= {}
|
124
|
+
message['Message-ID'] = response['MessageID']
|
125
|
+
message.delivered = !!response['MessageID']
|
126
|
+
message.postmark_response = response
|
127
|
+
end
|
128
|
+
|
129
|
+
def serialize(data)
|
130
|
+
Postmark::Json.encode(data)
|
131
|
+
end
|
132
|
+
|
133
|
+
def take_response_of
|
134
|
+
define_singleton_method(:to, yield)
|
135
|
+
rescue DeliveryError => e
|
136
|
+
define_singleton_method(:to, e.full_response || {}) do
|
137
|
+
raise e
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def define_singleton_method(name, object)
|
142
|
+
singleton_class = class << object; self; end
|
143
|
+
singleton_class.send(:define_method, name) do |&b|
|
144
|
+
ret = b.call(self) if b
|
145
|
+
yield if block_given?
|
146
|
+
ret
|
147
|
+
end
|
148
|
+
object
|
149
|
+
end
|
150
|
+
|
151
|
+
def format_response(response, compatible = false)
|
152
|
+
return {} unless response
|
153
|
+
|
154
|
+
if response.kind_of? Array
|
155
|
+
response.map { |entry| Postmark::HashHelper.to_ruby(entry, compatible) }
|
156
|
+
else
|
157
|
+
Postmark::HashHelper.to_ruby(response, compatible)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
end
|
data/lib/postmark/bounce.rb
CHANGED
@@ -6,18 +6,19 @@ module Postmark
|
|
6
6
|
attr_reader :email, :bounced_at, :type, :details, :name, :id, :server_id, :tag, :message_id, :subject
|
7
7
|
|
8
8
|
def initialize(values = {})
|
9
|
-
|
10
|
-
@
|
11
|
-
@
|
12
|
-
@
|
13
|
-
@
|
14
|
-
@
|
15
|
-
@
|
16
|
-
@
|
17
|
-
@
|
18
|
-
@
|
19
|
-
@
|
20
|
-
@
|
9
|
+
values = Postmark::HashHelper.to_ruby(values)
|
10
|
+
@id = values[:id]
|
11
|
+
@email = values[:email]
|
12
|
+
@bounced_at = Time.parse(values[:bounced_at])
|
13
|
+
@type = values[:type]
|
14
|
+
@name = values[:name]
|
15
|
+
@details = values[:details]
|
16
|
+
@tag = values[:tag]
|
17
|
+
@dump_available = values[:dump_available]
|
18
|
+
@inactive = values[:inactive]
|
19
|
+
@can_activate = values[:can_activate]
|
20
|
+
@message_id = values[:message_id]
|
21
|
+
@subject = values[:subject]
|
21
22
|
end
|
22
23
|
|
23
24
|
def inactive?
|
@@ -29,11 +30,11 @@ module Postmark
|
|
29
30
|
end
|
30
31
|
|
31
32
|
def dump
|
32
|
-
Postmark
|
33
|
+
Postmark.api_client.dump_bounce(id)[:body]
|
33
34
|
end
|
34
35
|
|
35
36
|
def activate
|
36
|
-
Bounce.new(Postmark
|
37
|
+
Bounce.new(Postmark.api_client.activate_bounce(id))
|
37
38
|
end
|
38
39
|
|
39
40
|
def dump_available?
|
@@ -42,17 +43,19 @@ module Postmark
|
|
42
43
|
|
43
44
|
class << self
|
44
45
|
def find(id)
|
45
|
-
Bounce.new(Postmark
|
46
|
+
Bounce.new(Postmark.api_client.get_bounce(id))
|
46
47
|
end
|
47
48
|
|
48
49
|
def all(options = {})
|
49
50
|
options[:count] ||= 30
|
50
51
|
options[:offset] ||= 0
|
51
|
-
Postmark
|
52
|
+
Postmark.api_client.get_bounces(options).map do |bounce_json|
|
53
|
+
Bounce.new(bounce_json)
|
54
|
+
end
|
52
55
|
end
|
53
56
|
|
54
57
|
def tags
|
55
|
-
Postmark
|
58
|
+
Postmark.api_client.get_bounced_tags
|
56
59
|
end
|
57
60
|
end
|
58
61
|
|
@@ -8,9 +8,16 @@ module Mail
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def deliver!(mail)
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
settings = self.settings.dup
|
12
|
+
api_key = settings.delete(:api_key)
|
13
|
+
api_client = ::Postmark::ApiClient.new(api_key, settings)
|
14
|
+
response = api_client.deliver_message(mail)
|
15
|
+
|
16
|
+
if settings[:return_response]
|
17
|
+
response
|
18
|
+
else
|
19
|
+
self
|
20
|
+
end
|
14
21
|
end
|
15
22
|
|
16
23
|
end
|