wj-mailgun-ruby 1.1.7
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 +7 -0
- data/.gitignore +23 -0
- data/.rubocop.yml +8 -0
- data/.rubocop_todo.yml +22 -0
- data/.ruby-env.yml.example +12 -0
- data/.ruby-version +1 -0
- data/.travis.yml +24 -0
- data/Gemfile +6 -0
- data/LICENSE +191 -0
- data/README.md +241 -0
- data/Rakefile +35 -0
- data/docs/Domains.md +54 -0
- data/docs/Events.md +46 -0
- data/docs/MessageBuilder.md +105 -0
- data/docs/Messages.md +107 -0
- data/docs/OptInHandler.md +103 -0
- data/docs/Snippets.md +526 -0
- data/docs/Suppressions.md +82 -0
- data/docs/Webhooks.md +40 -0
- data/lib/mailgun-ruby.rb +2 -0
- data/lib/mailgun.rb +39 -0
- data/lib/mailgun/address.rb +45 -0
- data/lib/mailgun/chains.rb +16 -0
- data/lib/mailgun/client.rb +199 -0
- data/lib/mailgun/domains/domains.rb +84 -0
- data/lib/mailgun/events/events.rb +120 -0
- data/lib/mailgun/exceptions/exceptions.rb +65 -0
- data/lib/mailgun/lists/opt_in_handler.rb +58 -0
- data/lib/mailgun/messages/batch_message.rb +125 -0
- data/lib/mailgun/messages/message_builder.rb +413 -0
- data/lib/mailgun/response.rb +62 -0
- data/lib/mailgun/suppressions.rb +270 -0
- data/lib/mailgun/version.rb +4 -0
- data/lib/mailgun/webhooks/webhooks.rb +101 -0
- data/lib/railgun.rb +8 -0
- data/lib/railgun/attachment.rb +56 -0
- data/lib/railgun/errors.rb +27 -0
- data/lib/railgun/mailer.rb +161 -0
- data/lib/railgun/message.rb +17 -0
- data/lib/railgun/railtie.rb +9 -0
- data/mailgun.gemspec +37 -0
- data/spec/integration/bounces_spec.rb +44 -0
- data/spec/integration/campaign_spec.rb +60 -0
- data/spec/integration/complaints_spec.rb +38 -0
- data/spec/integration/domains_spec.rb +39 -0
- data/spec/integration/email_validation_spec.rb +57 -0
- data/spec/integration/events_spec.rb +28 -0
- data/spec/integration/list_members_spec.rb +63 -0
- data/spec/integration/list_spec.rb +58 -0
- data/spec/integration/mailgun_spec.rb +121 -0
- data/spec/integration/messages/sample_data/mime.txt +38 -0
- data/spec/integration/routes_spec.rb +74 -0
- data/spec/integration/stats_spec.rb +15 -0
- data/spec/integration/suppressions_spec.rb +126 -0
- data/spec/integration/unsubscribes_spec.rb +42 -0
- data/spec/integration/webhook_spec.rb +54 -0
- data/spec/spec_helper.rb +45 -0
- data/spec/unit/connection/test_client.rb +99 -0
- data/spec/unit/events/events_spec.rb +50 -0
- data/spec/unit/lists/opt_in_handler_spec.rb +24 -0
- data/spec/unit/mailgun_spec.rb +127 -0
- data/spec/unit/messages/batch_message_spec.rb +131 -0
- data/spec/unit/messages/message_builder_spec.rb +584 -0
- data/spec/unit/messages/sample_data/mailgun_icon.png +0 -0
- data/spec/unit/messages/sample_data/mime.txt +38 -0
- data/spec/unit/messages/sample_data/rackspace_logo.jpg +0 -0
- data/vcr_cassettes/bounces.yml +175 -0
- data/vcr_cassettes/complaints.yml +175 -0
- data/vcr_cassettes/domains.todo.yml +42 -0
- data/vcr_cassettes/domains.yml +360 -0
- data/vcr_cassettes/email_validation.yml +167 -0
- data/vcr_cassettes/events.yml +108 -0
- data/vcr_cassettes/exceptions.yml +45 -0
- data/vcr_cassettes/list_members.yml +320 -0
- data/vcr_cassettes/mailing_list.todo.yml +43 -0
- data/vcr_cassettes/mailing_list.yml +390 -0
- data/vcr_cassettes/routes.yml +359 -0
- data/vcr_cassettes/send_message.yml +107 -0
- data/vcr_cassettes/stats.yml +44 -0
- data/vcr_cassettes/suppressions.yml +676 -0
- data/vcr_cassettes/unsubscribes.yml +191 -0
- data/vcr_cassettes/webhooks.yml +276 -0
- metadata +263 -0
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module Mailgun
|
4
|
+
# A Mailgun::Response object is instantiated for each response generated
|
5
|
+
# by the Client request. The Response object supports deserialization of
|
6
|
+
# the JSON result. Or, if you prefer JSON or YAML formatting, call the
|
7
|
+
# method for conversion.
|
8
|
+
#
|
9
|
+
# See the Github documentation for full examples.
|
10
|
+
class Response
|
11
|
+
# All responses have a payload and a code corresponding to http, though
|
12
|
+
# slightly different
|
13
|
+
attr_accessor :body, :code
|
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
|
+
|
20
|
+
def initialize(response)
|
21
|
+
@body = response.body
|
22
|
+
@code = response.code
|
23
|
+
end
|
24
|
+
|
25
|
+
# Return response as Ruby Hash
|
26
|
+
#
|
27
|
+
# @return [Hash] A standard Ruby Hash containing the HTTP result.
|
28
|
+
|
29
|
+
def to_h
|
30
|
+
JSON.parse(@body)
|
31
|
+
rescue => err
|
32
|
+
raise ParseError.new(err), err
|
33
|
+
end
|
34
|
+
|
35
|
+
# Replace @body with Ruby Hash
|
36
|
+
#
|
37
|
+
# @return [Hash] A standard Ruby Hash containing the HTTP result.
|
38
|
+
def to_h!
|
39
|
+
@body = JSON.parse(@body)
|
40
|
+
rescue => err
|
41
|
+
raise ParseError.new(err), err
|
42
|
+
end
|
43
|
+
|
44
|
+
# Return response as Yaml
|
45
|
+
#
|
46
|
+
# @return [String] A string containing response as YAML
|
47
|
+
def to_yaml
|
48
|
+
YAML.dump(to_h)
|
49
|
+
rescue => err
|
50
|
+
raise ParseError.new(err), err
|
51
|
+
end
|
52
|
+
|
53
|
+
# Replace @body with YAML
|
54
|
+
#
|
55
|
+
# @return [String] A string containing response as YAML
|
56
|
+
def to_yaml!
|
57
|
+
@body = YAML.dump(to_h)
|
58
|
+
rescue => err
|
59
|
+
raise ParseError.new(err), err
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,270 @@
|
|
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
|
+
if not v.is_a? String then
|
161
|
+
unsubscribe[k] = v.to_s
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
valid.push unsubscribe
|
166
|
+
end
|
167
|
+
|
168
|
+
response = @client.post("#{@domain}/unsubscribes", valid.to_json, { "Content-Type" => "application/json" })
|
169
|
+
return response, split_return
|
170
|
+
end
|
171
|
+
|
172
|
+
def delete_unsubscribe(address, params = {})
|
173
|
+
@client.delete("#{@domain}/unsubscribes/#{address}")
|
174
|
+
end
|
175
|
+
|
176
|
+
####
|
177
|
+
# Complaints Endpoint (/v3/:domain/complaints)
|
178
|
+
####
|
179
|
+
|
180
|
+
def list_complaints(params = {})
|
181
|
+
response = @client.get("#{@domain}/complaints", params)
|
182
|
+
extract_paging response
|
183
|
+
response
|
184
|
+
end
|
185
|
+
|
186
|
+
def get_complaint(address)
|
187
|
+
@client.get("#{@domain}/complaints/#{address}", nil)
|
188
|
+
end
|
189
|
+
|
190
|
+
def create_complaint(params = {})
|
191
|
+
@client.post("#{@domain}/complaints", params)
|
192
|
+
end
|
193
|
+
|
194
|
+
# Creates multiple complaints on the Mailgun API.
|
195
|
+
# If a complaint does not have a valid structure, it will be added to a list of unsendable complaints.
|
196
|
+
# The list of unsendable complaints will be returned at the end of this operation.
|
197
|
+
#
|
198
|
+
# If more than 999 complaint entries are provided, the list will be split and recursive calls will be made.
|
199
|
+
#
|
200
|
+
# @param [Array] data Array of complaint hashes
|
201
|
+
# @return [Response] Mailgun API response
|
202
|
+
# @return [Array] Return values from recursive call for list split.
|
203
|
+
def create_complaints(data)
|
204
|
+
# `data` should be a list of hashes, with each hash containing *at least* an `address` key.
|
205
|
+
split_return = []
|
206
|
+
if data.length >= 1000 then
|
207
|
+
resp, resp_l = create_complaints data[999..-1]
|
208
|
+
split_return.push(resp)
|
209
|
+
split_return.concat(resp_l)
|
210
|
+
data = data[0..998]
|
211
|
+
elsif data.length == 0 then
|
212
|
+
return nil, []
|
213
|
+
end
|
214
|
+
|
215
|
+
valid = []
|
216
|
+
# Validate the complaints given
|
217
|
+
while not data.empty? do
|
218
|
+
complaint = data.pop
|
219
|
+
# complaints MUST contain a `address` key.
|
220
|
+
if not complaint.include? :address then
|
221
|
+
raise Mailgun::ParameterError.new "Complaint MUST include a :address key: #{complaint}"
|
222
|
+
end
|
223
|
+
|
224
|
+
complaint.each do |k, v|
|
225
|
+
# Hash values MUST be strings.
|
226
|
+
if not v.is_a? String then
|
227
|
+
complaint[k] = v.to_s
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
valid.push complaint
|
232
|
+
end
|
233
|
+
|
234
|
+
response = @client.post("#{@domain}/complaints", valid.to_json, { "Content-Type" => "application/json" })
|
235
|
+
return response, split_return
|
236
|
+
end
|
237
|
+
|
238
|
+
def delete_complaint(address)
|
239
|
+
@client.delete("#{@domain}/complaints/#{address}")
|
240
|
+
end
|
241
|
+
|
242
|
+
private
|
243
|
+
|
244
|
+
def get_from_paging(uri, params = {})
|
245
|
+
@client.get(uri, params)
|
246
|
+
end
|
247
|
+
|
248
|
+
def extract_paging(response)
|
249
|
+
rhash = response.to_h
|
250
|
+
return nil unless rhash.include? "paging"
|
251
|
+
|
252
|
+
page_info = rhash["paging"]
|
253
|
+
|
254
|
+
# Build the `next` endpoint
|
255
|
+
page_next = URI.parse(page_info["next"])
|
256
|
+
@paging_next = {
|
257
|
+
:path => page_next.path[/\/v[\d](.+)/, 1],
|
258
|
+
:params => Hash[URI.decode_www_form page_next.query],
|
259
|
+
}
|
260
|
+
|
261
|
+
# Build the `prev` endpoint
|
262
|
+
page_prev = URI.parse(page_info["previous"])
|
263
|
+
@paging_prev = {
|
264
|
+
:path => page_prev.path[/\/v[\d](.+)/, 1],
|
265
|
+
:params => Hash[URI.decode_www_form page_prev.query],
|
266
|
+
}
|
267
|
+
end
|
268
|
+
|
269
|
+
end
|
270
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Mailgun
|
2
|
+
|
3
|
+
# A Mailgun::Webhooks object is a simple CRUD interface to Mailgun Webhooks.
|
4
|
+
# Uses Mailgun
|
5
|
+
class Webhooks
|
6
|
+
|
7
|
+
# Public creates a new Mailgun::Webhooks instance.
|
8
|
+
# Defaults to Mailgun::Client
|
9
|
+
def initialize(client = Mailgun::Client.new)
|
10
|
+
@client = client
|
11
|
+
end
|
12
|
+
|
13
|
+
# Public: Get Webhooks
|
14
|
+
#
|
15
|
+
# domain - a string the domain name to retrieve webhooks for
|
16
|
+
# options - a Hash of options
|
17
|
+
#
|
18
|
+
# Returns a Hash of the list of domains or nil
|
19
|
+
def list(domain, options = {})
|
20
|
+
res = @client.get("domains/#{domain}/webhooks", options)
|
21
|
+
res.to_h['webhooks']
|
22
|
+
end
|
23
|
+
alias_method :get_webhooks, :list
|
24
|
+
|
25
|
+
# Public: Get webook information for a specific action
|
26
|
+
#
|
27
|
+
# domain - a String of Domain name to find a webhook url for
|
28
|
+
# action - a String identifying the webhook to get the URL for
|
29
|
+
#
|
30
|
+
# Returns a String of the url for the identified webhook or an
|
31
|
+
# empty String if one is not set
|
32
|
+
def info(domain, action)
|
33
|
+
res = @client.get("domains/#{domain}/webhooks/#{action}")
|
34
|
+
res.to_h['webhook']['url'] || ''
|
35
|
+
rescue NoMethodError
|
36
|
+
''
|
37
|
+
end
|
38
|
+
alias_method :get_webhook_url, :info
|
39
|
+
|
40
|
+
# Public: Add webhook
|
41
|
+
#
|
42
|
+
# domain - A String of the domain name (ex. domain.com)
|
43
|
+
# action - A String of the action to create a webhook for
|
44
|
+
# url - A String of the url of the webhook
|
45
|
+
#
|
46
|
+
# Returns a Boolean of whether the webhook was created
|
47
|
+
def create(domain, action, url = '')
|
48
|
+
res = @client.post("domains/#{domain}/webhooks", id: action, url: url)
|
49
|
+
res.to_h['webhook']['url'] == url && res.to_h[message] == 'Webhook has been created'
|
50
|
+
end
|
51
|
+
alias_method :add, :create
|
52
|
+
alias_method :add_webhook, :create
|
53
|
+
|
54
|
+
# Public: Sets all webhooks to the same URL
|
55
|
+
#
|
56
|
+
# domain - A String of the domain name
|
57
|
+
# url - A String of the url to set all webhooks to
|
58
|
+
#
|
59
|
+
# Returns true or false
|
60
|
+
def create_all(domain, url = '')
|
61
|
+
%w(bounce click deliver drop open spam unsubscribe).each do |action|
|
62
|
+
add_webhook domain, action, url
|
63
|
+
end
|
64
|
+
true
|
65
|
+
rescue
|
66
|
+
false
|
67
|
+
end
|
68
|
+
alias_method :add_all_webhooks, :create_all
|
69
|
+
|
70
|
+
# Public: Delete a specific webhook
|
71
|
+
#
|
72
|
+
# domain - The required String of domain name
|
73
|
+
# action - The required String of the webhook action to delete
|
74
|
+
#
|
75
|
+
# Returns a Boolean of the success
|
76
|
+
def remove(domain, action)
|
77
|
+
fail Mailgun::ParameterError('Domain not provided to remove webhook from') unless domain
|
78
|
+
fail Mailgun::ParameterError('Action not provided to identify webhook to remove') unless action
|
79
|
+
@client.delete("domains/#{domain}/webhooks/#{action}").to_h['message'] == 'Webhook has been deleted'
|
80
|
+
rescue Mailgun::CommunicationError
|
81
|
+
false
|
82
|
+
end
|
83
|
+
alias_method :delete, :remove
|
84
|
+
alias_method :delete_webhook, :remove
|
85
|
+
|
86
|
+
# Public: Delete all webhooks for a domain
|
87
|
+
#
|
88
|
+
# domain - A required String of the domain to remove all webhooks for
|
89
|
+
#
|
90
|
+
# Returns a Boolean on the success
|
91
|
+
def remove_all(domain)
|
92
|
+
fail Mailgun::ParameterError('Domain not provided to remove webhooks from') unless domain
|
93
|
+
%w(bounce click deliver drop open spam unsubscribe).each do |action|
|
94
|
+
delete_webhook domain, action
|
95
|
+
end
|
96
|
+
end
|
97
|
+
alias_method :delete_all, :remove_all
|
98
|
+
alias_method :delete_all_webooks, :remove_all
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|