wj-mailgun-ruby 1.1.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +23 -0
  3. data/.rubocop.yml +8 -0
  4. data/.rubocop_todo.yml +22 -0
  5. data/.ruby-env.yml.example +12 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +24 -0
  8. data/Gemfile +6 -0
  9. data/LICENSE +191 -0
  10. data/README.md +241 -0
  11. data/Rakefile +35 -0
  12. data/docs/Domains.md +54 -0
  13. data/docs/Events.md +46 -0
  14. data/docs/MessageBuilder.md +105 -0
  15. data/docs/Messages.md +107 -0
  16. data/docs/OptInHandler.md +103 -0
  17. data/docs/Snippets.md +526 -0
  18. data/docs/Suppressions.md +82 -0
  19. data/docs/Webhooks.md +40 -0
  20. data/lib/mailgun-ruby.rb +2 -0
  21. data/lib/mailgun.rb +39 -0
  22. data/lib/mailgun/address.rb +45 -0
  23. data/lib/mailgun/chains.rb +16 -0
  24. data/lib/mailgun/client.rb +199 -0
  25. data/lib/mailgun/domains/domains.rb +84 -0
  26. data/lib/mailgun/events/events.rb +120 -0
  27. data/lib/mailgun/exceptions/exceptions.rb +65 -0
  28. data/lib/mailgun/lists/opt_in_handler.rb +58 -0
  29. data/lib/mailgun/messages/batch_message.rb +125 -0
  30. data/lib/mailgun/messages/message_builder.rb +413 -0
  31. data/lib/mailgun/response.rb +62 -0
  32. data/lib/mailgun/suppressions.rb +270 -0
  33. data/lib/mailgun/version.rb +4 -0
  34. data/lib/mailgun/webhooks/webhooks.rb +101 -0
  35. data/lib/railgun.rb +8 -0
  36. data/lib/railgun/attachment.rb +56 -0
  37. data/lib/railgun/errors.rb +27 -0
  38. data/lib/railgun/mailer.rb +161 -0
  39. data/lib/railgun/message.rb +17 -0
  40. data/lib/railgun/railtie.rb +9 -0
  41. data/mailgun.gemspec +37 -0
  42. data/spec/integration/bounces_spec.rb +44 -0
  43. data/spec/integration/campaign_spec.rb +60 -0
  44. data/spec/integration/complaints_spec.rb +38 -0
  45. data/spec/integration/domains_spec.rb +39 -0
  46. data/spec/integration/email_validation_spec.rb +57 -0
  47. data/spec/integration/events_spec.rb +28 -0
  48. data/spec/integration/list_members_spec.rb +63 -0
  49. data/spec/integration/list_spec.rb +58 -0
  50. data/spec/integration/mailgun_spec.rb +121 -0
  51. data/spec/integration/messages/sample_data/mime.txt +38 -0
  52. data/spec/integration/routes_spec.rb +74 -0
  53. data/spec/integration/stats_spec.rb +15 -0
  54. data/spec/integration/suppressions_spec.rb +126 -0
  55. data/spec/integration/unsubscribes_spec.rb +42 -0
  56. data/spec/integration/webhook_spec.rb +54 -0
  57. data/spec/spec_helper.rb +45 -0
  58. data/spec/unit/connection/test_client.rb +99 -0
  59. data/spec/unit/events/events_spec.rb +50 -0
  60. data/spec/unit/lists/opt_in_handler_spec.rb +24 -0
  61. data/spec/unit/mailgun_spec.rb +127 -0
  62. data/spec/unit/messages/batch_message_spec.rb +131 -0
  63. data/spec/unit/messages/message_builder_spec.rb +584 -0
  64. data/spec/unit/messages/sample_data/mailgun_icon.png +0 -0
  65. data/spec/unit/messages/sample_data/mime.txt +38 -0
  66. data/spec/unit/messages/sample_data/rackspace_logo.jpg +0 -0
  67. data/vcr_cassettes/bounces.yml +175 -0
  68. data/vcr_cassettes/complaints.yml +175 -0
  69. data/vcr_cassettes/domains.todo.yml +42 -0
  70. data/vcr_cassettes/domains.yml +360 -0
  71. data/vcr_cassettes/email_validation.yml +167 -0
  72. data/vcr_cassettes/events.yml +108 -0
  73. data/vcr_cassettes/exceptions.yml +45 -0
  74. data/vcr_cassettes/list_members.yml +320 -0
  75. data/vcr_cassettes/mailing_list.todo.yml +43 -0
  76. data/vcr_cassettes/mailing_list.yml +390 -0
  77. data/vcr_cassettes/routes.yml +359 -0
  78. data/vcr_cassettes/send_message.yml +107 -0
  79. data/vcr_cassettes/stats.yml +44 -0
  80. data/vcr_cassettes/suppressions.yml +676 -0
  81. data/vcr_cassettes/unsubscribes.yml +191 -0
  82. data/vcr_cassettes/webhooks.yml +276 -0
  83. metadata +263 -0
@@ -0,0 +1,56 @@
1
+ module Railgun
2
+
3
+ class Attachment < StringIO
4
+
5
+ attr_reader :filename, :content_type, :path,
6
+ :original_filename, :overwritten_filename
7
+
8
+ def initialize(attachment, *args)
9
+ @path = ''
10
+ @inline = args.detect { |opt| opt[:inline] }
11
+
12
+ if @inline
13
+ @filename = attachment.cid
14
+ else
15
+ @filename = attachment.filename
16
+ end
17
+
18
+ @original_filename = @filename
19
+
20
+ if args.detect { |opt| opt[:filename] }
21
+ @filename = opt[:filename]
22
+ end
23
+
24
+ @overwritten_filename = @filename
25
+
26
+ @content_type = attachment.content_type.split(';')[0]
27
+
28
+ super attachment.body.decoded
29
+ end
30
+
31
+ def inline?
32
+ @inline
33
+ end
34
+
35
+ def is_original_filename
36
+ @original_filename == @overwritten_filename
37
+ end
38
+
39
+ def source_filename
40
+ @filename
41
+ end
42
+
43
+ def attach_to_message!(mb)
44
+ if mb.nil?
45
+ nil
46
+ end
47
+
48
+ if inline?
49
+ mb.add_inline_image self, @filename
50
+ else
51
+ mb.add_attachment self, @filename
52
+ end
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,27 @@
1
+ module Railgun
2
+
3
+ class Error < StandardError
4
+
5
+ attr_reader :object
6
+
7
+ def initialize(message = nil, object = nil)
8
+ super(message)
9
+
10
+ @object = object
11
+ end
12
+ end
13
+
14
+ class ConfigurationError < Error
15
+ end
16
+
17
+ class InternalError < Error
18
+
19
+ attr_reader :source_exception
20
+
21
+ def initialize(source_exc, message = nil, object = nil)
22
+ super(message, object)
23
+
24
+ @source_exception = source_exc
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,161 @@
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, :settings
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
+ # To avoid exception in mail gem v2.6
30
+ @settings = { return_response: true }
31
+
32
+ if (@config[:fake_message_send] || false)
33
+ Rails.logger.info "NOTE: fake message sending has been enabled for mailgun-ruby!"
34
+ @mg_client.enable_test_mode!
35
+ end
36
+ end
37
+
38
+ def deliver!(mail)
39
+ mg_message = Railgun.transform_for_mailgun(mail)
40
+ response = @mg_client.send_message(@domain, mg_message)
41
+
42
+ if response.code == 200 then
43
+ mg_id = response.to_h['id']
44
+ mail.message_id = mg_id
45
+ end
46
+ response
47
+ end
48
+
49
+ def mailgun_client
50
+ @mg_obj
51
+ end
52
+
53
+ end
54
+
55
+ module_function
56
+
57
+ # Performs a series of transformations on the `mailgun*` attributes.
58
+ # After prefixing them with the proper option type, they are added to
59
+ # the message hash where they will then be sent to the API as JSON.
60
+ #
61
+ # @param [Mail::Message] mail message to transform
62
+ #
63
+ # @return [Hash] transformed message hash
64
+ def transform_for_mailgun(mail)
65
+ message = build_message_object(mail)
66
+
67
+ # v:* attributes (variables)
68
+ mail.mailgun_variables.try(:each) do |k, v|
69
+ message["v:#{k}"] = v
70
+ end
71
+
72
+ # o:* attributes (options)
73
+ mail.mailgun_options.try(:each) do |k, v|
74
+ message["o:#{k}"] = v
75
+ end
76
+
77
+ # h:* attributes (headers)
78
+ mail.mailgun_headers.try(:each) do |k, v|
79
+ message["h:#{k}"] = v
80
+ end
81
+
82
+ # recipient variables
83
+ message['recipient-variables'] = mail.mailgun_recipient_variables.to_json if mail.mailgun_recipient_variables
84
+
85
+ # reject blank values
86
+ message.delete_if do |k, v|
87
+ v.nil? or (v.respond_to?(:empty) and v.empty?)
88
+ end
89
+
90
+ return message
91
+ end
92
+
93
+ # Acts on a Rails/ActionMailer message object and uses Mailgun::MessageBuilder
94
+ # to construct a new message.
95
+ #
96
+ # @param [Mail::Message] mail message to transform
97
+ #
98
+ # @returns [Hash] Message hash from Mailgun::MessageBuilder
99
+ def build_message_object(mail)
100
+ mb = Mailgun::MessageBuilder.new
101
+
102
+ mb.from mail[:from]
103
+ mb.reply_to(mail[:reply_to].to_s) if mail[:reply_to].present?
104
+ mb.subject mail.subject
105
+ mb.body_html extract_body_html(mail)
106
+ mb.body_text extract_body_text(mail)
107
+
108
+ [:to, :cc, :bcc].each do |rcpt_type|
109
+ addrs = mail[rcpt_type] || nil
110
+ case addrs
111
+ when String
112
+ # Likely a single recipient
113
+ mb.add_recipient rcpt_type.to_s, addrs
114
+ when Array
115
+ addrs.each do |addr|
116
+ mb.add_recipient rcpt_type.to_s, addr
117
+ end
118
+ when Mail::Field
119
+ mb.add_recipient rcpt_type.to_s, addrs.to_s
120
+ end
121
+ end
122
+
123
+ return mb.message if mail.attachments.empty?
124
+
125
+ mail.attachments.each do |attach|
126
+ attach = Attachment.new(attach, encoding: 'ascii-8bit', inline: attach.inline?)
127
+ attach.attach_to_message! mb
128
+ end
129
+
130
+ return mb.message
131
+ end
132
+
133
+ # Returns the decoded HTML body from the Mail::Message object if available,
134
+ # otherwise nil.
135
+ #
136
+ # @param [Mail::Message] mail message to transform
137
+ #
138
+ # @return [String]
139
+ def extract_body_html(mail)
140
+ begin
141
+ (mail.html_part || mail).body.decoded || nil
142
+ rescue
143
+ nil
144
+ end
145
+ end
146
+
147
+ # Returns the decoded text body from the Mail::Message object if it is available,
148
+ # otherwise nil.
149
+ #
150
+ # @param [Mail::Message] mail message to transform
151
+ #
152
+ # @return [String]
153
+ def extract_body_text(mail)
154
+ begin
155
+ (mail.text_part || mail).body.decoded || nil
156
+ rescue
157
+ nil
158
+ end
159
+ end
160
+
161
+ 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
@@ -0,0 +1,37 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'mailgun/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+
8
+ spec.name = 'wj-mailgun-ruby'
9
+ spec.version = Mailgun::VERSION
10
+ spec.homepage = 'http://www.mailgun.com'
11
+ spec.platform = Gem::Platform::RUBY
12
+ spec.license = 'Apache-2.0'
13
+
14
+ spec.summary = "Mailgun's Official Ruby SDK"
15
+ spec.description = "Mailgun's Official Ruby SDK for interacting with the Mailgun API."
16
+
17
+ spec.authors = ['Mailgun', 'Travis Swientek',"greatmanta111"]
18
+ spec.email = 'support@mailgunhq.com'
19
+
20
+ spec.files = `git ls-files -z`.split("\x0")
21
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
22
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
23
+ spec.require_paths = ["lib"]
24
+
25
+ spec.required_ruby_version = '>= 2.0.0'
26
+
27
+ spec.add_development_dependency 'bundler', '~> 1.5'
28
+ spec.add_development_dependency 'rspec', '~> 3.0'
29
+ spec.add_development_dependency 'rake', '~> 10.0'
30
+ spec.add_development_dependency 'webmock', '~> 1.22'
31
+ spec.add_development_dependency 'pry', '~> 0.9'
32
+ spec.add_development_dependency 'vcr', '~> 3.0'
33
+ spec.add_development_dependency 'simplecov', '~> 0.11'
34
+
35
+ spec.add_dependency 'rest-client', '~> 2.0'
36
+
37
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+ require 'mailgun'
3
+
4
+ vcr_opts = { :cassette_name => "bounces" }
5
+
6
+ describe 'For the Bounces endpoint', order: :defined, vcr: vcr_opts do
7
+ before(:all) do
8
+ @mg_obj = Mailgun::Client.new(APIKEY, APIHOST, APIVERSION, SSL)
9
+ @domain = TESTDOMAIN
10
+ @email = "integration-test-email@#{TESTDOMAIN}"
11
+ end
12
+
13
+ it 'creates a bounce' do
14
+ @result = @mg_obj.post("#{@domain}/bounces",
15
+ {:address => @email,
16
+ :code => 550,
17
+ :error => "Integration Test"})
18
+
19
+ @result.to_h!
20
+ expect(@result.body["message"]).to eq("Address has been added to the bounces table")
21
+ expect(@result.body["address"]).to eq(@email)
22
+ end
23
+
24
+ it 'get a bounce.' do
25
+ result = @mg_obj.get("#{@domain}/bounces/#{@email}")
26
+
27
+ result.to_h!
28
+ expect(result.body["code"]).to eq("550")
29
+ expect(result.body["address"]).to eq(@email)
30
+ expect(result.body["error"]).to eq("Integration Test")
31
+ end
32
+
33
+ it 'gets a list of bounces.' do
34
+ result = @mg_obj.get("#{@domain}/bounces")
35
+
36
+ result.to_h!
37
+ expect(result.body["items"].length).to be > 0
38
+ end
39
+
40
+ it 'deletes a bounce' do
41
+ @mg_obj.delete("#{@domain}/bounces/#{@email}")
42
+ end
43
+
44
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+ require 'mailgun'
3
+
4
+ vcr_opts = { :cassette_name => "campaigns" }
5
+
6
+ describe 'For the campaigns endpoint', vcr: vcr_opts do
7
+ before(:all) do
8
+ skip 'pending removal'
9
+ @mg_obj = Mailgun::Client.new(APIKEY, APIHOST, APIVERSION, SSL)
10
+ @domain = TESTDOMAIN
11
+ @campaign_id = "integration_test_campaign"
12
+ end
13
+
14
+ it 'creates a campaign' do
15
+ result = @mg_obj.post("#{@domain}/campaigns", {:name => 'My Campaign',
16
+ :id => @campaign_id})
17
+
18
+ result.to_h!
19
+ expect(result.body["message"]).to eq("Campaign created")
20
+ expect(result.body["campaign"]["id"]).to eq(@campaign_id)
21
+ expect(result.body["campaign"]["name"]).to eq('My Campaign')
22
+ end
23
+
24
+ it 'get a campaign.' do
25
+ result = @mg_obj.get("#{@domain}/campaigns/#{@campaign_id}")
26
+
27
+ result.to_h!
28
+ expect(result.body["id"]).to eq(@campaign_id)
29
+ expect(result.body["name"]).to eq('My Campaign')
30
+ end
31
+
32
+ it 'gets a list of all campaigns.' do
33
+ result = @mg_obj.get("#{@domain}/campaigns", {:limit => 50})
34
+
35
+ result.to_h!
36
+ expect(result.body["total_count"]).to be > 0
37
+ end
38
+
39
+ it 'update a campaign.' do
40
+ result = @mg_obj.put("#{@domain}/campaigns/#{@campaign_id}", {:name => 'My Updated Campaign',
41
+ :id => @campaign_id})
42
+
43
+ result.to_h!
44
+ expect(result.body["message"]).to eq("Campaign updated")
45
+ expect(result.body["campaign"]["id"]).to eq(@campaign_id)
46
+ expect(result.body["campaign"]["name"]).to eq('My Updated Campaign')
47
+ end
48
+
49
+ it 'get campaign events.' do
50
+ expect{@mg_obj.get("#{@domain}/campaigns/#{@campaign_id}/events", {:groupby => "clicked"})}.not_to raise_error
51
+ end
52
+
53
+ it 'get campaign stats.' do
54
+ expect{@mg_obj.get("#{@domain}/campaigns/#{@campaign_id}/stats", {:groupby => "domain"})}.not_to raise_error
55
+ end
56
+
57
+ it 'removes a campaign' do
58
+ @mg_obj.delete("#{@domain}/campaigns/#{@campaign_id}")
59
+ end
60
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+ require 'mailgun'
3
+
4
+ vcr_opts = { :cassette_name => "complaints" }
5
+
6
+ describe 'For the Complaints endpoint', order: :defined, vcr: vcr_opts do
7
+ before(:all) do
8
+ @mg_obj = Mailgun::Client.new(APIKEY, APIHOST, APIVERSION, SSL)
9
+ @domain = TESTDOMAIN
10
+ @email = "integration-test-email@#{TESTDOMAIN}"
11
+ end
12
+
13
+ it 'creates a complaint' do
14
+ @result = @mg_obj.post("#{@domain}/complaints", {:address => @email})
15
+
16
+ @result.to_h!
17
+ expect(@result.body["message"]).to eq("Address has been added to the complaints table")
18
+ expect(@result.body["address"]).to eq(@email)
19
+ end
20
+
21
+ it 'get a complaint.' do
22
+ result = @mg_obj.get("#{@domain}/complaints/#{@email}")
23
+
24
+ result.to_h!
25
+ expect(result.body["address"]).to eq(@email)
26
+ end
27
+
28
+ it 'gets a list of complaints.' do
29
+ result = @mg_obj.get("#{@domain}/complaints")
30
+
31
+ result.to_h!
32
+ expect(result.body["items"].length).to be > 0
33
+ end
34
+
35
+ it 'removes a complaint' do
36
+ @mg_obj.delete("#{@domain}/complaints/#{@email}")
37
+ end
38
+ end