mailgun-ruby 1.1.0 → 1.2.5

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.
Files changed (57) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.ruby-env.yml.example +1 -1
  4. data/.travis.yml +23 -7
  5. data/Gemfile +2 -0
  6. data/README.md +79 -18
  7. data/{Domains.md → docs/Domains.md} +18 -0
  8. data/{Events.md → docs/Events.md} +0 -0
  9. data/{MessageBuilder.md → docs/MessageBuilder.md} +32 -13
  10. data/{Messages.md → docs/Messages.md} +3 -3
  11. data/{OptInHandler.md → docs/OptInHandler.md} +0 -0
  12. data/{Snippets.md → docs/Snippets.md} +21 -2
  13. data/docs/Suppressions.md +82 -0
  14. data/{Webhooks.md → docs/Webhooks.md} +1 -1
  15. data/docs/railgun/Overview.md +11 -0
  16. data/docs/railgun/Parameters.md +83 -0
  17. data/lib/mailgun/address.rb +48 -0
  18. data/lib/mailgun/client.rb +85 -8
  19. data/lib/mailgun/events/events.rb +40 -12
  20. data/lib/mailgun/exceptions/exceptions.rb +21 -7
  21. data/lib/mailgun/lists/opt_in_handler.rb +0 -1
  22. data/lib/mailgun/messages/batch_message.rb +3 -2
  23. data/lib/mailgun/messages/message_builder.rb +139 -30
  24. data/lib/mailgun/response.rb +7 -0
  25. data/lib/mailgun/suppressions.rb +273 -0
  26. data/lib/mailgun/version.rb +1 -1
  27. data/lib/mailgun/webhooks/webhooks.rb +1 -1
  28. data/lib/mailgun-ruby.rb +2 -0
  29. data/lib/mailgun.rb +1 -0
  30. data/lib/railgun/attachment.rb +56 -0
  31. data/lib/railgun/errors.rb +27 -0
  32. data/lib/railgun/mailer.rb +253 -0
  33. data/lib/railgun/message.rb +18 -0
  34. data/lib/railgun/railtie.rb +10 -0
  35. data/lib/railgun.rb +8 -0
  36. data/mailgun.gemspec +12 -13
  37. data/spec/integration/email_validation_spec.rb +57 -15
  38. data/spec/integration/events_spec.rb +9 -1
  39. data/spec/integration/mailer_spec.rb +67 -0
  40. data/spec/integration/mailgun_spec.rb +51 -1
  41. data/spec/integration/suppressions_spec.rb +142 -0
  42. data/spec/spec_helper.rb +3 -1
  43. data/spec/unit/connection/test_client.rb +16 -0
  44. data/spec/unit/events/events_spec.rb +36 -2
  45. data/spec/unit/mailgun_spec.rb +32 -10
  46. data/spec/unit/messages/batch_message_spec.rb +56 -40
  47. data/spec/unit/messages/message_builder_spec.rb +267 -81
  48. data/spec/unit/messages/sample_data/unknown.type +0 -0
  49. data/spec/unit/railgun/content_type_spec.rb +71 -0
  50. data/spec/unit/railgun/mailer_spec.rb +388 -0
  51. data/vcr_cassettes/email_validation.yml +136 -25
  52. data/vcr_cassettes/events.yml +48 -1
  53. data/vcr_cassettes/exceptions.yml +45 -0
  54. data/vcr_cassettes/mailer_invalid_domain.yml +109 -0
  55. data/vcr_cassettes/message_deliver.yml +149 -0
  56. data/vcr_cassettes/suppressions.yml +727 -0
  57. metadata +65 -40
@@ -1,3 +1,5 @@
1
+ require 'ostruct'
2
+
1
3
  module Mailgun
2
4
  # A Mailgun::Response object is instantiated for each response generated
3
5
  # by the Client request. The Response object supports deserialization of
@@ -10,6 +12,11 @@ module Mailgun
10
12
  # slightly different
11
13
  attr_accessor :body, :code
12
14
 
15
+ def self.from_hash(h)
16
+ # Create a "fake" response object with the data passed from h
17
+ self.new OpenStruct.new(h)
18
+ end
19
+
13
20
  def initialize(response)
14
21
  @body = response.body
15
22
  @code = response.code
@@ -0,0 +1,273 @@
1
+ require 'uri'
2
+
3
+ require 'mailgun/exceptions/exceptions'
4
+
5
+ module Mailgun
6
+
7
+ # The Mailgun::Suppressions object makes it easy to manage "suppressions"
8
+ # attached to an account. "Suppressions" means bounces, unsubscribes, and complaints.
9
+ class Suppressions
10
+
11
+ # @param [Mailgun::Client] client API client to use for requests
12
+ # @param [String] domain Domain name to use for the suppression endpoints.
13
+ def initialize(client, domain)
14
+ @client = client
15
+ @domain = domain
16
+
17
+ @paging_next = nil
18
+ @paging_prev = nil
19
+ end
20
+
21
+ ####
22
+ # Paging operations
23
+ ####
24
+
25
+ def next
26
+ response = get_from_paging @paging_next[:path], @paging_next[:params]
27
+ extract_paging response
28
+ response
29
+ end
30
+
31
+ def prev
32
+ response = get_from_paging @paging_prev[:path], @paging_prev[:params]
33
+ extract_paging response
34
+ response
35
+ end
36
+
37
+ ####
38
+ # Bounces Endpoint (/v3/:domain/bounces)
39
+ ####
40
+
41
+ def list_bounces(params = {})
42
+ response = @client.get("#{@domain}/bounces", params)
43
+ extract_paging response
44
+ response
45
+ end
46
+
47
+ def get_bounce(address)
48
+ @client.get("#{@domain}/bounces/#{address}", nil)
49
+ end
50
+
51
+ def create_bounce(params = {})
52
+ @client.post("#{@domain/bounces}", params)
53
+ end
54
+
55
+ # Creates multiple bounces on the Mailgun API.
56
+ # If a bounce does not have a valid structure, it will be added to a list of unsendable bounces.
57
+ # The list of unsendable bounces will be returned at the end of this operation.
58
+ #
59
+ # If more than 999 bounce entries are provided, the list will be split and recursive calls will be made.
60
+ #
61
+ # @param [Array] data Array of bounce hashes
62
+ # @return [Response] Mailgun API response
63
+ # @return [Array] Return values from recursive call for list split.
64
+ def create_bounces(data)
65
+ # `data` should be a list of hashes, with each hash containing *at least* an `address` key.
66
+ split_return = []
67
+ if data.length >= 1000 then
68
+ resp, resp_l = create_bounces data[999..-1]
69
+ split_return.push(resp)
70
+ split_return.concat(resp_l)
71
+ data = data[0..998]
72
+ elsif data.length == 0 then
73
+ return nil, []
74
+ end
75
+
76
+ valid = []
77
+ # Validate the bounces given
78
+ # NOTE: `data` could potentially be very large (1000 elements) so it is
79
+ # more efficient to pop from data and push into a different array as
80
+ # opposed to possibly copying the entire array to another array.
81
+ while not data.empty? do
82
+ bounce = data.pop
83
+ # Bounces MUST contain a `address` key.
84
+ if not bounce.include? :address then
85
+ raise Mailgun::ParameterError.new "Bounce MUST include a :address key: #{bounce}"
86
+ end
87
+
88
+ bounce.each do |k, v|
89
+ # Hash values MUST be strings.
90
+ if not v.is_a? String then
91
+ bounce[k] = v.to_s
92
+ end
93
+ end
94
+
95
+ valid.push bounce
96
+ end
97
+
98
+ response = @client.post("#{@domain}/bounces", valid.to_json, { "Content-Type" => "application/json" })
99
+ return response, split_return
100
+ end
101
+
102
+ def delete_bounce(address)
103
+ @client.delete("#{@domain}/bounces/#{address}")
104
+ end
105
+
106
+ def delete_all_bounces
107
+ @client.delete("#{@domain}/bounces")
108
+ end
109
+
110
+ ####
111
+ # Unsubscribes Endpoint (/v3/:domain/unsubscribes)
112
+ ####
113
+
114
+ def list_unsubscribes(params = {})
115
+ response = @client.get("#{@domain}/unsubscribes", params)
116
+ extract_paging response
117
+ response
118
+ end
119
+
120
+ def get_unsubscribe(address)
121
+ @client.get("#{@domain}/unsubscribes/#{address}")
122
+ end
123
+
124
+ def create_unsubscribe(params = {})
125
+ @client.post("#{@domain}/unsubscribes", params)
126
+ end
127
+
128
+ # Creates multiple unsubscribes on the Mailgun API.
129
+ # If an unsubscribe does not have a valid structure, it will be added to a list of unsendable unsubscribes.
130
+ # The list of unsendable unsubscribes will be returned at the end of this operation.
131
+ #
132
+ # If more than 999 unsubscribe entries are provided, the list will be split and recursive calls will be made.
133
+ #
134
+ # @param [Array] data Array of unsubscribe hashes
135
+ # @return [Response] Mailgun API response
136
+ # @return [Array] Return values from recursive call for list split.
137
+ def create_unsubscribes(data)
138
+ # `data` should be a list of hashes, with each hash containing *at least* an `address` key.
139
+ split_return = []
140
+ if data.length >= 1000 then
141
+ resp, resp_l = create_unsubscribes data[999..-1]
142
+ split_return.push(resp)
143
+ split_return.concat(resp_l)
144
+ data = data[0..998]
145
+ elsif data.length == 0 then
146
+ return nil, []
147
+ end
148
+
149
+ valid = []
150
+ # Validate the unsubscribes given
151
+ while not data.empty? do
152
+ unsubscribe = data.pop
153
+ # unsubscribes MUST contain a `address` key.
154
+ if not unsubscribe.include? :address then
155
+ raise Mailgun::ParameterError.new "Unsubscribe MUST include a :address key: #{unsubscribe}"
156
+ end
157
+
158
+ unsubscribe.each do |k, v|
159
+ # Hash values MUST be strings.
160
+ # However, unsubscribes contain an array of tags
161
+ if v.is_a? Array
162
+ unsubscribe[k] = v.map(&:to_s)
163
+ elsif !v.is_a? String
164
+ unsubscribe[k] = v.to_s
165
+ end
166
+ end
167
+
168
+ valid.push unsubscribe
169
+ end
170
+
171
+ response = @client.post("#{@domain}/unsubscribes", valid.to_json, { "Content-Type" => "application/json" })
172
+ return response, split_return
173
+ end
174
+
175
+ def delete_unsubscribe(address, params = {})
176
+ @client.delete("#{@domain}/unsubscribes/#{address}")
177
+ end
178
+
179
+ ####
180
+ # Complaints Endpoint (/v3/:domain/complaints)
181
+ ####
182
+
183
+ def list_complaints(params = {})
184
+ response = @client.get("#{@domain}/complaints", params)
185
+ extract_paging response
186
+ response
187
+ end
188
+
189
+ def get_complaint(address)
190
+ @client.get("#{@domain}/complaints/#{address}", nil)
191
+ end
192
+
193
+ def create_complaint(params = {})
194
+ @client.post("#{@domain}/complaints", params)
195
+ end
196
+
197
+ # Creates multiple complaints on the Mailgun API.
198
+ # If a complaint does not have a valid structure, it will be added to a list of unsendable complaints.
199
+ # The list of unsendable complaints will be returned at the end of this operation.
200
+ #
201
+ # If more than 999 complaint entries are provided, the list will be split and recursive calls will be made.
202
+ #
203
+ # @param [Array] data Array of complaint hashes
204
+ # @return [Response] Mailgun API response
205
+ # @return [Array] Return values from recursive call for list split.
206
+ def create_complaints(data)
207
+ # `data` should be a list of hashes, with each hash containing *at least* an `address` key.
208
+ split_return = []
209
+ if data.length >= 1000 then
210
+ resp, resp_l = create_complaints data[999..-1]
211
+ split_return.push(resp)
212
+ split_return.concat(resp_l)
213
+ data = data[0..998]
214
+ elsif data.length == 0 then
215
+ return nil, []
216
+ end
217
+
218
+ valid = []
219
+ # Validate the complaints given
220
+ while not data.empty? do
221
+ complaint = data.pop
222
+ # complaints MUST contain a `address` key.
223
+ if not complaint.include? :address then
224
+ raise Mailgun::ParameterError.new "Complaint MUST include a :address key: #{complaint}"
225
+ end
226
+
227
+ complaint.each do |k, v|
228
+ # Hash values MUST be strings.
229
+ if not v.is_a? String then
230
+ complaint[k] = v.to_s
231
+ end
232
+ end
233
+
234
+ valid.push complaint
235
+ end
236
+
237
+ response = @client.post("#{@domain}/complaints", valid.to_json, { "Content-Type" => "application/json" })
238
+ return response, split_return
239
+ end
240
+
241
+ def delete_complaint(address)
242
+ @client.delete("#{@domain}/complaints/#{address}")
243
+ end
244
+
245
+ private
246
+
247
+ def get_from_paging(uri, params = {})
248
+ @client.get(uri, params)
249
+ end
250
+
251
+ def extract_paging(response)
252
+ rhash = response.to_h
253
+ return nil unless rhash.include? "paging"
254
+
255
+ page_info = rhash["paging"]
256
+
257
+ # Build the `next` endpoint
258
+ page_next = URI.parse(page_info["next"])
259
+ @paging_next = {
260
+ :path => page_next.path[/\/v[\d](.+)/, 1],
261
+ :params => Hash[URI.decode_www_form page_next.query],
262
+ }
263
+
264
+ # Build the `prev` endpoint
265
+ page_prev = URI.parse(page_info["previous"])
266
+ @paging_prev = {
267
+ :path => page_prev.path[/\/v[\d](.+)/, 1],
268
+ :params => Hash[URI.decode_www_form page_prev.query],
269
+ }
270
+ end
271
+
272
+ end
273
+ end
@@ -1,4 +1,4 @@
1
1
  # It's the version. Yeay!
2
2
  module Mailgun
3
- VERSION = '1.1.0'
3
+ VERSION = '1.2.5'
4
4
  end
@@ -46,7 +46,7 @@ module Mailgun
46
46
  # Returns a Boolean of whether the webhook was created
47
47
  def create(domain, action, url = '')
48
48
  res = @client.post("domains/#{domain}/webhooks", id: action, url: url)
49
- res.to_h['webhook'] == url && res.to_h[message] == 'Webhook has been created'
49
+ res.to_h['webhook']['url'] == url && res.to_h['message'] == 'Webhook has been created'
50
50
  end
51
51
  alias_method :add, :create
52
52
  alias_method :add_webhook, :create
@@ -0,0 +1,2 @@
1
+ require 'mailgun'
2
+ require 'railgun' if defined?(Rails)
data/lib/mailgun.rb CHANGED
@@ -7,6 +7,7 @@ require 'mailgun/version'
7
7
  require 'mailgun/client'
8
8
  require 'mailgun/response'
9
9
  require 'mailgun/chains'
10
+ require 'mailgun/address'
10
11
  require 'mailgun/lists/opt_in_handler'
11
12
  require 'mailgun/messages/batch_message'
12
13
  require 'mailgun/messages/message_builder'
@@ -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,253 @@
1
+ require 'action_mailer'
2
+ require 'json'
3
+ require 'mailgun'
4
+ require 'rails'
5
+ require 'railgun/errors'
6
+
7
+ module Railgun
8
+
9
+ # Railgun::Mailer is an ActionMailer provider for sending mail through
10
+ # Mailgun.
11
+ class Mailer
12
+
13
+ # List of the headers that will be ignored when copying headers from `mail.header_fields`
14
+ IGNORED_HEADERS = %w[ to from subject reply-to mime-version template ]
15
+
16
+ # [Hash] config ->
17
+ # Requires *at least* `api_key` and `domain` keys.
18
+ attr_accessor :config, :domain, :settings
19
+
20
+ # Initialize the Railgun mailer.
21
+ #
22
+ # @param [Hash] config Hash of config values, typically from `app_config.action_mailer.mailgun_config`
23
+ def initialize(config)
24
+ @config = config
25
+
26
+ [:api_key, :domain].each do |k|
27
+ raise Railgun::ConfigurationError.new("Config requires `#{k}` key", @config) unless @config.has_key?(k)
28
+ end
29
+
30
+ @mg_client = Mailgun::Client.new(
31
+ config[:api_key],
32
+ config[:api_host] || 'api.mailgun.net',
33
+ config[:api_version] || 'v3',
34
+ config[:api_ssl].nil? ? true : config[:api_ssl],
35
+ false,
36
+ config[:timeout],
37
+ )
38
+ @domain = @config[:domain]
39
+
40
+ # To avoid exception in mail gem v2.6
41
+ @settings = { return_response: true }
42
+
43
+ if (@config[:fake_message_send] || false)
44
+ Rails.logger.info "NOTE: fake message sending has been enabled for mailgun-ruby!"
45
+ @mg_client.enable_test_mode!
46
+ end
47
+ end
48
+
49
+ def deliver!(mail)
50
+ @mg_domain = set_mg_domain(mail)
51
+ mail[:domain] = nil if mail[:domain].present?
52
+
53
+ mg_message = Railgun.transform_for_mailgun(mail)
54
+ response = @mg_client.send_message(@mg_domain, mg_message)
55
+
56
+ if response.code == 200 then
57
+ mg_id = response.to_h['id']
58
+ mail.message_id = mg_id
59
+ end
60
+ response
61
+ end
62
+
63
+ def mailgun_client
64
+ @mg_client
65
+ end
66
+
67
+ private
68
+
69
+ # Set @mg_domain from mail[:domain] header if present, then remove it to prevent being sent.
70
+ def set_mg_domain(mail)
71
+ return mail[:domain].value if mail[:domain]
72
+ domain
73
+ end
74
+
75
+ end
76
+
77
+ module_function
78
+
79
+ # Performs a series of transformations on the `mailgun*` attributes.
80
+ # After prefixing them with the proper option type, they are added to
81
+ # the message hash where they will then be sent to the API as JSON.
82
+ #
83
+ # It is important to note that headers set in `mailgun_headers` on the message
84
+ # WILL overwrite headers set via `mail.headers()`.
85
+ #
86
+ # @param [Mail::Message] mail message to transform
87
+ #
88
+ # @return [Hash] transformed message hash
89
+ def transform_for_mailgun(mail)
90
+ message = build_message_object(mail)
91
+
92
+ # o:* attributes (options)
93
+ mail.mailgun_options.try(:each) do |k, v|
94
+ message["o:#{k}"] = v.dup
95
+ end
96
+
97
+ # t:* attributes (options)
98
+ mail.mailgun_template_variables.try(:each) do |k, v|
99
+ message["t:#{k}"] = v.dup
100
+ end
101
+
102
+ # support for using ActionMailer's `headers()` inside of the mailer
103
+ # note: this will filter out parameters such as `from`, `to`, and so forth
104
+ # as they are accepted as POST parameters on the message endpoint.
105
+
106
+ msg_headers = Hash.new
107
+
108
+ # h:* attributes (headers)
109
+
110
+ # Let's set all of these headers on the [Mail::Message] so that
111
+ # the are created inside of a [Mail::Header] instance and processed there.
112
+ mail.headers(mail.mailgun_headers || {})
113
+ mail.header_fields.each do |field|
114
+ header = field.name.downcase
115
+ if msg_headers.include? header
116
+ msg_headers[header] = [msg_headers[header], field.value].flatten
117
+ else
118
+ msg_headers[header] = field.value
119
+ end
120
+ end
121
+
122
+ msg_headers.each do |k, v|
123
+ if Railgun::Mailer::IGNORED_HEADERS.include? k.downcase
124
+ Rails.logger.debug("[railgun] ignoring header (using envelope instead): #{k}")
125
+ next
126
+ end
127
+
128
+ # Cover cases like `cc`, `bcc` where parameters are valid
129
+ # headers BUT they are submitted as separate POST params
130
+ # and already exist on the message because of the call to
131
+ # `build_message_object`.
132
+ if message.include? k.downcase
133
+ Rails.logger.debug("[railgun] ignoring header (already set): #{k}")
134
+ next
135
+ end
136
+
137
+ message["h:#{k}"] = v
138
+ end
139
+
140
+ # recipient variables
141
+ message['recipient-variables'] = mail.mailgun_recipient_variables.to_json if mail.mailgun_recipient_variables
142
+
143
+ # reject blank values
144
+ message.delete_if do |k, v|
145
+ return true if v.nil?
146
+
147
+ # if it's an array remove empty elements
148
+ v.delete_if { |i| i.respond_to?(:empty?) && i.empty? } if v.is_a?(Array)
149
+
150
+ v.respond_to?(:empty?) && v.empty?
151
+ end
152
+
153
+ return message
154
+ end
155
+
156
+ # Acts on a Rails/ActionMailer message object and uses Mailgun::MessageBuilder
157
+ # to construct a new message.
158
+ #
159
+ # @param [Mail::Message] mail message to transform
160
+ #
161
+ # @returns [Hash] Message hash from Mailgun::MessageBuilder
162
+ def build_message_object(mail)
163
+ mb = Mailgun::MessageBuilder.new
164
+
165
+ mb.from mail[:from]
166
+ mb.reply_to(mail[:reply_to].to_s) if mail[:reply_to].present?
167
+ mb.template(mail[:template].to_s) if mail[:template].present?
168
+ mb.subject mail.subject
169
+ mb.body_html extract_body_html(mail)
170
+ mb.body_text extract_body_text(mail)
171
+
172
+ [:to, :cc, :bcc].each do |rcpt_type|
173
+ addrs = mail[rcpt_type] || nil
174
+ case addrs
175
+ when String
176
+ # Likely a single recipient
177
+ mb.add_recipient rcpt_type.to_s, addrs
178
+ when Array
179
+ addrs.each do |addr|
180
+ mb.add_recipient rcpt_type.to_s, addr
181
+ end
182
+ when Mail::Field
183
+ mb.add_recipient rcpt_type.to_s, addrs.to_s
184
+ end
185
+ end
186
+
187
+ # v:* attributes (variables)
188
+ mail.mailgun_variables.try(:each) do |name, value|
189
+ mb.variable(name, value)
190
+ end
191
+
192
+ return mb.message if mail.attachments.empty?
193
+
194
+ mail.attachments.each do |attach|
195
+ attach = Attachment.new(attach, encoding: 'ascii-8bit', inline: attach.inline?)
196
+ attach.attach_to_message! mb
197
+ end
198
+
199
+ return mb.message
200
+ end
201
+
202
+ # Returns the decoded HTML body from the Mail::Message object if available,
203
+ # otherwise nil.
204
+ #
205
+ # @param [Mail::Message] mail message to transform
206
+ #
207
+ # @return [String]
208
+ def extract_body_html(mail)
209
+ begin
210
+ retrieve_html_part(mail).body.decoded || nil
211
+ rescue
212
+ nil
213
+ end
214
+ end
215
+
216
+ # Returns the decoded text body from the Mail::Message object if it is available,
217
+ # otherwise nil.
218
+ #
219
+ # @param [Mail::Message] mail message to transform
220
+ #
221
+ # @return [String]
222
+ def extract_body_text(mail)
223
+ begin
224
+ retrieve_text_part(mail).body.decoded || nil
225
+ rescue
226
+ nil
227
+ end
228
+ end
229
+
230
+ # Returns the mail object from the Mail::Message object if text part exists,
231
+ # (decomposing multipart into individual format if necessary)
232
+ # otherwise nil.
233
+ #
234
+ # @param [Mail::Message] mail message to transform
235
+ #
236
+ # @return [Mail::Message] mail message with its content-type = text/plain
237
+ def retrieve_text_part(mail)
238
+ return mail.text_part if mail.multipart?
239
+ (mail.mime_type =~ /^text\/plain$/i) && mail
240
+ end
241
+
242
+ # Returns the mail object from the Mail::Message object if html part exists,
243
+ # (decomposing multipart into individual format if necessary)
244
+ # otherwise nil.
245
+ #
246
+ # @param [Mail::Message] mail message to transform
247
+ #
248
+ # @return [Mail::Message] mail message with its content-type = text/html
249
+ def retrieve_html_part(mail)
250
+ return mail.html_part if mail.multipart?
251
+ (mail.mime_type =~ /^text\/html$/i) && mail
252
+ end
253
+ end
@@ -0,0 +1,18 @@
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
+ :mailgun_template_variables
16
+
17
+ end
18
+ end
@@ -0,0 +1,10 @@
1
+ require 'railgun/mailer'
2
+
3
+ module Railgun
4
+ class Railtie < ::Rails::Railtie
5
+ ActiveSupport.on_load(:action_mailer) do
6
+ add_delivery_method :mailgun, Railgun::Mailer
7
+ ActiveSupport.run_load_hooks(:mailgun_mailer, Railgun::Mailer)
8
+ end
9
+ end
10
+ 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