mailgun-ruby 1.1.2 → 1.2.4
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 +5 -5
- data/.ruby-env.yml.example +1 -1
- data/.travis.yml +8 -5
- data/Gemfile +1 -1
- data/README.md +77 -9
- data/{Domains.md → docs/Domains.md} +18 -0
- data/{Events.md → docs/Events.md} +0 -0
- data/{MessageBuilder.md → docs/MessageBuilder.md} +24 -5
- data/{Messages.md → docs/Messages.md} +3 -3
- data/{OptInHandler.md → docs/OptInHandler.md} +0 -0
- data/{Snippets.md → docs/Snippets.md} +21 -2
- data/docs/Suppressions.md +82 -0
- data/{Webhooks.md → docs/Webhooks.md} +1 -1
- data/docs/railgun/Overview.md +11 -0
- data/docs/railgun/Parameters.md +83 -0
- data/lib/mailgun/address.rb +5 -2
- data/lib/mailgun/client.rb +39 -8
- data/lib/mailgun/events/events.rb +40 -12
- data/lib/mailgun/messages/batch_message.rb +3 -2
- data/lib/mailgun/messages/message_builder.rb +99 -26
- data/lib/mailgun/suppressions.rb +273 -0
- data/lib/mailgun/version.rb +1 -1
- data/lib/mailgun/webhooks/webhooks.rb +1 -1
- data/lib/mailgun-ruby.rb +2 -1
- data/lib/railgun/attachment.rb +56 -0
- data/lib/railgun/errors.rb +27 -0
- data/lib/railgun/mailer.rb +237 -0
- data/lib/railgun/message.rb +17 -0
- data/lib/railgun/railtie.rb +10 -0
- data/lib/railgun.rb +8 -0
- data/mailgun.gemspec +12 -12
- data/spec/integration/email_validation_spec.rb +14 -0
- data/spec/integration/events_spec.rb +9 -1
- data/spec/integration/mailgun_spec.rb +0 -0
- data/spec/integration/suppressions_spec.rb +142 -0
- data/spec/spec_helper.rb +3 -1
- data/spec/unit/events/events_spec.rb +36 -2
- data/spec/unit/messages/batch_message_spec.rb +1 -0
- data/spec/unit/messages/message_builder_spec.rb +95 -19
- data/spec/unit/messages/sample_data/unknown.type +0 -0
- data/spec/unit/railgun/content_type_spec.rb +71 -0
- data/spec/unit/railgun/mailer_spec.rb +242 -0
- data/vcr_cassettes/email_validation.yml +57 -9
- data/vcr_cassettes/events.yml +48 -1
- data/vcr_cassettes/suppressions.yml +727 -0
- metadata +68 -36
@@ -11,6 +11,7 @@ module Mailgun
|
|
11
11
|
#
|
12
12
|
# See the Github documentation for full examples.
|
13
13
|
class Events
|
14
|
+
include Enumerable
|
14
15
|
|
15
16
|
# Public: event initializer
|
16
17
|
#
|
@@ -23,31 +24,47 @@ module Mailgun
|
|
23
24
|
@paging_previous = nil
|
24
25
|
end
|
25
26
|
|
26
|
-
# Public: Issues a simple get against the client.
|
27
|
+
# Public: Issues a simple get against the client. Alias of `next`.
|
27
28
|
#
|
28
29
|
# params - a Hash of query options and/or filters.
|
29
30
|
#
|
30
31
|
# Returns a Mailgun::Response object.
|
31
32
|
def get(params = nil)
|
32
|
-
|
33
|
+
self.next(params)
|
33
34
|
end
|
34
35
|
|
35
36
|
# Public: Using built in paging, obtains the next set of data.
|
36
37
|
# If an events request hasn't been sent previously, this will send one
|
37
38
|
# without parameters
|
38
39
|
#
|
40
|
+
# params - a Hash of query options and/or filters.
|
41
|
+
#
|
39
42
|
# Returns a Mailgun::Response object.
|
40
|
-
def next
|
41
|
-
get_events(
|
43
|
+
def next(params = nil)
|
44
|
+
get_events(params, @paging_next)
|
42
45
|
end
|
43
46
|
|
44
47
|
# Public: Using built in paging, obtains the previous set of data.
|
45
48
|
# If an events request hasn't been sent previously, this will send one
|
46
49
|
# without parameters
|
47
50
|
#
|
51
|
+
# params - a Hash of query options and/or filters.
|
52
|
+
#
|
48
53
|
# Returns Mailgun::Response object.
|
49
|
-
def previous
|
50
|
-
get_events(
|
54
|
+
def previous(params = nil)
|
55
|
+
get_events(params, @paging_previous)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Public: Allows iterating through all events and performs automatic paging.
|
59
|
+
#
|
60
|
+
# &block - Block to execute on items.
|
61
|
+
def each(&block)
|
62
|
+
items = self.next.to_h['items']
|
63
|
+
|
64
|
+
until items.empty?
|
65
|
+
items.each(&block)
|
66
|
+
items = self.next.to_h['items']
|
67
|
+
end
|
51
68
|
end
|
52
69
|
|
53
70
|
private
|
@@ -70,12 +87,23 @@ module Mailgun
|
|
70
87
|
#
|
71
88
|
# Return is irrelevant.
|
72
89
|
def extract_paging(response)
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
@
|
78
|
-
|
90
|
+
paging = response.to_h['paging']
|
91
|
+
next_page_url = paging && paging['next'] # gives nil when any one of the keys doens't exist
|
92
|
+
previous_page_url = paging && paging['previous'] # can be replaced with Hash#dig for ruby >= 2.3.0
|
93
|
+
@paging_next = extract_endpoint_from(next_page_url)
|
94
|
+
@paging_previous = extract_endpoint_from(previous_page_url)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Internal: given a paging URL, extract the endpoint
|
98
|
+
#
|
99
|
+
# response - the endpoint for the previous/next page
|
100
|
+
#
|
101
|
+
# Returns a String of the partial URI if the given url follows the regular API format
|
102
|
+
# Returns nil in other cases (e.g. when given nil, or an irrelevant url)
|
103
|
+
def extract_endpoint_from(url = nil)
|
104
|
+
URI.parse(url).path[/\/v[\d]\/#{@domain}\/events\/(.+)/,1]
|
105
|
+
rescue URI::InvalidURIError
|
106
|
+
nil
|
79
107
|
end
|
80
108
|
|
81
109
|
# Internal: construct the event path to be used by the client
|
@@ -48,7 +48,7 @@ module Mailgun
|
|
48
48
|
send_message if @counters[:recipients][recipient_type] == Mailgun::Chains::MAX_RECIPIENTS
|
49
49
|
|
50
50
|
compiled_address = parse_address(address, variables)
|
51
|
-
|
51
|
+
set_multi_complex(recipient_type, compiled_address)
|
52
52
|
|
53
53
|
store_recipient_variables(recipient_type, address, variables) if recipient_type != :from
|
54
54
|
|
@@ -84,7 +84,7 @@ module Mailgun
|
|
84
84
|
# @return [Boolean]
|
85
85
|
def send_message
|
86
86
|
rkey = 'recipient-variables'
|
87
|
-
|
87
|
+
set_multi_simple rkey, JSON.generate(@recipient_variables)
|
88
88
|
@message[rkey] = @message[rkey].first if @message.key?(rkey)
|
89
89
|
|
90
90
|
response = @client.send_message(@domain, @message).to_h!
|
@@ -111,6 +111,7 @@ module Mailgun
|
|
111
111
|
# This method resets the message object to prepare for the next batch
|
112
112
|
# of recipients.
|
113
113
|
def reset_message
|
114
|
+
@recipient_variables = {}
|
114
115
|
@message.delete('recipient-variables')
|
115
116
|
@message.delete(:to)
|
116
117
|
@message.delete(:cc)
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'mime/types'
|
1
2
|
require 'time'
|
2
3
|
|
3
4
|
module Mailgun
|
@@ -23,17 +24,24 @@ module Mailgun
|
|
23
24
|
|
24
25
|
# Adds a specific type of recipient to the message object.
|
25
26
|
#
|
27
|
+
# WARNING: Setting 'h:reply-to' with add_recipient() is deprecated! Use 'reply_to' instead.
|
28
|
+
#
|
26
29
|
# @param [String] recipient_type The type of recipient. "to", "cc", "bcc" or "h:reply-to".
|
27
30
|
# @param [String] address The email address of the recipient to add to the message object.
|
28
31
|
# @param [Hash] variables A hash of the variables associated with the recipient. We recommend "first" and "last" at a minimum!
|
29
32
|
# @return [void]
|
30
33
|
def add_recipient(recipient_type, address, variables = nil)
|
34
|
+
if recipient_type == "h:reply-to"
|
35
|
+
warn 'DEPRECATION: "add_recipient("h:reply-to", ...)" is deprecated. Please use "reply_to" instead.'
|
36
|
+
return reply_to(address, variables)
|
37
|
+
end
|
38
|
+
|
31
39
|
if (@counters[:recipients][recipient_type] || 0) >= Mailgun::Chains::MAX_RECIPIENTS
|
32
40
|
fail Mailgun::ParameterError, 'Too many recipients added to message.', address
|
33
41
|
end
|
34
42
|
|
35
43
|
compiled_address = parse_address(address, variables)
|
36
|
-
|
44
|
+
set_multi_complex(recipient_type, compiled_address)
|
37
45
|
|
38
46
|
@counters[:recipients][recipient_type] += 1 if @counters[:recipients].key?(recipient_type)
|
39
47
|
end
|
@@ -53,12 +61,25 @@ module Mailgun
|
|
53
61
|
from(address, variables)
|
54
62
|
end
|
55
63
|
|
64
|
+
# Set the message's Reply-To address.
|
65
|
+
#
|
66
|
+
# Rationale: According to RFC, only one Reply-To address is allowed, so it
|
67
|
+
# is *okay* to bypass the set_multi_simple and set reply-to directly.
|
68
|
+
#
|
69
|
+
# @param [String] address The email address to provide as Reply-To.
|
70
|
+
# @param [Hash] variables A hash of variables associated with the recipient.
|
71
|
+
# @return [void]
|
72
|
+
def reply_to(address, variables = nil)
|
73
|
+
compiled_address = parse_address(address, variables)
|
74
|
+
header("reply-to", compiled_address)
|
75
|
+
end
|
76
|
+
|
56
77
|
# Set a subject for the message object
|
57
78
|
#
|
58
79
|
# @param [String] subject The subject for the email.
|
59
80
|
# @return [void]
|
60
81
|
def subject(subj = nil)
|
61
|
-
|
82
|
+
set_multi_simple(:subject, subj)
|
62
83
|
end
|
63
84
|
|
64
85
|
# Deprecated: Please use "subject" instead.
|
@@ -72,7 +93,7 @@ module Mailgun
|
|
72
93
|
# @param [String] text_body The text body for the email.
|
73
94
|
# @return [void]
|
74
95
|
def body_text(text_body = nil)
|
75
|
-
|
96
|
+
set_multi_simple(:text, text_body)
|
76
97
|
end
|
77
98
|
|
78
99
|
# Deprecated: Please use "body_text" instead.
|
@@ -86,7 +107,7 @@ module Mailgun
|
|
86
107
|
# @param [String] html_body The html body for the email.
|
87
108
|
# @return [void]
|
88
109
|
def body_html(html_body = nil)
|
89
|
-
|
110
|
+
set_multi_simple(:html, html_body)
|
90
111
|
end
|
91
112
|
|
92
113
|
# Deprecated: Please use "body_html" instead.
|
@@ -97,7 +118,7 @@ module Mailgun
|
|
97
118
|
|
98
119
|
# Adds a series of attachments, when called upon.
|
99
120
|
#
|
100
|
-
# @param [String] attachment A file object for attaching as an attachment.
|
121
|
+
# @param [String|File] attachment A file object for attaching as an attachment.
|
101
122
|
# @param [String] filename The filename you wish the attachment to be.
|
102
123
|
# @return [void]
|
103
124
|
def add_attachment(attachment, filename = nil)
|
@@ -106,19 +127,27 @@ module Mailgun
|
|
106
127
|
|
107
128
|
# Adds an inline image to the mesage object.
|
108
129
|
#
|
109
|
-
# @param [String] inline_image A file object for attaching an inline image.
|
130
|
+
# @param [String|File] inline_image A file object for attaching an inline image.
|
110
131
|
# @param [String] filename The filename you wish the inline image to be.
|
111
132
|
# @return [void]
|
112
133
|
def add_inline_image(inline_image, filename = nil)
|
113
134
|
add_file(:inline, inline_image, filename)
|
114
135
|
end
|
115
136
|
|
137
|
+
# Adds a List-Unsubscribe for the message header.
|
138
|
+
#
|
139
|
+
# @param [Array<String>] *variables Any number of url or mailto
|
140
|
+
# @return [void]
|
141
|
+
def list_unsubscribe(*variables)
|
142
|
+
set_single('h:List-Unsubscribe', variables.map { |var| "<#{var}>" }.join(','))
|
143
|
+
end
|
144
|
+
|
116
145
|
# Send a message in test mode. (The message won't really be sent to the recipient)
|
117
146
|
#
|
118
147
|
# @param [Boolean] mode The boolean or string value (will fix itself)
|
119
148
|
# @return [void]
|
120
149
|
def test_mode(mode)
|
121
|
-
|
150
|
+
set_multi_simple('o:testmode', bool_lookup(mode))
|
122
151
|
end
|
123
152
|
|
124
153
|
# Deprecated: 'set_test_mode' is depreciated. Please use 'test_mode' instead.
|
@@ -132,7 +161,7 @@ module Mailgun
|
|
132
161
|
# @param [Boolean] mode The boolean or string value(will fix itself)
|
133
162
|
# @return [void]
|
134
163
|
def dkim(mode)
|
135
|
-
|
164
|
+
set_multi_simple('o:dkim', bool_lookup(mode))
|
136
165
|
end
|
137
166
|
|
138
167
|
# Deprecated: 'set_dkim' is deprecated. Please use 'dkim' instead.
|
@@ -148,7 +177,7 @@ module Mailgun
|
|
148
177
|
def add_campaign_id(campaign_id)
|
149
178
|
fail(Mailgun::ParameterError, 'Too many campaigns added to message.', campaign_id) if @counters[:attributes][:campaign_id] >= Mailgun::Chains::MAX_CAMPAIGN_IDS
|
150
179
|
|
151
|
-
|
180
|
+
set_multi_complex('o:campaign', campaign_id)
|
152
181
|
@counters[:attributes][:campaign_id] += 1
|
153
182
|
end
|
154
183
|
|
@@ -160,7 +189,7 @@ module Mailgun
|
|
160
189
|
if @counters[:attributes][:tag] >= Mailgun::Chains::MAX_TAGS
|
161
190
|
fail Mailgun::ParameterError, 'Too many tags added to message.', tag
|
162
191
|
end
|
163
|
-
|
192
|
+
set_multi_complex('o:tag', tag)
|
164
193
|
@counters[:attributes][:tag] += 1
|
165
194
|
end
|
166
195
|
|
@@ -169,7 +198,7 @@ module Mailgun
|
|
169
198
|
# @param [Boolean] tracking Boolean true or false.
|
170
199
|
# @return [void]
|
171
200
|
def track_opens(mode)
|
172
|
-
|
201
|
+
set_single('o:tracking-opens', bool_lookup(mode))
|
173
202
|
end
|
174
203
|
|
175
204
|
# Deprecated: 'set_open_tracking' is deprecated. Please use 'track_opens' instead.
|
@@ -183,7 +212,7 @@ module Mailgun
|
|
183
212
|
# @param [String] mode True, False, or HTML (for HTML only tracking)
|
184
213
|
# @return [void]
|
185
214
|
def track_clicks(mode)
|
186
|
-
|
215
|
+
set_single('o:tracking-clicks', bool_lookup(mode))
|
187
216
|
end
|
188
217
|
|
189
218
|
# Depreciated: 'set_click_tracking. is deprecated. Please use 'track_clicks' instead.
|
@@ -201,7 +230,7 @@ module Mailgun
|
|
201
230
|
# @return [void]
|
202
231
|
def deliver_at(timestamp)
|
203
232
|
time_str = DateTime.parse(timestamp)
|
204
|
-
|
233
|
+
set_multi_simple('o:deliverytime', time_str.rfc2822)
|
205
234
|
end
|
206
235
|
|
207
236
|
# Deprecated: 'set_delivery_time' is deprecated. Please use 'deliver_at' instead.
|
@@ -218,8 +247,12 @@ module Mailgun
|
|
218
247
|
# @return [void]
|
219
248
|
def header(name, data)
|
220
249
|
fail(Mailgun::ParameterError, 'Header name for message must be specified') if name.to_s.empty?
|
221
|
-
|
222
|
-
|
250
|
+
begin
|
251
|
+
jsondata = make_json data
|
252
|
+
set_single("h:#{name}", jsondata)
|
253
|
+
rescue Mailgun::ParameterError
|
254
|
+
set_single("h:#{name}", data)
|
255
|
+
end
|
223
256
|
end
|
224
257
|
|
225
258
|
# Deprecated: 'set_custom_data' is deprecated. Please use 'header' instead.
|
@@ -228,6 +261,23 @@ module Mailgun
|
|
228
261
|
header name, data
|
229
262
|
end
|
230
263
|
|
264
|
+
# Attaches custom JSON data to the message. See the following doc page for more info.
|
265
|
+
# https://documentation.mailgun.com/user_manual.html#attaching-data-to-messages
|
266
|
+
#
|
267
|
+
# @param [String] name A name for the custom variable block.
|
268
|
+
# @param [String|Hash] data Either a string or a hash. If it is not valid JSON or
|
269
|
+
# can not be converted to JSON, ParameterError will be raised.
|
270
|
+
# @return [void]
|
271
|
+
def variable(name, data)
|
272
|
+
fail(Mailgun::ParameterError, 'Variable name must be specified') if name.to_s.empty?
|
273
|
+
begin
|
274
|
+
jsondata = make_json data
|
275
|
+
set_single("v:#{name}", jsondata)
|
276
|
+
rescue Mailgun::ParameterError
|
277
|
+
set_single("v:#{name}", data)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
231
281
|
# Add custom parameter to the message. A custom parameter is any parameter that
|
232
282
|
# is not yet supported by the SDK, but available at the API. Note: No validation
|
233
283
|
# is performed. Don't forget to prefix the parameter with o, h, or v.
|
@@ -236,7 +286,7 @@ module Mailgun
|
|
236
286
|
# @param [string] data A string of data for the parameter.
|
237
287
|
# @return [void]
|
238
288
|
def add_custom_parameter(name, data)
|
239
|
-
|
289
|
+
set_multi_complex(name, data)
|
240
290
|
end
|
241
291
|
|
242
292
|
# Set the Message-Id header to a custom value. Don't forget to enclose the
|
@@ -249,7 +299,7 @@ module Mailgun
|
|
249
299
|
def message_id(data = nil)
|
250
300
|
key = 'h:Message-Id'
|
251
301
|
return @message.delete(key) if data.to_s.empty?
|
252
|
-
|
302
|
+
set_single(key, data)
|
253
303
|
end
|
254
304
|
|
255
305
|
# Deprecated: 'set_message_id' is deprecated. Use 'message_id' instead.
|
@@ -260,13 +310,23 @@ module Mailgun
|
|
260
310
|
|
261
311
|
private
|
262
312
|
|
313
|
+
# Sets a single value in the message hash where "multidict" features are not needed.
|
314
|
+
# Does *not* permit duplicate params.
|
315
|
+
#
|
316
|
+
# @param [String] parameter The message object parameter name.
|
317
|
+
# @param [String] value The value of the parameter.
|
318
|
+
# @return [void]
|
319
|
+
def set_single(parameter, value)
|
320
|
+
@message[parameter] = value ? value : ''
|
321
|
+
end
|
322
|
+
|
263
323
|
# Sets values within the multidict, however, prevents
|
264
324
|
# duplicate values for keys.
|
265
325
|
#
|
266
326
|
# @param [String] parameter The message object parameter name.
|
267
327
|
# @param [String] value The value of the parameter.
|
268
328
|
# @return [void]
|
269
|
-
def
|
329
|
+
def set_multi_simple(parameter, value)
|
270
330
|
@message[parameter] = value ? [value] : ['']
|
271
331
|
end
|
272
332
|
|
@@ -276,7 +336,7 @@ module Mailgun
|
|
276
336
|
# @param [String] parameter The message object parameter name.
|
277
337
|
# @param [String] value The value of the parameter.
|
278
338
|
# @return [void]
|
279
|
-
def
|
339
|
+
def set_multi_complex(parameter, value)
|
280
340
|
@message[parameter] << (value || '')
|
281
341
|
end
|
282
342
|
|
@@ -307,9 +367,9 @@ module Mailgun
|
|
307
367
|
#
|
308
368
|
# Returns a JSON object or raises ParameterError
|
309
369
|
def make_json(obj)
|
310
|
-
return JSON.parse(obj).
|
311
|
-
return obj.
|
312
|
-
return JSON.generate(obj).
|
370
|
+
return JSON.parse(obj).to_json if obj.is_a?(String)
|
371
|
+
return obj.to_json if obj.is_a?(Hash)
|
372
|
+
return JSON.generate(obj).to_json
|
313
373
|
rescue
|
314
374
|
raise Mailgun::ParameterError, 'Provided data could not be made into JSON. Try a JSON string or Hash.', obj
|
315
375
|
end
|
@@ -324,17 +384,24 @@ module Mailgun
|
|
324
384
|
def parse_address(address, vars)
|
325
385
|
return address unless vars.is_a? Hash
|
326
386
|
fail(Mailgun::ParameterError, 'Email address not specified') unless address.is_a? String
|
387
|
+
if vars['full_name'] != nil && (vars['first'] != nil || vars['last'] != nil)
|
388
|
+
fail(Mailgun::ParameterError, 'Must specify at most one of full_name or first/last. Vars passed: #{vars}')
|
389
|
+
end
|
327
390
|
|
328
|
-
|
391
|
+
if vars['full_name']
|
392
|
+
full_name = vars['full_name']
|
393
|
+
elsif vars['first'] || vars['last']
|
394
|
+
full_name = "#{vars['first']} #{vars['last']}".strip
|
395
|
+
end
|
329
396
|
|
330
|
-
return "'#{full_name}' <#{address}>" if
|
397
|
+
return "'#{full_name}' <#{address}>" if full_name
|
331
398
|
address
|
332
399
|
end
|
333
400
|
|
334
401
|
# Private: Adds a file to the message.
|
335
402
|
#
|
336
403
|
# @param [Symbol] disposition The type of file: :attachment or :inline
|
337
|
-
# @param [String] attachment A file object for attaching as an attachment.
|
404
|
+
# @param [String|File] attachment A file object for attaching as an attachment.
|
338
405
|
# @param [String] filename The filename you wish the attachment to be.
|
339
406
|
# @return [void]
|
340
407
|
#
|
@@ -347,11 +414,17 @@ module Mailgun
|
|
347
414
|
'Unable to access attachment file object.'
|
348
415
|
) unless attachment.respond_to?(:read)
|
349
416
|
|
417
|
+
if attachment.respond_to?(:path) && !attachment.respond_to?(:content_type)
|
418
|
+
mime_types = MIME::Types.type_for(attachment.path)
|
419
|
+
content_type = mime_types.empty? ? 'application/octet-stream' : mime_types[0].content_type
|
420
|
+
attachment.instance_eval "def content_type; '#{content_type}'; end"
|
421
|
+
end
|
422
|
+
|
350
423
|
unless filename.nil?
|
351
424
|
attachment.instance_variable_set :@original_filename, filename
|
352
425
|
attachment.instance_eval 'def original_filename; @original_filename; end'
|
353
426
|
end
|
354
|
-
|
427
|
+
set_multi_complex(disposition, attachment)
|
355
428
|
end
|
356
429
|
end
|
357
430
|
|