mailgunner 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -37,7 +37,23 @@ Environment variables
37
37
 
38
38
  Best practice for storing credentials for external services is to use environment
39
39
  variables, as described by [12factor.net/config](http://www.12factor.net/config).
40
-
41
40
  Mailgunner::Client defaults to extracting the domain and api_key values it needs
42
41
  from the MAILGUN_API_KEY and MAILGUN_SMTP_LOGIN environment variables. These will
43
42
  exist if you are using Mailgun on Heroku, or you can set them manually.
43
+
44
+
45
+ Email validation
46
+ ----------------
47
+
48
+ If you only need [email validation](http://documentation.mailgun.com/api-email-validation.html),
49
+ you can instead use your Mailgun public key to authenticate like this:
50
+
51
+ ```ruby
52
+ require 'mailgunner'
53
+
54
+ public_key = 'pubkey-5ogiflzbnjrljiky49qxsiozqef5jxp7'
55
+
56
+ mailgun = Mailgunner::Client.new(api_key: public_key)
57
+
58
+ response = mailgun.validate_address('john@gmail.com')
59
+ ```
@@ -1,14 +1,15 @@
1
1
  require 'net/http'
2
2
  require 'json'
3
3
  require 'cgi'
4
- require 'uri'
4
+ require 'mailgunner/response'
5
+ require 'mailgunner/delivery_method' if defined?(ActionMailer)
5
6
 
6
7
  module Mailgunner
7
8
  class Client
8
9
  attr_accessor :domain, :api_key, :http
9
10
 
10
11
  def initialize(options = {})
11
- @domain = options.fetch(:domain) { ENV.fetch('MAILGUN_SMTP_LOGIN').split('@').last }
12
+ @domain = options.fetch(:domain) { ENV['MAILGUN_SMTP_LOGIN'].to_s.split('@').last }
12
13
 
13
14
  @api_key = options.fetch(:api_key) { ENV.fetch('MAILGUN_API_KEY') }
14
15
 
@@ -25,18 +26,6 @@ module Mailgunner
25
26
  @http.use_ssl = true
26
27
  end
27
28
 
28
- def json
29
- Kernel.warn 'Mailgunner::Client#json is deprecated'
30
-
31
- @json
32
- end
33
-
34
- def json=(json)
35
- Kernel.warn 'Mailgunner::Client#json= is deprecated'
36
-
37
- @json = json
38
- end
39
-
40
29
  def validate_address(value)
41
30
  get('/v2/address/validate', address: value)
42
31
  end
@@ -49,6 +38,14 @@ module Mailgunner
49
38
  post("/v2/#{escape @domain}/messages", attributes)
50
39
  end
51
40
 
41
+ def send_mime(mail)
42
+ to = ['to', Array(mail.to).join(',')]
43
+
44
+ message = ['message', mail.encoded, {filename: 'message.mime'}]
45
+
46
+ multipart_post("/v2/#{escape @domain}/messages.mime", [to, message])
47
+ end
48
+
52
49
  def get_domains(params = {})
53
50
  get('/v2/domains', params)
54
51
  end
@@ -113,12 +110,6 @@ module Mailgunner
113
110
  get("/v2/#{escape @domain}/stats", params)
114
111
  end
115
112
 
116
- def get_log(params = {})
117
- Kernel.warn 'Mailgunner::Client#get_log is deprecated'
118
-
119
- get("/v2/#{escape @domain}/log", params)
120
- end
121
-
122
113
  def get_events(params = {})
123
114
  get("/v2/#{escape @domain}/events", params)
124
115
  end
@@ -143,30 +134,6 @@ module Mailgunner
143
134
  delete("/v2/routes/#{escape id}")
144
135
  end
145
136
 
146
- def get_mailboxes(params = {})
147
- Kernel.warn 'Mailgunner::Client#get_mailboxes is deprecated'
148
-
149
- get("/v2/#{escape @domain}/mailboxes", params)
150
- end
151
-
152
- def add_mailbox(attributes = {})
153
- Kernel.warn 'Mailgunner::Client#add_mailbox is deprecated'
154
-
155
- post("/v2/#{escape @domain}/mailboxes", attributes)
156
- end
157
-
158
- def update_mailbox(name, attributes = {})
159
- Kernel.warn 'Mailgunner::Client#update_mailbox is deprecated'
160
-
161
- put("/v2/#{escape @domain}/mailboxes/#{escape name}", attributes)
162
- end
163
-
164
- def delete_mailbox(name)
165
- Kernel.warn 'Mailgunner::Client#delete_mailbox is deprecated'
166
-
167
- delete("/v2/#{escape @domain}/mailboxes/#{escape name}")
168
- end
169
-
170
137
  def get_campaigns(params = {})
171
138
  get("/v2/#{escape @domain}/campaigns", params)
172
139
  end
@@ -258,25 +225,29 @@ module Mailgunner
258
225
  private
259
226
 
260
227
  def get(path, params = {})
261
- transmit(Net::HTTP::Get, request_uri(path, params))
228
+ transmit(Net::HTTP::Get.new(request_uri(path, params)))
262
229
  end
263
230
 
264
231
  def post(path, attributes = {})
265
- transmit(Net::HTTP::Post, path, attributes)
232
+ transmit(Net::HTTP::Post.new(path)) { |message| message.set_form_data(attributes) }
233
+ end
234
+
235
+ def multipart_post(path, data)
236
+ transmit(Net::HTTP::Post.new(path)) { |message| message.set_form(data, 'multipart/form-data') }
266
237
  end
267
238
 
268
239
  def put(path, attributes = {})
269
- transmit(Net::HTTP::Put, path, attributes)
240
+ transmit(Net::HTTP::Put.new(path)) { |message| message.set_form_data(attributes) }
270
241
  end
271
242
 
272
243
  def delete(path)
273
- transmit(Net::HTTP::Delete, path)
244
+ transmit(Net::HTTP::Delete.new(path))
274
245
  end
275
246
 
276
- def transmit(subclass, path, attributes = nil)
277
- message = subclass.new(path)
247
+ def transmit(message)
278
248
  message.basic_auth('api', @api_key)
279
- message.body = URI.encode_www_form(attributes) if attributes
249
+
250
+ yield message if block_given?
280
251
 
281
252
  Response.new(@http.request(message), :json => @json)
282
253
  end
@@ -301,40 +272,4 @@ module Mailgunner
301
272
  CGI.escape(component.to_s)
302
273
  end
303
274
  end
304
-
305
- class Response
306
- def initialize(http_response, options = {})
307
- @http_response = http_response
308
-
309
- @json = options.fetch(:json) { JSON }
310
- end
311
-
312
- def method_missing(name, *args, &block)
313
- @http_response.send(name, *args, &block)
314
- end
315
-
316
- def respond_to_missing?(name, include_private = false)
317
- @http_response.respond_to?(name)
318
- end
319
-
320
- def ok?
321
- code.to_i == 200
322
- end
323
-
324
- def client_error?
325
- (400 .. 499).include?(code.to_i)
326
- end
327
-
328
- def server_error?
329
- (500 .. 599).include?(code.to_i)
330
- end
331
-
332
- def json?
333
- self['Content-Type'].split(';').first == 'application/json'
334
- end
335
-
336
- def object
337
- @object ||= @json.parse(body)
338
- end
339
- end
340
275
  end
@@ -0,0 +1,38 @@
1
+ require 'mail/check_delivery_params'
2
+
3
+ module Mailgunner
4
+ class DeliveryFailed < StandardError
5
+ end
6
+
7
+ class DeliveryMethod
8
+ include Mail::CheckDeliveryParams
9
+
10
+ attr_accessor :settings
11
+
12
+ def initialize(values)
13
+ @settings = values
14
+
15
+ @client = if @settings.has_key?(:domain)
16
+ Client.new(domain: @settings[:domain])
17
+ else
18
+ Client.new
19
+ end
20
+ end
21
+
22
+ def deliver!(mail)
23
+ check_delivery_params(mail)
24
+
25
+ response = @client.send_mime(mail)
26
+
27
+ if response.ok?
28
+ return response
29
+ elsif response.json? && response.object.has_key?('message')
30
+ raise DeliveryFailed, response.object['message']
31
+ else
32
+ raise DeliveryFailed, "#{response.code} #{response.message}"
33
+ end
34
+ end
35
+ end
36
+
37
+ ActionMailer::Base.add_delivery_method :mailgun, DeliveryMethod
38
+ end
@@ -0,0 +1,37 @@
1
+ module Mailgunner
2
+ class Response
3
+ def initialize(http_response, options = {})
4
+ @http_response = http_response
5
+
6
+ @json = options.fetch(:json) { JSON }
7
+ end
8
+
9
+ def method_missing(name, *args, &block)
10
+ @http_response.send(name, *args, &block)
11
+ end
12
+
13
+ def respond_to_missing?(name, include_private = false)
14
+ @http_response.respond_to?(name)
15
+ end
16
+
17
+ def ok?
18
+ code.to_i == 200
19
+ end
20
+
21
+ def client_error?
22
+ (400 .. 499).include?(code.to_i)
23
+ end
24
+
25
+ def server_error?
26
+ (500 .. 599).include?(code.to_i)
27
+ end
28
+
29
+ def json?
30
+ self['Content-Type'].split(';').first == 'application/json'
31
+ end
32
+
33
+ def object
34
+ @object ||= @json.parse(body)
35
+ end
36
+ end
37
+ end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'mailgunner'
3
- s.version = '1.2.0'
3
+ s.version = '1.3.0'
4
4
  s.platform = Gem::Platform::RUBY
5
5
  s.authors = ['Tim Craft']
6
6
  s.email = ['mail@timcraft.com']
@@ -10,6 +10,8 @@ Gem::Specification.new do |s|
10
10
  s.files = Dir.glob('{lib,spec}/**/*') + %w(README.md mailgunner.gemspec)
11
11
  s.add_development_dependency('rake', '>= 0.9.3')
12
12
  s.add_development_dependency('mocha', '~> 0.13.2')
13
- s.add_development_dependency('faux', '~> 1.1.0')
13
+ s.add_development_dependency('webmock', '~> 1.13.0')
14
+ s.add_development_dependency('mail', '~> 2.5.4')
15
+ s.add_development_dependency('actionmailer', '~> 4.0.0')
14
16
  s.require_path = 'lib'
15
17
  end
@@ -0,0 +1,57 @@
1
+ require 'minitest/autorun'
2
+ require 'webmock/minitest'
3
+ require 'action_mailer'
4
+ require 'mailgunner'
5
+ require 'mailgunner/delivery_method'
6
+
7
+ class ExampleMailer < ActionMailer::Base
8
+ default from: 'testing@localhost'
9
+
10
+ def registration_confirmation(user)
11
+ mail to: user[:email], subject: 'Welcome!', body: 'Hello!'
12
+ end
13
+ end
14
+
15
+ describe 'Mailgunner::DeliveryMethod' do
16
+ before do
17
+ @api_key = 'xxx'
18
+
19
+ @domain = 'samples.mailgun.org'
20
+
21
+ @base_url = "https://api:#@api_key@api.mailgun.net/v2"
22
+
23
+ @address = 'user@example.com'
24
+
25
+ ActionMailer::Base.delivery_method = :mailgun
26
+
27
+ ENV['MAILGUN_API_KEY'] = @api_key
28
+
29
+ ENV['MAILGUN_SMTP_LOGIN'] = "postmaster@#@domain"
30
+ end
31
+
32
+ it 'delivers the mail to mailgun in mime format' do
33
+ stub_request(:post, "#@base_url/#@domain/messages.mime")
34
+
35
+ ExampleMailer.registration_confirmation(email: @address).deliver
36
+ end
37
+
38
+ it 'raises an exception if the api returns an error' do
39
+ stub_request(:post, "#@base_url/#@domain/messages.mime").to_return({
40
+ status: 403,
41
+ headers: {'Content-Type' => 'application/json'},
42
+ body: '{"message": "Invalid API key"}'
43
+ })
44
+
45
+ proc { ExampleMailer.registration_confirmation(email: @address).deliver }.must_raise(Mailgunner::DeliveryFailed)
46
+ end
47
+
48
+ it 'allows the domain to be specified explicitly via the delivery method settings' do
49
+ stub_request(:post, "#@base_url/app123.mailgun.org/messages.mime")
50
+
51
+ ActionMailer::Base.mailgun_settings = {domain: 'app123.mailgun.org'}
52
+
53
+ ExampleMailer.registration_confirmation(email: @address).deliver
54
+
55
+ ActionMailer::Base.mailgun_settings = {}
56
+ end
57
+ end
@@ -0,0 +1,101 @@
1
+ require 'minitest/autorun'
2
+ require 'mocha/setup'
3
+ require 'mailgunner'
4
+
5
+ describe 'Mailgunner::Response' do
6
+ before do
7
+ @http_response = mock()
8
+
9
+ @response = Mailgunner::Response.new(@http_response)
10
+ end
11
+
12
+ it 'delegates missing methods to the http response object' do
13
+ @http_response.stubs(:code).returns('200')
14
+
15
+ @response.code.must_equal('200')
16
+ end
17
+
18
+ describe 'ok query method' do
19
+ it 'returns true if the status code is 200' do
20
+ @http_response.expects(:code).returns('200')
21
+
22
+ @response.ok?.must_equal(true)
23
+ end
24
+
25
+ it 'returns false otherwise' do
26
+ @http_response.expects(:code).returns('400')
27
+
28
+ @response.ok?.must_equal(false)
29
+ end
30
+ end
31
+
32
+ describe 'client_error query method' do
33
+ it 'returns true if the status code is 4xx' do
34
+ @http_response.stubs(:code).returns(%w(400 401 402 404).sample)
35
+
36
+ @response.client_error?.must_equal(true)
37
+ end
38
+
39
+ it 'returns false otherwise' do
40
+ @http_response.stubs(:code).returns('200')
41
+
42
+ @response.client_error?.must_equal(false)
43
+ end
44
+ end
45
+
46
+ describe 'server_error query method' do
47
+ it 'returns true if the status code is 5xx' do
48
+ @http_response.stubs(:code).returns(%w(500 502 503 504).sample)
49
+
50
+ @response.server_error?.must_equal(true)
51
+ end
52
+
53
+ it 'returns false otherwise' do
54
+ @http_response.stubs(:code).returns('200')
55
+
56
+ @response.server_error?.must_equal(false)
57
+ end
58
+ end
59
+
60
+ describe 'json query method' do
61
+ it 'returns true if the response has a json content type' do
62
+ @http_response.expects(:[]).with('Content-Type').returns('application/json;charset=utf-8')
63
+
64
+ @response.json?.must_equal(true)
65
+ end
66
+
67
+ it 'returns false otherwise' do
68
+ @http_response.expects(:[]).with('Content-Type').returns('text/html')
69
+
70
+ @response.json?.must_equal(false)
71
+ end
72
+ end
73
+
74
+ describe 'object method' do
75
+ it 'decodes the response body as json and returns a hash' do
76
+ @http_response.expects(:body).returns('{"foo":"bar"}')
77
+
78
+ @response.object.must_equal({'foo' => 'bar'})
79
+ end
80
+ end
81
+ end
82
+
83
+ describe 'Mailgunner::Response initialized with an alternative json implementation' do
84
+ before do
85
+ @json = mock()
86
+
87
+ @http_response = stub
88
+
89
+ @response = Mailgunner::Response.new(@http_response, :json => @json)
90
+ end
91
+
92
+ describe 'object method' do
93
+ it 'uses the alternative json implementation to parse the response body' do
94
+ @http_response.stubs(:body).returns(response_body = '{"foo":"bar"}')
95
+
96
+ @json.expects(:parse).with(response_body)
97
+
98
+ @response.object
99
+ end
100
+ end
101
+ end