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