mailgun-ruby 1.1.2 → 1.1.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7191c12a6dc2a08445f7cb623f10608e9208c9d2
4
- data.tar.gz: 5033b5cd5eb92f6ce5c2d678222f8c7d9d118c78
3
+ metadata.gz: a0e2728bd22128bd8d8209b0b5b1f6c941501f5a
4
+ data.tar.gz: b8f2c9a4cccdccd328142fae44a14be8154cb9d6
5
5
  SHA512:
6
- metadata.gz: 38dd19ded277a5566d738c88368b811607762ee258eaca48f7f1afe069b6fd84672bef1e6fb9637b028c06762c840dbd84b7a722ec2e6b7bd51828f99eedd406
7
- data.tar.gz: a87aab066601ca346f897d551ff37165b862654d378c156df84b9443714c0795b9bb7299e16e0bce5666511d28900b0d0d7677ffe7ff826b2681d1d9c96ea7cc
6
+ metadata.gz: 779520a0342a79b09dbd433f516d969234636b7585d14ab3f7f827a08071611b55b2fc1b81c7aa6215a7ac6fb08535f95a792109a309557e5e612c6be5d02dc1
7
+ data.tar.gz: 0ad45cd374509486f177649b7d6dd3f3e3df7b14b3c40643657699670f6acb6e0b710200bc1df19a1bc7710795fe654dd6efcd1590e05650de00460898aca0e0
data/README.md CHANGED
@@ -27,6 +27,8 @@ Usage
27
27
  Here's how to send a message using the library:
28
28
 
29
29
  ```ruby
30
+ require 'mailgun'
31
+
30
32
  # First, instantiate the Mailgun Client with your API key
31
33
  mg_client = Mailgun::Client.new 'your-api-key'
32
34
 
@@ -139,8 +141,8 @@ For usage examples on each API endpoint, head over to our official documentation
139
141
  pages. Or the [Snippets](Snippets.md) file.
140
142
 
141
143
  This SDK includes the following components:
142
- - [Message Builder](Messages.md)
143
- - [Batch Message](Messages.md)
144
+ - [Message Builder](MessageBuilder.md)
145
+ - [Batch Message](MessageBuilder.md)
144
146
  - [Opt-In Handler](OptInHandler.md)
145
147
  - [Domains](Domains.md)
146
148
  - [Webhooks](Webhooks.md)
data/Snippets.md CHANGED
@@ -41,8 +41,7 @@ mg_client.send_message "sending_domain.com", data
41
41
 
42
42
  ```ruby
43
43
  # Don't include a file, pull the file to a string
44
- mime_string = '
45
- From: Bob Sample <example@example.com>
44
+ mime_string = 'From: Bob Sample <example@example.com>
46
45
  MIME-Version: 1.0
47
46
  Content-Type: multipart/mixed;
48
47
  boundary="--boundary-goes-here--"
@@ -82,9 +82,10 @@ module Mailgun
82
82
  # with. Be sure to include your domain, where necessary.
83
83
  # @param [Hash] data This should be a standard Hash
84
84
  # containing required parameters for the requested resource.
85
+ # @param [Hash] headers Additional headers to pass to the resource.
85
86
  # @return [Mailgun::Response] A Mailgun::Response object.
86
- def post(resource_path, data)
87
- response = @http_client[resource_path].post(data)
87
+ def post(resource_path, data, headers = {})
88
+ response = @http_client[resource_path].post(data, headers)
88
89
  Response.new(response)
89
90
  rescue => err
90
91
  raise communication_error err
@@ -94,8 +95,9 @@ module Mailgun
94
95
  #
95
96
  # @param [String] resource_path This is the API resource you wish to interact
96
97
  # with. Be sure to include your domain, where necessary.
97
- # @param [Hash] query_string This should be a standard Hash
98
+ # @param [Hash] params This should be a standard Hash
98
99
  # containing required parameters for the requested resource.
100
+ # @param [String] accept Acceptable Content-Type of the response body.
99
101
  # @return [Mailgun::Response] A Mailgun::Response object.
100
102
  def get(resource_path, params = nil, accept = '*/*')
101
103
  if params
@@ -23,11 +23,18 @@ module Mailgun
23
23
 
24
24
  # Adds a specific type of recipient to the message object.
25
25
  #
26
+ # WARNING: Setting 'h:reply-to' with add_recipient() is deprecated! Use 'reply_to' instead.
27
+ #
26
28
  # @param [String] recipient_type The type of recipient. "to", "cc", "bcc" or "h:reply-to".
27
29
  # @param [String] address The email address of the recipient to add to the message object.
28
30
  # @param [Hash] variables A hash of the variables associated with the recipient. We recommend "first" and "last" at a minimum!
29
31
  # @return [void]
30
32
  def add_recipient(recipient_type, address, variables = nil)
33
+ if recipient_type == "h:reply-to"
34
+ warn 'DEPRECATION: "add_recipient("h:reply-to", ...)" is deprecated. Please use "reply_to" instead.'
35
+ return reply_to(address, variables)
36
+ end
37
+
31
38
  if (@counters[:recipients][recipient_type] || 0) >= Mailgun::Chains::MAX_RECIPIENTS
32
39
  fail Mailgun::ParameterError, 'Too many recipients added to message.', address
33
40
  end
@@ -53,6 +60,19 @@ module Mailgun
53
60
  from(address, variables)
54
61
  end
55
62
 
63
+ # Set the message's Reply-To address.
64
+ #
65
+ # Rationale: According to RFC, only one Reply-To address is allowed, so it
66
+ # is *okay* to bypass the simple_setter and set reply-to directly.
67
+ #
68
+ # @param [String] address The email address to provide as Reply-To.
69
+ # @param [Hash] variables A hash of variables associated with the recipient.
70
+ # @return [void]
71
+ def reply_to(address, variables = nil)
72
+ compiled_address = parse_address(address, variables)
73
+ @message["h:reply-to"] = compiled_address
74
+ end
75
+
56
76
  # Set a subject for the message object
57
77
  #
58
78
  # @param [String] subject The subject for the email.
@@ -0,0 +1,267 @@
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] Array of invalid bounce hashes.
64
+ # @return [Array] Return values from recursive call for list split.
65
+ def create_bounces(data)
66
+ # `data` should be a list of hashes, with each hash containing *at least* an `address` key.
67
+ split_return = []
68
+ if data.length >= 1000 then
69
+ split_return = create_bounces data[999..-1]
70
+ data = data[0..998]
71
+ elsif data.length == 0 then
72
+ []
73
+ end
74
+
75
+ valid = []
76
+ # Validate the bounces given
77
+ # NOTE: `data` could potentially be very large (1000 elements) so it is
78
+ # more efficient to pop from data and push into a different array as
79
+ # opposed to possibly copying the entire array to another array.
80
+ while not data.empty? do
81
+ bounce = data.pop
82
+ # Bounces MUST contain a `address` key.
83
+ if not bounce.include? :address then
84
+ raise Mailgun::ParameterError.new "Bounce MUST include a :address key: #{bounce}"
85
+ end
86
+
87
+ bounce.each do |k, v|
88
+ # Hash values MUST be strings.
89
+ if not v.is_a? String then
90
+ bounce[k] = v.to_s
91
+ end
92
+ end
93
+
94
+ valid.push bounce
95
+ end
96
+
97
+ response = @client.post("#{@domain}/bounces", valid.to_json, { "Content-Type" => "application/json" })
98
+ return response, split_return
99
+ end
100
+
101
+ def delete_bounce(address)
102
+ @client.delete("#{@domain}/bounces/#{address}")
103
+ end
104
+
105
+ def delete_all_bounces
106
+ @client.delete("#{@domain}/bounces")
107
+ end
108
+
109
+ ####
110
+ # Unsubscribes Endpoint (/v3/:domain/unsubscribes)
111
+ ####
112
+
113
+ def list_unsubscribes(params = {})
114
+ response = @client.get("#{@domain}/unsubscribes", params)
115
+ extract_paging response
116
+ response
117
+ end
118
+
119
+ def get_unsubscribe(address)
120
+ @client.get("#{@domain}/unsubscribes/#{address}")
121
+ end
122
+
123
+ def create_unsubscribe(params = {})
124
+ @client.post("#{@domain}/unsubscribes", params)
125
+ end
126
+
127
+ # Creates multiple unsubscribes on the Mailgun API.
128
+ # If an unsubscribe does not have a valid structure, it will be added to a list of unsendable unsubscribes.
129
+ # The list of unsendable unsubscribes will be returned at the end of this operation.
130
+ #
131
+ # If more than 999 unsubscribe entries are provided, the list will be split and recursive calls will be made.
132
+ #
133
+ # @param [Array] data Array of unsubscribe hashes
134
+ # @return [Response] Mailgun API response
135
+ # @return [Array] Array of invalid unsubscribe hashes.
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
+ split_return = create_unsubscribes data[999..-1]
142
+ data = data[0..998]
143
+ elsif data.length == 0 then
144
+ []
145
+ end
146
+
147
+ valid = []
148
+ # Validate the unsubscribes given
149
+ while not data.empty? do
150
+ unsubscribe = data.pop
151
+ # unsubscribes MUST contain a `address` key.
152
+ if not unsubscribe.include? :address then
153
+ raise Mailgun::ParameterError.new "Unsubscribe MUST include a :address key: #{unsubscribe}"
154
+ end
155
+
156
+ unsubscribe.each do |k, v|
157
+ # Hash values MUST be strings.
158
+ if not v.is_a? String then
159
+ unsubscribe[k] = v.to_s
160
+ end
161
+ end
162
+
163
+ valid.push unsubscribe
164
+ end
165
+
166
+ response = @client.post("#{@domain}/unsubscribes", valid.to_json, { "Content-Type" => "application/json" })
167
+ return response, split_return
168
+ end
169
+
170
+ def delete_unsubscribe(address, params = {})
171
+ @client.delete("#{@domain}/unsubscribes/#{address}")
172
+ end
173
+
174
+ ####
175
+ # Complaints Endpoint (/v3/:domain/complaints)
176
+ ####
177
+
178
+ def list_complaints(params = {})
179
+ response = @client.get("#{@domain}/complaints", params)
180
+ extract_paging response
181
+ response
182
+ end
183
+
184
+ def get_complaint(address)
185
+ @client.get("#{@domain}/complaints/#{address}", nil)
186
+ end
187
+
188
+ def create_complaint(params = {})
189
+ @client.post("#{@domain}/complaints", params)
190
+ end
191
+
192
+ # Creates multiple complaints on the Mailgun API.
193
+ # If a complaint does not have a valid structure, it will be added to a list of unsendable complaints.
194
+ # The list of unsendable complaints will be returned at the end of this operation.
195
+ #
196
+ # If more than 999 complaint entries are provided, the list will be split and recursive calls will be made.
197
+ #
198
+ # @param [Array] data Array of complaint hashes
199
+ # @return [Response] Mailgun API response
200
+ # @return [Array] Array of invalid complaint hashes.
201
+ # @return [Array] Return values from recursive call for list split.
202
+ def create_complaints(data)
203
+ # `data` should be a list of hashes, with each hash containing *at least* an `address` key.
204
+ split_return = []
205
+ if data.length >= 1000 then
206
+ split_return = create_complaints data[999..-1]
207
+ data = data[0..998]
208
+ elsif data.length == 0 then
209
+ []
210
+ end
211
+
212
+ valid = []
213
+ # Validate the complaints given
214
+ while not data.empty? do
215
+ complaint = data.pop
216
+ # complaints MUST contain a `address` key.
217
+ if not complaint.include? :address then
218
+ raise Mailgun::ParameterError.new "Complaint MUST include a :address key: #{complaint}"
219
+ end
220
+
221
+ complaint.each do |k, v|
222
+ # Hash values MUST be strings.
223
+ if not v.is_a? String then
224
+ complaint[k] = v.to_s
225
+ end
226
+ end
227
+
228
+ valid.push complaint
229
+ end
230
+
231
+ response = @client.post("#{@domain}/complaints", valid.to_json, { "Content-Type" => "application/json" })
232
+ return response, split_return
233
+ end
234
+
235
+ def delete_complaint(address)
236
+ @client.delete("#{@domain}/complaints/#{address}")
237
+ end
238
+
239
+ private
240
+
241
+ def get_from_paging(uri, params = {})
242
+ @client.get(uri, params)
243
+ end
244
+
245
+ def extract_paging(response)
246
+ rhash = response.to_h
247
+ return nil unless rhash.include? "paging"
248
+
249
+ page_info = rhash["paging"]
250
+
251
+ # Build the `next` endpoint
252
+ page_next = URI.parse(page_info["next"])
253
+ @paging_next = {
254
+ :path => page_next.path[/\/v[\d](.+)/, 1],
255
+ :params => Hash[URI.decode_www_form page_next.query],
256
+ }
257
+
258
+ # Build the `prev` endpoint
259
+ page_prev = URI.parse(page_info["previous"])
260
+ @paging_prev = {
261
+ :path => page_prev.path[/\/v[\d](.+)/, 1],
262
+ :params => Hash[URI.decode_www_form page_prev.query],
263
+ }
264
+ end
265
+
266
+ end
267
+ end
@@ -1,4 +1,4 @@
1
1
  # It's the version. Yeay!
2
2
  module Mailgun
3
- VERSION = '1.1.2'
3
+ VERSION = '1.1.3'
4
4
  end
data/lib/railgun.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'action_mailer'
2
+ require 'active_support/rescuable'
3
+
4
+ require 'railgun/attachment'
5
+ require 'railgun/errors'
6
+ require 'railgun/mailer'
7
+ require 'railgun/message'
8
+
9
+ module Railgun
10
+ end
@@ -0,0 +1,21 @@
1
+ module Railgun
2
+
3
+ class Attachment < StringIO
4
+
5
+ attr_reader :source_filename, :content_type, :path
6
+
7
+ def initialize(attachment, *args)
8
+ @path = ''
9
+ if args.detect { |opt| opt[:inline] }
10
+ basename = @source_filename = attachment.cid
11
+ else
12
+ basename = @source_filename = attachment.filename
13
+ end
14
+
15
+ @content_type = attachment.content_type.split(';')[0]
16
+
17
+ super attachment.body.decoded
18
+ end
19
+
20
+ end
21
+ 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,162 @@
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("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.subject mail.subject
101
+ mb.body_html extract_body_html(mail)
102
+ mb.body_text extract_body_text(mail)
103
+
104
+ [:to, :cc, :bcc].each do |rcpt_type|
105
+ addrs = mail[rcpt_type] || nil
106
+ case addrs
107
+ when String
108
+ # Likely a single recipient
109
+ mb.add_recipient rcpt_type.to_s, addrs
110
+ when Array
111
+ addrs.each do |addr|
112
+ mb.add_recipient rcpt_type.to_s, addr
113
+ end
114
+ when Mail::Field
115
+ mb.add_recipient rcpt_type.to_s, addrs.to_s
116
+ end
117
+ end
118
+
119
+ return mb.message if mail.attachments.empty?
120
+
121
+ mail.attachments.each do |attach|
122
+ if attach.inline?
123
+ mb.add_inline_image Attachment.new(attach, encoding: 'ascii-8bit', inline: true)
124
+ else
125
+ mb.add_attachment Attachment.new(attach, encoding: 'ascii-8bit')
126
+ end
127
+ end
128
+
129
+ return mb.message
130
+ end
131
+
132
+ # Returns the decoded HTML body from the Mail::Message object if available,
133
+ # otherwise nil.
134
+ #
135
+ # @param [Mail::Message] mail message to transform
136
+ #
137
+ # @return [String]
138
+ def extract_body_html(mail)
139
+ begin
140
+ (mail.html_part || mail).body.decoded || nil
141
+ rescue
142
+ nil
143
+ end
144
+ end
145
+
146
+ # Returns the decoded text body from the Mail::Message object if it is available,
147
+ # otherwise nil.
148
+ #
149
+ # @param [Mail::Message] mail message to transform
150
+ #
151
+ # @return [String]
152
+ def extract_body_text(mail)
153
+ begin
154
+ (mail.text_part || mail).body.decoded || nil
155
+ rescue
156
+ nil
157
+ end
158
+ end
159
+
160
+ end
161
+
162
+ ActionMailer::Base.add_delivery_method :mailgun, Railgun::Mailer