mailgun-ruby 1.1.0 → 1.2.5

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.
Files changed (57) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.ruby-env.yml.example +1 -1
  4. data/.travis.yml +23 -7
  5. data/Gemfile +2 -0
  6. data/README.md +79 -18
  7. data/{Domains.md → docs/Domains.md} +18 -0
  8. data/{Events.md → docs/Events.md} +0 -0
  9. data/{MessageBuilder.md → docs/MessageBuilder.md} +32 -13
  10. data/{Messages.md → docs/Messages.md} +3 -3
  11. data/{OptInHandler.md → docs/OptInHandler.md} +0 -0
  12. data/{Snippets.md → docs/Snippets.md} +21 -2
  13. data/docs/Suppressions.md +82 -0
  14. data/{Webhooks.md → docs/Webhooks.md} +1 -1
  15. data/docs/railgun/Overview.md +11 -0
  16. data/docs/railgun/Parameters.md +83 -0
  17. data/lib/mailgun/address.rb +48 -0
  18. data/lib/mailgun/client.rb +85 -8
  19. data/lib/mailgun/events/events.rb +40 -12
  20. data/lib/mailgun/exceptions/exceptions.rb +21 -7
  21. data/lib/mailgun/lists/opt_in_handler.rb +0 -1
  22. data/lib/mailgun/messages/batch_message.rb +3 -2
  23. data/lib/mailgun/messages/message_builder.rb +139 -30
  24. data/lib/mailgun/response.rb +7 -0
  25. data/lib/mailgun/suppressions.rb +273 -0
  26. data/lib/mailgun/version.rb +1 -1
  27. data/lib/mailgun/webhooks/webhooks.rb +1 -1
  28. data/lib/mailgun-ruby.rb +2 -0
  29. data/lib/mailgun.rb +1 -0
  30. data/lib/railgun/attachment.rb +56 -0
  31. data/lib/railgun/errors.rb +27 -0
  32. data/lib/railgun/mailer.rb +253 -0
  33. data/lib/railgun/message.rb +18 -0
  34. data/lib/railgun/railtie.rb +10 -0
  35. data/lib/railgun.rb +8 -0
  36. data/mailgun.gemspec +12 -13
  37. data/spec/integration/email_validation_spec.rb +57 -15
  38. data/spec/integration/events_spec.rb +9 -1
  39. data/spec/integration/mailer_spec.rb +67 -0
  40. data/spec/integration/mailgun_spec.rb +51 -1
  41. data/spec/integration/suppressions_spec.rb +142 -0
  42. data/spec/spec_helper.rb +3 -1
  43. data/spec/unit/connection/test_client.rb +16 -0
  44. data/spec/unit/events/events_spec.rb +36 -2
  45. data/spec/unit/mailgun_spec.rb +32 -10
  46. data/spec/unit/messages/batch_message_spec.rb +56 -40
  47. data/spec/unit/messages/message_builder_spec.rb +267 -81
  48. data/spec/unit/messages/sample_data/unknown.type +0 -0
  49. data/spec/unit/railgun/content_type_spec.rb +71 -0
  50. data/spec/unit/railgun/mailer_spec.rb +388 -0
  51. data/vcr_cassettes/email_validation.yml +136 -25
  52. data/vcr_cassettes/events.yml +48 -1
  53. data/vcr_cassettes/exceptions.yml +45 -0
  54. data/vcr_cassettes/mailer_invalid_domain.yml +109 -0
  55. data/vcr_cassettes/message_deliver.yml +149 -0
  56. data/vcr_cassettes/suppressions.yml +727 -0
  57. metadata +65 -40
@@ -1,4 +1,5 @@
1
1
  require 'mailgun/chains'
2
+ require 'mailgun/suppressions'
2
3
  require 'mailgun/exceptions/exceptions'
3
4
 
4
5
  module Mailgun
@@ -12,13 +13,50 @@ module Mailgun
12
13
  def initialize(api_key = Mailgun.api_key,
13
14
  api_host = 'api.mailgun.net',
14
15
  api_version = 'v3',
15
- ssl = true)
16
+ ssl = true,
17
+ test_mode = false,
18
+ timeout = nil,
19
+ proxy_url = nil)
20
+
21
+ rest_client_params = {
22
+ user: 'api',
23
+ password: api_key,
24
+ user_agent: "mailgun-sdk-ruby/#{Mailgun::VERSION}"
25
+ }
26
+ rest_client_params[:timeout] = timeout if timeout
16
27
 
17
28
  endpoint = endpoint_generator(api_host, api_version, ssl)
18
- @http_client = RestClient::Resource.new(endpoint,
19
- user: 'api',
20
- password: api_key,
21
- user_agent: "mailgun-sdk-ruby/#{Mailgun::VERSION}")
29
+ RestClient.proxy = proxy_url
30
+ @http_client = RestClient::Resource.new(endpoint, rest_client_params)
31
+ @test_mode = test_mode
32
+ end
33
+
34
+ # Enable test mode
35
+ #
36
+ # Prevents sending of any messages.
37
+ def enable_test_mode!
38
+ @test_mode = true
39
+ end
40
+
41
+ # Disable test mode
42
+ #
43
+ # Reverts the test_mode flag and allows the client to send messages.
44
+ def disable_test_mode!
45
+ @test_mode = false
46
+ end
47
+
48
+ # Client is in test mode?
49
+ #
50
+ # @return [Boolean] Is the client set in test mode?
51
+ def test_mode?
52
+ @test_mode
53
+ end
54
+
55
+ # Provides a store of all the emails sent in test mode so you can check them.
56
+ #
57
+ # @return [Hash]
58
+ def self.deliveries
59
+ @@deliveries ||= []
22
60
  end
23
61
 
24
62
  # Simple Message Sending
@@ -28,8 +66,25 @@ module Mailgun
28
66
  # containing required parameters for the requested resource.
29
67
  # @return [Mailgun::Response] A Mailgun::Response object.
30
68
  def send_message(working_domain, data)
69
+ perform_data_validation(working_domain, data)
70
+
71
+ if test_mode? then
72
+ Mailgun::Client.deliveries << data
73
+ return Response.from_hash(
74
+ {
75
+ :body => "{\"id\": \"test-mode-mail-#{SecureRandom.uuid}@localhost\", \"message\": \"Queued. Thank you.\"}",
76
+ :code => 200,
77
+ }
78
+ )
79
+ end
80
+
31
81
  case data
32
82
  when Hash
83
+ # Remove nil values from the data hash
84
+ # Submitting nils to the API will likely cause an error.
85
+ # See also: https://github.com/mailgun/mailgun-ruby/issues/32
86
+ data = data.select { |k, v| v != nil }
87
+
33
88
  if data.key?(:message)
34
89
  if data[:message].is_a?(String)
35
90
  data[:message] = convert_string_to_file(data[:message])
@@ -50,9 +105,10 @@ module Mailgun
50
105
  # with. Be sure to include your domain, where necessary.
51
106
  # @param [Hash] data This should be a standard Hash
52
107
  # containing required parameters for the requested resource.
108
+ # @param [Hash] headers Additional headers to pass to the resource.
53
109
  # @return [Mailgun::Response] A Mailgun::Response object.
54
- def post(resource_path, data)
55
- response = @http_client[resource_path].post(data)
110
+ def post(resource_path, data, headers = {})
111
+ response = @http_client[resource_path].post(data, headers)
56
112
  Response.new(response)
57
113
  rescue => err
58
114
  raise communication_error err
@@ -62,8 +118,9 @@ module Mailgun
62
118
  #
63
119
  # @param [String] resource_path This is the API resource you wish to interact
64
120
  # with. Be sure to include your domain, where necessary.
65
- # @param [Hash] query_string This should be a standard Hash
121
+ # @param [Hash] params This should be a standard Hash
66
122
  # containing required parameters for the requested resource.
123
+ # @param [String] accept Acceptable Content-Type of the response body.
67
124
  # @return [Mailgun::Response] A Mailgun::Response object.
68
125
  def get(resource_path, params = nil, accept = '*/*')
69
126
  if params
@@ -102,6 +159,14 @@ module Mailgun
102
159
  raise communication_error err
103
160
  end
104
161
 
162
+ # Constructs a Suppressions client for the given domain.
163
+ #
164
+ # @param [String] domain Domain which suppressions requests will be made for
165
+ # @return [Mailgun::Suppressions]
166
+ def suppressions(domain)
167
+ Suppressions.new(self, domain)
168
+ end
169
+
105
170
  private
106
171
 
107
172
  # Converts MIME string to file for easy uploading to API
@@ -139,5 +204,17 @@ module Mailgun
139
204
  CommunicationError.new(e.message)
140
205
  end
141
206
 
207
+ def perform_data_validation(working_domain, data)
208
+ message = data.respond_to?(:message) ? data.message : data
209
+ fail ParameterError.new('Missing working domain', working_domain) unless working_domain
210
+ fail ParameterError.new(
211
+ 'Missing `to` recipient, message should containg at least 1 recipient',
212
+ working_domain
213
+ ) if message.fetch('to', []).empty? && message.fetch(:to, []).empty?
214
+ fail ParameterError.new(
215
+ 'Missing a `from` sender, message should containg at least 1 `from` sender',
216
+ working_domain
217
+ ) if message.fetch('from', []).empty? && message.fetch(:from, []).empty?
218
+ end
142
219
  end
143
220
  end
@@ -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
- get_events(params)
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(nil, @paging_next)
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(nil, @paging_previous)
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
- # This is pretty hackish. But the URL will never change in API v2.
74
- @paging_next = response.to_h['paging']['next'].split('/')[6]
75
- @paging_previous = response.to_h['paging']['previous'].split('/')[6]
76
- rescue
77
- @paging_next = nil
78
- @paging_previous = nil
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
@@ -34,19 +34,33 @@ module Mailgun
34
34
 
35
35
  # Public: fallback if there is no response code on the object
36
36
  NOCODE = 000
37
+ FORBIDDEN = 'Forbidden'
37
38
 
38
39
  # Public: initialization of new error given a message and/or object
39
40
  #
40
- # message - a String detailing the error
41
- # object - a RestClient::Reponse object
41
+ # message - a String detailing the error
42
+ # response - a RestClient::Response object
42
43
  #
43
- def initialize(message = nil, object = nil)
44
- @object = object
45
- @code = object.http_code || NOCODE
46
- super(JSON.parse(object.body)['message'])
44
+ def initialize(message = nil, response = nil)
45
+ @response = response
46
+ @code = response.code || NOCODE
47
+
48
+ begin
49
+ api_message = JSON.parse(response.body)['message']
50
+ rescue JSON::ParserError
51
+ api_message = response.body
52
+ rescue NoMethodError
53
+ api_message = "Unknown API error"
54
+ end
55
+ api_message = api_message + ' - Invalid Domain or API key' if api_message == FORBIDDEN
56
+
57
+ message = message || ''
58
+ message = message + ': ' + api_message
59
+
60
+ super(message, response)
47
61
  rescue NoMethodError, JSON::ParserError
48
62
  @code = NOCODE
49
- super(message)
63
+ super(message, response)
50
64
  end
51
65
 
52
66
  end
@@ -1,4 +1,3 @@
1
- require 'json'
2
1
  require 'uri'
3
2
  require 'base64'
4
3
  require 'openssl'
@@ -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
- complex_setter(recipient_type, compiled_address)
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
- simple_setter rkey, JSON.generate(@recipient_variables)
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,5 +1,5 @@
1
+ require 'mime/types'
1
2
  require 'time'
2
- require 'json'
3
3
 
4
4
  module Mailgun
5
5
 
@@ -24,17 +24,24 @@ module Mailgun
24
24
 
25
25
  # Adds a specific type of recipient to the message object.
26
26
  #
27
+ # WARNING: Setting 'h:reply-to' with add_recipient() is deprecated! Use 'reply_to' instead.
28
+ #
27
29
  # @param [String] recipient_type The type of recipient. "to", "cc", "bcc" or "h:reply-to".
28
30
  # @param [String] address The email address of the recipient to add to the message object.
29
31
  # @param [Hash] variables A hash of the variables associated with the recipient. We recommend "first" and "last" at a minimum!
30
32
  # @return [void]
31
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
+
32
39
  if (@counters[:recipients][recipient_type] || 0) >= Mailgun::Chains::MAX_RECIPIENTS
33
40
  fail Mailgun::ParameterError, 'Too many recipients added to message.', address
34
41
  end
35
42
 
36
43
  compiled_address = parse_address(address, variables)
37
- complex_setter(recipient_type, compiled_address)
44
+ set_multi_complex(recipient_type, compiled_address)
38
45
 
39
46
  @counters[:recipients][recipient_type] += 1 if @counters[:recipients].key?(recipient_type)
40
47
  end
@@ -54,12 +61,25 @@ module Mailgun
54
61
  from(address, variables)
55
62
  end
56
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
+
57
77
  # Set a subject for the message object
58
78
  #
59
79
  # @param [String] subject The subject for the email.
60
80
  # @return [void]
61
81
  def subject(subj = nil)
62
- simple_setter(:subject, subj)
82
+ set_multi_simple(:subject, subj)
63
83
  end
64
84
 
65
85
  # Deprecated: Please use "subject" instead.
@@ -73,7 +93,7 @@ module Mailgun
73
93
  # @param [String] text_body The text body for the email.
74
94
  # @return [void]
75
95
  def body_text(text_body = nil)
76
- simple_setter(:text, text_body)
96
+ set_multi_simple(:text, text_body)
77
97
  end
78
98
 
79
99
  # Deprecated: Please use "body_text" instead.
@@ -87,7 +107,7 @@ module Mailgun
87
107
  # @param [String] html_body The html body for the email.
88
108
  # @return [void]
89
109
  def body_html(html_body = nil)
90
- simple_setter(:html, html_body)
110
+ set_multi_simple(:html, html_body)
91
111
  end
92
112
 
93
113
  # Deprecated: Please use "body_html" instead.
@@ -98,7 +118,7 @@ module Mailgun
98
118
 
99
119
  # Adds a series of attachments, when called upon.
100
120
  #
101
- # @param [String] attachment A file object for attaching as an attachment.
121
+ # @param [String|File] attachment A file object for attaching as an attachment.
102
122
  # @param [String] filename The filename you wish the attachment to be.
103
123
  # @return [void]
104
124
  def add_attachment(attachment, filename = nil)
@@ -107,19 +127,27 @@ module Mailgun
107
127
 
108
128
  # Adds an inline image to the mesage object.
109
129
  #
110
- # @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.
111
131
  # @param [String] filename The filename you wish the inline image to be.
112
132
  # @return [void]
113
133
  def add_inline_image(inline_image, filename = nil)
114
134
  add_file(:inline, inline_image, filename)
115
135
  end
116
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
+
117
145
  # Send a message in test mode. (The message won't really be sent to the recipient)
118
146
  #
119
147
  # @param [Boolean] mode The boolean or string value (will fix itself)
120
148
  # @return [void]
121
149
  def test_mode(mode)
122
- simple_setter('o:testmode', bool_lookup(mode))
150
+ set_multi_simple('o:testmode', bool_lookup(mode))
123
151
  end
124
152
 
125
153
  # Deprecated: 'set_test_mode' is depreciated. Please use 'test_mode' instead.
@@ -133,7 +161,7 @@ module Mailgun
133
161
  # @param [Boolean] mode The boolean or string value(will fix itself)
134
162
  # @return [void]
135
163
  def dkim(mode)
136
- simple_setter('o:dkim', bool_lookup(mode))
164
+ set_multi_simple('o:dkim', bool_lookup(mode))
137
165
  end
138
166
 
139
167
  # Deprecated: 'set_dkim' is deprecated. Please use 'dkim' instead.
@@ -149,7 +177,7 @@ module Mailgun
149
177
  def add_campaign_id(campaign_id)
150
178
  fail(Mailgun::ParameterError, 'Too many campaigns added to message.', campaign_id) if @counters[:attributes][:campaign_id] >= Mailgun::Chains::MAX_CAMPAIGN_IDS
151
179
 
152
- complex_setter('o:campaign', campaign_id)
180
+ set_multi_complex('o:campaign', campaign_id)
153
181
  @counters[:attributes][:campaign_id] += 1
154
182
  end
155
183
 
@@ -161,7 +189,7 @@ module Mailgun
161
189
  if @counters[:attributes][:tag] >= Mailgun::Chains::MAX_TAGS
162
190
  fail Mailgun::ParameterError, 'Too many tags added to message.', tag
163
191
  end
164
- complex_setter('o:tag', tag)
192
+ set_multi_complex('o:tag', tag)
165
193
  @counters[:attributes][:tag] += 1
166
194
  end
167
195
 
@@ -170,7 +198,9 @@ module Mailgun
170
198
  # @param [Boolean] tracking Boolean true or false.
171
199
  # @return [void]
172
200
  def track_opens(mode)
173
- simple_setter('o:tracking-opens', bool_lookup(mode))
201
+ value = bool_lookup(mode)
202
+ set_single('o:tracking-opens', value)
203
+ set_multi_simple('o:tracking', value)
174
204
  end
175
205
 
176
206
  # Deprecated: 'set_open_tracking' is deprecated. Please use 'track_opens' instead.
@@ -184,7 +214,9 @@ module Mailgun
184
214
  # @param [String] mode True, False, or HTML (for HTML only tracking)
185
215
  # @return [void]
186
216
  def track_clicks(mode)
187
- simple_setter('o:tracking-clicks', bool_lookup(mode))
217
+ value = bool_lookup(mode)
218
+ set_single('o:tracking-clicks', value)
219
+ set_multi_simple('o:tracking', value)
188
220
  end
189
221
 
190
222
  # Depreciated: 'set_click_tracking. is deprecated. Please use 'track_clicks' instead.
@@ -202,7 +234,7 @@ module Mailgun
202
234
  # @return [void]
203
235
  def deliver_at(timestamp)
204
236
  time_str = DateTime.parse(timestamp)
205
- simple_setter('o:deliverytime', time_str.rfc2822)
237
+ set_multi_simple('o:deliverytime', time_str.rfc2822)
206
238
  end
207
239
 
208
240
  # Deprecated: 'set_delivery_time' is deprecated. Please use 'deliver_at' instead.
@@ -219,8 +251,12 @@ module Mailgun
219
251
  # @return [void]
220
252
  def header(name, data)
221
253
  fail(Mailgun::ParameterError, 'Header name for message must be specified') if name.to_s.empty?
222
- jsondata = make_json data
223
- simple_setter("v:#{name}", jsondata)
254
+ begin
255
+ jsondata = make_json data
256
+ set_single("h:#{name}", jsondata)
257
+ rescue Mailgun::ParameterError
258
+ set_single("h:#{name}", data)
259
+ end
224
260
  end
225
261
 
226
262
  # Deprecated: 'set_custom_data' is deprecated. Please use 'header' instead.
@@ -229,6 +265,23 @@ module Mailgun
229
265
  header name, data
230
266
  end
231
267
 
268
+ # Attaches custom JSON data to the message. See the following doc page for more info.
269
+ # https://documentation.mailgun.com/user_manual.html#attaching-data-to-messages
270
+ #
271
+ # @param [String] name A name for the custom variable block.
272
+ # @param [String|Hash] data Either a string or a hash. If it is not valid JSON or
273
+ # can not be converted to JSON, ParameterError will be raised.
274
+ # @return [void]
275
+ def variable(name, data)
276
+ fail(Mailgun::ParameterError, 'Variable name must be specified') if name.to_s.empty?
277
+ begin
278
+ jsondata = make_json data
279
+ set_single("v:#{name}", jsondata)
280
+ rescue Mailgun::ParameterError
281
+ set_single("v:#{name}", data)
282
+ end
283
+ end
284
+
232
285
  # Add custom parameter to the message. A custom parameter is any parameter that
233
286
  # is not yet supported by the SDK, but available at the API. Note: No validation
234
287
  # is performed. Don't forget to prefix the parameter with o, h, or v.
@@ -237,7 +290,7 @@ module Mailgun
237
290
  # @param [string] data A string of data for the parameter.
238
291
  # @return [void]
239
292
  def add_custom_parameter(name, data)
240
- complex_setter(name, data)
293
+ set_multi_complex(name, data)
241
294
  end
242
295
 
243
296
  # Set the Message-Id header to a custom value. Don't forget to enclose the
@@ -250,7 +303,7 @@ module Mailgun
250
303
  def message_id(data = nil)
251
304
  key = 'h:Message-Id'
252
305
  return @message.delete(key) if data.to_s.empty?
253
- @message[key] = data
306
+ set_single(key, data)
254
307
  end
255
308
 
256
309
  # Deprecated: 'set_message_id' is deprecated. Use 'message_id' instead.
@@ -259,15 +312,57 @@ module Mailgun
259
312
  message_id data
260
313
  end
261
314
 
315
+ # Set name of a template stored via template API. See Templates for more information
316
+ # https://documentation.mailgun.com/en/latest/api-templates.html
317
+ #
318
+ # @param [String] tag A defined template name to use. Passing nil or
319
+ # empty string will delete template key and value from @message hash.
320
+ # @return [void]
321
+ def template(template_name = nil)
322
+ key = 'template'
323
+ return @message.delete(key) if template_name.to_s.empty?
324
+ set_single(key, template_name)
325
+ end
326
+
327
+ # Set specific template version.
328
+ #
329
+ # @param [String] tag A defined template name to use. Passing nil or
330
+ # empty string will delete template key and value from @message hash.
331
+ # @return [void]
332
+ def template_version(version = nil)
333
+ key = 't:version'
334
+ return @message.delete(key) if version.to_s.empty?
335
+ set_single(key, version)
336
+ end
337
+
338
+ # Turn off or on template rendering in the text part
339
+ # of the message in case of template sending.
340
+ #
341
+ # @param [Boolean] tracking Boolean true or false.
342
+ # @return [void]
343
+ def template_text(mode)
344
+ set_single('t:text', bool_lookup(mode))
345
+ end
346
+
262
347
  private
263
348
 
349
+ # Sets a single value in the message hash where "multidict" features are not needed.
350
+ # Does *not* permit duplicate params.
351
+ #
352
+ # @param [String] parameter The message object parameter name.
353
+ # @param [String] value The value of the parameter.
354
+ # @return [void]
355
+ def set_single(parameter, value)
356
+ @message[parameter] = value ? value : ''
357
+ end
358
+
264
359
  # Sets values within the multidict, however, prevents
265
360
  # duplicate values for keys.
266
361
  #
267
362
  # @param [String] parameter The message object parameter name.
268
363
  # @param [String] value The value of the parameter.
269
364
  # @return [void]
270
- def simple_setter(parameter, value)
365
+ def set_multi_simple(parameter, value)
271
366
  @message[parameter] = value ? [value] : ['']
272
367
  end
273
368
 
@@ -277,7 +372,7 @@ module Mailgun
277
372
  # @param [String] parameter The message object parameter name.
278
373
  # @param [String] value The value of the parameter.
279
374
  # @return [void]
280
- def complex_setter(parameter, value)
375
+ def set_multi_complex(parameter, value)
281
376
  @message[parameter] << (value || '')
282
377
  end
283
378
 
@@ -288,6 +383,7 @@ module Mailgun
288
383
  def bool_lookup(value)
289
384
  return 'yes' if %w(true yes yep).include? value.to_s.downcase
290
385
  return 'no' if %w(false no nope).include? value.to_s.downcase
386
+ warn 'WARN: for bool type actions next values are prefered: true yes yep | false no nope | htmlonly'
291
387
  value
292
388
  end
293
389
 
@@ -308,9 +404,9 @@ module Mailgun
308
404
  #
309
405
  # Returns a JSON object or raises ParameterError
310
406
  def make_json(obj)
311
- return JSON.parse(obj).to_s if obj.is_a?(String)
312
- return obj.to_s if obj.is_a?(Hash)
313
- return JSON.generate(obj).to_s
407
+ return JSON.parse(obj).to_json if obj.is_a?(String)
408
+ return obj.to_json if obj.is_a?(Hash)
409
+ return JSON.generate(obj).to_json
314
410
  rescue
315
411
  raise Mailgun::ParameterError, 'Provided data could not be made into JSON. Try a JSON string or Hash.', obj
316
412
  end
@@ -325,19 +421,26 @@ module Mailgun
325
421
  def parse_address(address, vars)
326
422
  return address unless vars.is_a? Hash
327
423
  fail(Mailgun::ParameterError, 'Email address not specified') unless address.is_a? String
424
+ if vars['full_name'] != nil && (vars['first'] != nil || vars['last'] != nil)
425
+ fail(Mailgun::ParameterError, 'Must specify at most one of full_name or first/last. Vars passed: #{vars}')
426
+ end
328
427
 
329
- full_name = "#{vars['first']} #{vars['last']}".strip
428
+ if vars['full_name']
429
+ full_name = vars['full_name']
430
+ elsif vars['first'] || vars['last']
431
+ full_name = "#{vars['first']} #{vars['last']}".strip
432
+ end
330
433
 
331
- return "'#{full_name}' <#{address}>" if defined?(full_name)
434
+ return "'#{full_name}' <#{address}>" if full_name
332
435
  address
333
436
  end
334
437
 
335
438
  # Private: Adds a file to the message.
336
439
  #
337
- # disposition - a Symbol of either :inline or :attachment specifying how to
338
- # to set the file/image
339
- # attachment - either a file object or string which is a path to the file
340
- # filename - optional String signifying the filename
440
+ # @param [Symbol] disposition The type of file: :attachment or :inline
441
+ # @param [String|File] attachment A file object for attaching as an attachment.
442
+ # @param [String] filename The filename you wish the attachment to be.
443
+ # @return [void]
341
444
  #
342
445
  # Returns nothing
343
446
  def add_file(disposition, filedata, filename)
@@ -348,11 +451,17 @@ module Mailgun
348
451
  'Unable to access attachment file object.'
349
452
  ) unless attachment.respond_to?(:read)
350
453
 
454
+ if attachment.respond_to?(:path) && !attachment.respond_to?(:content_type)
455
+ mime_types = MIME::Types.type_for(attachment.path)
456
+ content_type = mime_types.empty? ? 'application/octet-stream' : mime_types[0].content_type
457
+ attachment.instance_eval "def content_type; '#{content_type}'; end"
458
+ end
459
+
351
460
  unless filename.nil?
352
461
  attachment.instance_variable_set :@original_filename, filename
353
462
  attachment.instance_eval 'def original_filename; @original_filename; end'
354
463
  end
355
- complex_setter(disposition, attachment)
464
+ set_multi_complex(disposition, attachment)
356
465
  end
357
466
  end
358
467