mailgun-ruby 1.1.2 → 1.1.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,158 @@
1
+ require 'action_mailer'
2
+ require 'mailgun'
3
+ require 'rails'
4
+ require 'railgun/errors'
5
+
6
+ module Railgun
7
+
8
+ # Railgun::Mailer is an ActionMailer provider for sending mail through
9
+ # Mailgun.
10
+ class Mailer
11
+
12
+ # [Hash] config ->
13
+ # Requires *at least* `api_key` and `domain` keys.
14
+ attr_accessor :config, :domain
15
+
16
+ # Initialize the Railgun mailer.
17
+ #
18
+ # @param [Hash] config Hash of config values, typically from `app_config.action_mailer.mailgun_config`
19
+ def initialize(config)
20
+ @config = config
21
+
22
+ [:api_key, :domain].each do |k|
23
+ raise Railgun::ConfigurationError.new("Config requires `#{k}` key", @config) unless @config.has_key?(k)
24
+ end
25
+
26
+ @mg_client = Mailgun::Client.new(config[:api_key])
27
+ @domain = @config[:domain]
28
+
29
+ if (@config[:fake_message_send] || false)
30
+ Rails.logger.info "NOTE: fake message sending has been enabled for mailgun-ruby!"
31
+ @mg_client.enable_test_mode!
32
+ end
33
+ end
34
+
35
+ def deliver!(mail)
36
+ mg_message = Railgun.transform_for_mailgun(mail)
37
+ response = @mg_client.send_message(@domain, mg_message)
38
+
39
+ if response.code == 200 then
40
+ mg_id = response.body['id']
41
+ mail.message_id = mg_id
42
+ end
43
+ response
44
+ end
45
+
46
+ def mailgun_client
47
+ @mg_obj
48
+ end
49
+
50
+ end
51
+
52
+ module_function
53
+
54
+ # Performs a series of transformations on the `mailgun*` attributes.
55
+ # After prefixing them with the proper option type, they are added to
56
+ # the message hash where they will then be sent to the API as JSON.
57
+ #
58
+ # @param [Mail::Message] mail message to transform
59
+ #
60
+ # @return [Hash] transformed message hash
61
+ def transform_for_mailgun(mail)
62
+ message = build_message_object(mail)
63
+
64
+ # v:* attributes (variables)
65
+ mail.mailgun_variables.try(:each) do |k, v|
66
+ message["v:#{k}"] = v
67
+ end
68
+
69
+ # o:* attributes (options)
70
+ mail.mailgun_options.try(:each) do |k, v|
71
+ message["o:#{k}"] = v
72
+ end
73
+
74
+ # h:* attributes (headers)
75
+ mail.mailgun_headers.try(:each) do |k, v|
76
+ message["h:#{k}"] = v
77
+ end
78
+
79
+ # recipient variables
80
+ message['recipient-variables'] = mail.mailgun_recipient_variables.to_json if mail.mailgun_recipient_variables
81
+
82
+ # reject blank values
83
+ message.delete_if do |k, v|
84
+ v.nil? or (v.respond_to?(:empty) and v.empty?)
85
+ end
86
+
87
+ return message
88
+ end
89
+
90
+ # Acts on a Rails/ActionMailer message object and uses Mailgun::MessageBuilder
91
+ # to construct a new message.
92
+ #
93
+ # @param [Mail::Message] mail message to transform
94
+ #
95
+ # @returns [Hash] Message hash from Mailgun::MessageBuilder
96
+ def build_message_object(mail)
97
+ mb = Mailgun::MessageBuilder.new
98
+
99
+ mb.from mail[:from]
100
+ mb.reply_to(mail[:reply_to].to_s) if mail[:reply_to].present?
101
+ mb.subject mail.subject
102
+ mb.body_html extract_body_html(mail)
103
+ mb.body_text extract_body_text(mail)
104
+
105
+ [:to, :cc, :bcc].each do |rcpt_type|
106
+ addrs = mail[rcpt_type] || nil
107
+ case addrs
108
+ when String
109
+ # Likely a single recipient
110
+ mb.add_recipient rcpt_type.to_s, addrs
111
+ when Array
112
+ addrs.each do |addr|
113
+ mb.add_recipient rcpt_type.to_s, addr
114
+ end
115
+ when Mail::Field
116
+ mb.add_recipient rcpt_type.to_s, addrs.to_s
117
+ end
118
+ end
119
+
120
+ return mb.message if mail.attachments.empty?
121
+
122
+ mail.attachments.each do |attach|
123
+ attach = Attachment.new(attach, encoding: 'ascii-8bit', inline: attach.inline?)
124
+ attach.attach_to_message! mb
125
+ end
126
+
127
+ return mb.message
128
+ end
129
+
130
+ # Returns the decoded HTML body from the Mail::Message object if available,
131
+ # otherwise nil.
132
+ #
133
+ # @param [Mail::Message] mail message to transform
134
+ #
135
+ # @return [String]
136
+ def extract_body_html(mail)
137
+ begin
138
+ (mail.html_part || mail).body.decoded || nil
139
+ rescue
140
+ nil
141
+ end
142
+ end
143
+
144
+ # Returns the decoded text body from the Mail::Message object if it is available,
145
+ # otherwise nil.
146
+ #
147
+ # @param [Mail::Message] mail message to transform
148
+ #
149
+ # @return [String]
150
+ def extract_body_text(mail)
151
+ begin
152
+ (mail.text_part || mail).body.decoded || nil
153
+ rescue
154
+ nil
155
+ end
156
+ end
157
+
158
+ end
@@ -0,0 +1,17 @@
1
+ require 'mail'
2
+ require 'mailgun/messages/message_builder'
3
+ require 'railgun/attachment'
4
+ require 'railgun/errors'
5
+
6
+ module Mail
7
+
8
+ class Message
9
+
10
+ # Attributes to hold Mailgun-specific information
11
+ attr_accessor :mailgun_variables,
12
+ :mailgun_options,
13
+ :mailgun_recipient_variables,
14
+ :mailgun_headers
15
+
16
+ end
17
+ end
@@ -0,0 +1,9 @@
1
+ require 'railgun/mailer'
2
+
3
+ module Railgun
4
+ class Railtie < ::Rails::Railtie
5
+ config.before_configuration do
6
+ ActionMailer::Base.add_delivery_method :mailgun, Railgun::Mailer
7
+ end
8
+ end
9
+ end
data/lib/railgun.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'railgun/railtie'
2
+ require 'railgun/attachment'
3
+ require 'railgun/errors'
4
+ require 'railgun/mailer'
5
+ require 'railgun/message'
6
+
7
+ module Railgun
8
+ end
data/mailgun.gemspec CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
9
9
  spec.version = Mailgun::VERSION
10
10
  spec.homepage = 'http://www.mailgun.com'
11
11
  spec.platform = Gem::Platform::RUBY
12
- spec.license = 'Apache'
12
+ spec.license = 'Apache-2.0'
13
13
 
14
14
  spec.summary = "Mailgun's Official Ruby SDK"
15
15
  spec.description = "Mailgun's Official Ruby SDK for interacting with the Mailgun API."
@@ -1,5 +1,6 @@
1
1
  require 'spec_helper'
2
2
  require 'mailgun'
3
+ require 'mailgun/events/events'
3
4
 
4
5
  vcr_opts = { :cassette_name => "events" }
5
6
 
@@ -7,9 +8,10 @@ describe 'For the Events endpoint', vcr: vcr_opts do
7
8
  before(:all) do
8
9
  @mg_obj = Mailgun::Client.new(APIKEY, APIHOST, APIVERSION, SSL)
9
10
  @domain = TESTDOMAIN
11
+ @events = Mailgun::Events.new(@mg_obj, @domain)
10
12
  end
11
13
 
12
- it 'get an event.' do
14
+ it 'can get an event.' do
13
15
  result = @mg_obj.get("#{@domain}/events", {:limit => 1})
14
16
 
15
17
  result.to_h!
@@ -17,4 +19,10 @@ describe 'For the Events endpoint', vcr: vcr_opts do
17
19
  expect(result.body["paging"]).to include("next")
18
20
  expect(result.body["paging"]).to include("previous")
19
21
  end
22
+
23
+ it 'can iterate over all events with `each`' do
24
+ @events.each do |e|
25
+ expect(e.id).to eq("JAx9z641TuGGUyaJlD9sCQ")
26
+ end
27
+ end
20
28
  end
File without changes
@@ -0,0 +1,126 @@
1
+ require 'spec_helper'
2
+
3
+ require 'mailgun'
4
+ require 'mailgun/suppressions'
5
+
6
+ vcr_opts = { :cassette_name => 'suppressions' }
7
+
8
+ describe 'For the suppressions handling class', order: :defined, vcr: vcr_opts do
9
+
10
+ before(:all) do
11
+ @mg_obj = Mailgun::Client.new(APIKEY)
12
+ @suppress = Mailgun::Suppressions.new(@mg_obj, TESTDOMAIN)
13
+
14
+ @addresses = ['test1@example.com', 'test2@example.org', 'test3@example.net', 'test4@example.info']
15
+ end
16
+
17
+ it 'can batch-add bounces' do
18
+ bounces = []
19
+ @addresses.each do |addr|
20
+ bounces.push({
21
+ :address => addr,
22
+ :code => 500,
23
+ :error => 'integration testing',
24
+ })
25
+ end
26
+
27
+ response, nested = @suppress.create_bounces bounces
28
+ response.to_h!
29
+
30
+ expect(response.code).to eq(200)
31
+ expect(response.body['message']).to eq('4 addresses have been added to the bounces table')
32
+ expect(nested.length).to eq(0)
33
+ end
34
+
35
+ it 'raises ParameterError if no bounce[:address] is present' do
36
+ bounces = []
37
+ bounces.push({
38
+ :code => 500,
39
+ :error => 'integration testing',
40
+ })
41
+
42
+ expect { @suppress.create_bounces bounces }.to raise_error(Mailgun::ParameterError)
43
+ end
44
+
45
+ it 'removes a single bounce address' do
46
+ @addresses.each do |addr|
47
+ response = @suppress.delete_bounce addr
48
+ response.to_h!
49
+
50
+ expect(response.code).to eq(200)
51
+ expect(response.body['message']).to eq('Bounced address has been removed')
52
+ end
53
+ end
54
+
55
+ it 'can batch-add unsubscribes' do
56
+ unsubscribes = []
57
+ @addresses.each do |addr|
58
+ unsubscribes.push({
59
+ :address => addr,
60
+ :tag => 'integration',
61
+ })
62
+ end
63
+
64
+ response, nested = @suppress.create_unsubscribes unsubscribes
65
+ response.to_h!
66
+
67
+ expect(response.code).to eq(200)
68
+ expect(response.body['message']).to eq('4 addresses have been added to the unsubscribes table')
69
+ expect(nested.length).to eq(0)
70
+ end
71
+
72
+ it 'raises ParameterError if no unsubscribe[:address] is present' do
73
+ unsubscribes = []
74
+ unsubscribes.push({
75
+ :tag => 'integration',
76
+ })
77
+
78
+ expect { @suppress.create_unsubscribes unsubscribes }.to raise_error(Mailgun::ParameterError)
79
+ end
80
+
81
+ it 'removes a single unsubscribe address' do
82
+ @addresses.each do |addr|
83
+ response = @suppress.delete_unsubscribe addr
84
+ response.to_h!
85
+
86
+ expect(response.code).to eq(200)
87
+ expect(response.body['message']).to eq('Unsubscribe event has been removed')
88
+ end
89
+ end
90
+
91
+ it 'can batch-add complaints' do
92
+ complaints = []
93
+ @addresses.each do |addr|
94
+ complaints.push :address => addr
95
+ end
96
+
97
+ response, nested = @suppress.create_complaints complaints
98
+ response.to_h!
99
+
100
+ expect(response.code).to eq(200)
101
+ expect(response.body['message']).to eq('4 complaint addresses have been added to the complaints table')
102
+ expect(nested.length).to eq(0)
103
+ end
104
+
105
+ it 'raises ParameterError if no complaint[:address] is present' do
106
+ complaints = []
107
+ complaints.push({
108
+ :tag => 'integration',
109
+ })
110
+
111
+ expect { @suppress.create_complaints complaints }.to raise_error(Mailgun::ParameterError)
112
+ end
113
+
114
+ it 'removes a single complaint address' do
115
+ @addresses.each do |addr|
116
+ response = @suppress.delete_complaint addr
117
+ response.to_h!
118
+
119
+ expect(response.code).to eq(200)
120
+ expect(response.body['message']).to eq('Spam complaint has been removed')
121
+ end
122
+ end
123
+
124
+ # TODO: Add tests for pagination support.
125
+ end
126
+
@@ -5,7 +5,7 @@ describe 'The method get' do
5
5
  @mg_obj = Mailgun::UnitClient.new('events')
6
6
  events = Mailgun::Events.new(@mg_obj, "samples.mailgun.org")
7
7
  result = events.get()
8
-
8
+
9
9
  expect(result.body).to include("items")
10
10
  expect(result.body).to include("paging")
11
11
  end
@@ -17,7 +17,7 @@ describe 'The method next' do
17
17
  @mg_obj = Mailgun::UnitClient.new('events')
18
18
  events = Mailgun::Events.new(@mg_obj, "samples.mailgun.org")
19
19
  result = events.next()
20
-
20
+
21
21
  expect(result.body).to include("items")
22
22
  expect(result.body).to include("paging")
23
23
  end
@@ -33,3 +33,18 @@ describe 'The method previous' do
33
33
  expect(result.body).to include("paging")
34
34
  end
35
35
  end
36
+
37
+ describe 'The method each' do
38
+ it 'should iterate over all event items.' do
39
+ @mg_obj = Mailgun::UnitClient.new('events')
40
+ events = Mailgun::Events.new(@mg_obj, "samples.mailgun.org")
41
+ # Events from the UnitClient are actually empty.
42
+ count = 0
43
+ events.each do |e|
44
+ count = count + 1
45
+ end
46
+
47
+ # Better than nothing..
48
+ expect(count).to eq(0)
49
+ end
50
+ end
@@ -70,7 +70,7 @@ describe 'The method add_recipient' do
70
70
  recipient_type = 'h:reply-to'
71
71
  @mb_obj.add_recipient(recipient_type, @address, @variables)
72
72
 
73
- expect(@mb_obj.message[recipient_type][0]).to eq("'#{@variables['first']} #{@variables['last']}' <#{@address}>")
73
+ expect(@mb_obj.message[recipient_type]).to eq("'#{@variables['first']} #{@variables['last']}' <#{@address}>")
74
74
  @mb_obj.counters[:recipients].each_value{|value| expect(value).to eq(0)}
75
75
  end
76
76
 
@@ -249,6 +249,31 @@ describe 'The method add_inline_image' do
249
249
  end
250
250
  end
251
251
 
252
+ describe 'The method list_unsubscribe' do
253
+ before(:each) do
254
+ @mb_obj = Mailgun::MessageBuilder.new
255
+ end
256
+
257
+ it 'sets the message list_unsubscribe to blank if called and no parameters are provided' do
258
+ @mb_obj.list_unsubscribe
259
+ expect(@mb_obj.message['h:List-Unsubscribe']).to eq('')
260
+ end
261
+
262
+ it 'sets the message list_unsubscribe if called with one list_unsubscribe parameter' do
263
+ unsubscribe_to = 'http://example.com/stop-hassle'
264
+ @mb_obj.list_unsubscribe(unsubscribe_to)
265
+ expect(@mb_obj.message['h:List-Unsubscribe']).to eq("<#{unsubscribe_to}>")
266
+ end
267
+
268
+ it 'sets the message list_unsubscribe if called with many list_unsubscribe parameters' do
269
+ unsubscribe_to = %w(http://example.com/stop-hassle mailto:stop-hassle@example.com)
270
+ @mb_obj.list_unsubscribe(*unsubscribe_to)
271
+ expect(@mb_obj.message['h:List-Unsubscribe']).to eq(
272
+ unsubscribe_to.map { |var| "<#{var}>" }.join(',')
273
+ )
274
+ end
275
+ end
276
+
252
277
  describe 'The method set_test_mode' do
253
278
  it 'warns of set_test_mode deprecation' do
254
279
  @mb_obj = Mailgun::MessageBuilder.new
@@ -485,19 +510,31 @@ describe 'The method header' do
485
510
  it 'accepts valid JSON and appends as data to the message.' do
486
511
  @mb_obj.header('my-data', '{"key":"value"}')
487
512
 
488
- expect(@mb_obj.message["v:my-data"][0]).to be_kind_of(String)
489
- expect(@mb_obj.message["v:my-data"][0].to_s).to eq('{"key"=>"value"}')
513
+ expect(@mb_obj.message["h:my-data"]).to be_kind_of(String)
514
+ expect(@mb_obj.message["h:my-data"].to_s).to eq('{"key":"value"}')
515
+ end
516
+ end
517
+
518
+ describe 'The method variable' do
519
+ before(:each) do
520
+ @mb_obj = Mailgun::MessageBuilder.new
521
+ end
522
+ it 'accepts valid JSON and stores it as message[param].' do
523
+ @mb_obj.variable('my-data', '{"key":"value"}')
524
+
525
+ expect(@mb_obj.message["v:my-data"]).to be_kind_of(String)
526
+ expect(@mb_obj.message["v:my-data"].to_s).to eq('{"key":"value"}')
490
527
  end
491
528
  it 'accepts a hash and appends as data to the message.' do
492
529
  data = {'key' => 'value'}
493
- @mb_obj.header('my-data', data)
530
+ @mb_obj.variable('my-data', data)
494
531
 
495
- expect(@mb_obj.message["v:my-data"][0]).to be_kind_of(String)
496
- expect(@mb_obj.message["v:my-data"][0].to_s).to eq('{"key"=>"value"}')
532
+ expect(@mb_obj.message["v:my-data"]).to be_kind_of(String)
533
+ expect(@mb_obj.message["v:my-data"].to_s).to eq('{"key":"value"}')
497
534
  end
498
535
  it 'throws an exception on broken JSON.' do
499
536
  data = 'This is some crappy JSON.'
500
- expect {@mb_obj.header('my-data', data)}.to raise_error(Mailgun::ParameterError)
537
+ expect {@mb_obj.variable('my-data', data)}.to raise_error(Mailgun::ParameterError)
501
538
  end
502
539
  end
503
540
 
@@ -58,4 +58,51 @@ http_interactions:
58
58
  \ }\n}"
59
59
  http_version:
60
60
  recorded_at: Thu, 07 Jan 2016 22:08:06 GMT
61
- recorded_with: VCR 3.0.1
61
+ - request:
62
+ method: get
63
+ uri: https://api:<APIKEY>@api.mailgun.net/v3/DOMAIN.TEST/events
64
+ body:
65
+ encoding: US-ASCII
66
+ string: ''
67
+ headers:
68
+ Accept:
69
+ - "*/*"
70
+ Accept-Encoding:
71
+ - gzip, deflate
72
+ User-Agent:
73
+ - rest-client/2.0.1 (linux-gnu x86_64) ruby/2.3.3p222
74
+ Host:
75
+ - api.mailgun.net
76
+ response:
77
+ status:
78
+ code: 200
79
+ message: OK
80
+ headers:
81
+ Access-Control-Allow-Headers:
82
+ - Content-Type, x-requested-with
83
+ Access-Control-Allow-Methods:
84
+ - GET, POST, PUT, DELETE, OPTIONS
85
+ Access-Control-Allow-Origin:
86
+ - "*"
87
+ Access-Control-Max-Age:
88
+ - '600'
89
+ Content-Type:
90
+ - application/json
91
+ Date:
92
+ - Wed, 10 May 2017 20:06:54 GMT
93
+ Server:
94
+ - nginx
95
+ Content-Length:
96
+ - '2060'
97
+ Connection:
98
+ - keep-alive
99
+ body:
100
+ encoding: UTF-8
101
+ string: "{\n \"items\": [], \n \"paging\": {\n \"next\": \"https://api.mailgun.net/v3/DOMAIN.TEST/events/W3siYiI6ICIyMDE3LTA1LTEwVDIwOjA2OjU0LjU3NiswMDowMCIsICJlIjogIjIwMTctMDUtMDhUMjA6MDY6NTQuNTc3KzAwOjAwIn0sIHsiYiI6ICIyMDE3LTA1LTEwVDIwOjA2OjU0LjU3NiswMDowMCIsICJlIjogIjIwMTctMDUtMDhUMjA6MDY6NTQuNTc3KzAwOjAwIn0sIFsiZiJdLCBudWxsLCBbWyJhY2NvdW50LmlkIiwgIjU4MDUyMTg2NzhmYTE2MTNjNzkwYjUwZiJdLCBbImRvbWFpbi5uYW1lIiwgInNhbmRib3gyOTcwMTUyYWYzZDM0NTU5YmZjN2U3MTcwM2E2Y2YyNC5tYWlsZ3VuLm9yZyJdXSwgMTAwLCBudWxsXQ==\",
102
+ \n \"last\": \"https://api.mailgun.net/v3/DOMAIN.TEST/events/W3siYiI6ICIyMDE3LTA1LTEwVDIwOjA2OjU0LjU3NiswMDowMCIsICJlIjogIjIwMTctMDUtMDhUMjA6MDY6NTQuNTc3KzAwOjAwIn0sIHsiYiI6ICIyMDE3LTA1LTA4VDIwOjA2OjU0LjU3NyswMDowMCIsICJlIjogIjIwMTctMDUtMTBUMjA6MDY6NTQuNTc2KzAwOjAwIn0sIFsicCIsICJmIl0sIG51bGwsIFtbImFjY291bnQuaWQiLCAiNTgwNTIxODY3OGZhMTYxM2M3OTBiNTBmIl0sIFsiZG9tYWluLm5hbWUiLCAic2FuZGJveDI5NzAxNTJhZjNkMzQ1NTliZmM3ZTcxNzAzYTZjZjI0Lm1haWxndW4ub3JnIl1dLCAxMDAsIG51bGxd\",
103
+ \n \"first\": \"https://api.mailgun.net/v3/DOMAIN.TEST/events/W3siYiI6ICIyMDE3LTA1LTEwVDIwOjA2OjU0LjU3NiswMDowMCIsICJlIjogIjIwMTctMDUtMDhUMjA6MDY6NTQuNTc3KzAwOjAwIn0sIHsiYiI6ICIyMDE3LTA1LTEwVDIwOjA2OjU0LjU3NiswMDowMCIsICJlIjogIjIwMTctMDUtMDhUMjA6MDY6NTQuNTc3KzAwOjAwIn0sIFsiZiJdLCBudWxsLCBbWyJhY2NvdW50LmlkIiwgIjU4MDUyMTg2NzhmYTE2MTNjNzkwYjUwZiJdLCBbImRvbWFpbi5uYW1lIiwgInNhbmRib3gyOTcwMTUyYWYzZDM0NTU5YmZjN2U3MTcwM2E2Y2YyNC5tYWlsZ3VuLm9yZyJdXSwgMTAwLCBudWxsXQ==\",
104
+ \n \"previous\": \"https://api.mailgun.net/v3/DOMAIN.TEST/events/W3siYiI6ICIyMDE3LTA1LTEwVDIwOjA2OjU0LjU3NiswMDowMCIsICJlIjogIjIwMTctMDUtMDhUMjA6MDY6NTQuNTc3KzAwOjAwIn0sIHsiYiI6ICIyMDE3LTA1LTEwVDIwOjA2OjU0LjU3NiswMDowMCIsICJlIjogIjIwMTctMDUtMTBUMjA6MDY6NTQuNTc3KzAwOjAwIn0sIFsicCIsICJmIl0sIG51bGwsIFtbImFjY291bnQuaWQiLCAiNTgwNTIxODY3OGZhMTYxM2M3OTBiNTBmIl0sIFsiZG9tYWluLm5hbWUiLCAic2FuZGJveDI5NzAxNTJhZjNkMzQ1NTliZmM3ZTcxNzAzYTZjZjI0Lm1haWxndW4ub3JnIl1dLCAxMDAsIG51bGxd\"\n
105
+ \ }\n}"
106
+ http_version:
107
+ recorded_at: Wed, 10 May 2017 20:06:54 GMT
108
+ recorded_with: VCR 3.0.3