mailgun-ruby 1.1.2 → 1.1.3

Sign up to get free protection for your applications and to get access to all the features.
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