mailgun-ruby 1.1.0 → 1.2.5

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