mailjet 1.5.4 → 1.6.0

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.
data/Rakefile CHANGED
@@ -1,17 +1,22 @@
1
- #!/usr/bin/env rake
2
- require 'rake/testtask'
3
- require 'bundler'
4
- Bundler::GemHelper.install_tasks
1
+ require "rspec/core/rake_task"
5
2
 
6
- $:.push File.expand_path("../lib", __FILE__)
7
-
8
- Rake::TestTask.new(:spec) do |t|
9
- t.libs << 'lib'
10
- t.libs << 'spec'
11
- # t.pattern = 'spec/**/*_spec.rb'
12
- t.verbose = true
13
- t.test_files = Dir['spec/**/*_spec.rb']
3
+ RSpec::Core::RakeTask.new(:spec) do |t|
4
+ t.pattern = [
5
+ "spec/mailjet/api_error_spec.rb",
6
+ "spec/mailjet/apikey_spec.rb",
7
+ "spec/mailjet/mailer_spec.rb",
8
+ "spec/mailjet/resource_spec.rb",
9
+ "spec/configuration_spec.rb",
10
+ "spec/mailjet_spec.rb",
11
+ "spec/resources/contact_spec.rb",
12
+ "spec/resources/contactmetadata_spec.rb",
13
+ "spec/resources/messagehistory_spec.rb",
14
+ "spec/resources/getcontactslists_spec.rb",
15
+ "spec/resources/template_detailcontent_spec.rb",
16
+ "spec/resources/integration_spec.rb",
17
+ "spec/resources/newsletter_spec.rb",
18
+ "spec/resources/statcounters_spec.rb",
19
+ ]
14
20
  end
15
21
 
16
-
17
- task :default => :spec
22
+ task default: [:spec]
@@ -1,29 +1,28 @@
1
- # encoding: utf-8
2
- require 'active_support'
1
+ require "json"
3
2
 
4
3
  module Mailjet
5
4
  class ApiError < StandardError
5
+ attr_reader :code, :reason
6
6
 
7
- attr_accessor :code, :reason
7
+ # @param code [Integer] HTTP response status code
8
+ # @param body [String] JSON response body
9
+ # @param request [Object] any request object
10
+ # @param url [String] request URL
11
+ # @param params [Hash] request headers and parameters
12
+ def initialize(code, body, request, url, params)
13
+ @code = code
14
+ @reason = begin
15
+ resdec = JSON.parse(body)
16
+ resdec['ErrorMessage']
17
+ rescue JSON::ParserError
18
+ body
19
+ end
8
20
 
21
+ message = "error #{code} while sending #{request.inspect} to #{url} with #{params.inspect}"
22
+ error_details = body.inspect
23
+ hint = "Please see https://dev.mailjet.com/guides/#status-codes for more informations on error numbers."
9
24
 
10
- def initialize(code, res, request, request_path, params)
11
- self.code = code
12
- self.reason = ""
13
- unless res.blank?
14
- resdec = ActiveSupport::JSON.decode(res)
15
- self.reason = resdec['ErrorMessage']
16
- end
17
- # code is ugly, output is pretty
18
- super("error #{code} while sending #{request.inspect} to #{request_path} with #{params.inspect}\n\n" +
19
- if res['errors'].present?
20
- [(res['errors'] || [])].flatten.map do |param, text|
21
- [param, text].map(&:to_s).reject(&:blank?).join(': ')
22
- end.join("\n")
23
- else
24
- res.inspect
25
- end + "\n\nPlease see https://dev.mailjet.com/guides/#status-codes for more informations on error numbers.\n\n"
26
- )
25
+ super("#{message}\n\n#{error_details}\n\n#{hint}\n\n")
27
26
  end
28
27
  end
29
28
  end
@@ -6,6 +6,7 @@ module Mailjet
6
6
 
7
7
  DEFAULT = {
8
8
  api_version: 'v3',
9
+ sandbox_mode: false,
9
10
  end_point: 'https://api.mailjet.com',
10
11
  perform_api_call: true,
11
12
  }
@@ -6,7 +6,7 @@ require 'json'
6
6
  module Mailjet
7
7
  class Connection
8
8
 
9
- attr_accessor :adapter, :public_operations, :read_only, :perform_api_call
9
+ attr_accessor :adapter, :public_operations, :read_only, :perform_api_call, :read_timeout, :open_timeout
10
10
  alias :read_only? :read_only
11
11
 
12
12
  delegate :options, :concat_urls, :url, to: :adapter
@@ -35,8 +35,10 @@ module Mailjet
35
35
  adapter_class = options[:adapter_class] || RestClient::Resource
36
36
  self.public_operations = options[:public_operations] || []
37
37
  self.read_only = options[:read_only]
38
+ self.read_timeout = options[:read_timeout]
39
+ self.open_timeout = options[:open_timeout]
38
40
  # self.adapter = adapter_class.new(end_point, options.merge(user: api_key, password: secret_key, :verify_ssl => false, content_type: 'application/json'))
39
- self.adapter = adapter_class.new(end_point, options.merge(user: api_key, password: secret_key, content_type: 'application/json'))
41
+ self.adapter = adapter_class.new(end_point, options.merge(user: api_key, password: secret_key, content_type: 'application/json', read_timeout: self.read_timeout, open_timeout: self.open_timeout))
40
42
  self.perform_api_call = options.key?(:perform_api_call) ? options[:perform_api_call] : true
41
43
  end
42
44
 
@@ -85,7 +87,13 @@ module Mailjet
85
87
  formatted_payload = (additional_headers[:content_type] == :json) ? JSON.parse(payload) : payload
86
88
  params = params.merge(formatted_payload)
87
89
 
88
- raise Mailjet::ApiError.new(e.http_code, e.http_body, @adapter, @adapter.url, params)
90
+ http_body = if e.http_headers[:content_type] == "application/json"
91
+ e.http_body
92
+ else
93
+ "{}"
94
+ end
95
+
96
+ raise Mailjet::ApiError.new(e.http_code, http_body, @adapter, @adapter.url, params)
89
97
  end
90
98
 
91
99
  end
@@ -42,6 +42,13 @@ class Mailjet::APIMailer
42
42
 
43
43
  CONNECTION_PERMITTED_OPTIONS = [:api_key, :secret_key]
44
44
 
45
+ HEADER_BLACKLIST = [
46
+ 'from', 'sender', 'subject', 'to', 'cc', 'bcc', 'return-path', 'delivered-to', 'dkim-signature',
47
+ 'domainkey-status', 'received-spf', 'authentication-results', 'received', 'user-agent', 'x-mailer',
48
+ 'x-feedback-id', 'list-id', 'date', 'x-csa-complaints', 'message-id', 'reply-to', 'content-type',
49
+ 'mime-version', 'content-transfer-encoding'
50
+ ]
51
+
45
52
  def initialize(opts = {})
46
53
  options = HashWithIndifferentAccess.new(opts)
47
54
 
@@ -67,7 +74,7 @@ class Mailjet::APIMailer
67
74
  version = options[:version] || Mailjet.config.api_version
68
75
 
69
76
  if (version == 'v3.1')
70
- Mailjet::Send.create({ :Messages => [setContentV3_1(mail)] }, options)
77
+ Mailjet::Send.create({ :Messages => [setContentV3_1(mail)], SandboxMode: Mailjet.config.sandbox_mode }, options)
71
78
  else
72
79
  Mailjet::Send.create(setContentV3_0(mail), options)
73
80
  end
@@ -110,7 +117,7 @@ class Mailjet::APIMailer
110
117
  if mail.header && mail.header.fields.any?
111
118
  content[:Headers] = {}
112
119
  mail.header.fields.each do |header|
113
- if header.name.start_with?('X-') && !header.name.start_with?('X-MJ') && !header.name.start_with?('X-Mailjet')
120
+ if !header.name.start_with?('X-MJ') && !header.name.start_with?('X-Mailjet') && !HEADER_BLACKLIST.include?(header.name.downcase)
114
121
  content[:Headers][header.name] = header.value
115
122
  end
116
123
  end
@@ -119,8 +126,8 @@ class Mailjet::APIMailer
119
126
  # ReplyTo property was added in v3.1
120
127
  # Passing it as an header if mail.reply_to
121
128
 
122
- if mail.reply_to
123
- if mail.reply_to.respond_to?(:display_names) && mail.reply_to.display_names.first
129
+ if mail[:reply_to]
130
+ if mail[:reply_to].respond_to?(:display_names) && mail[:reply_to].display_names.first
124
131
  content[:ReplyTo] = {:Email=> mail[:reply_to].addresses.first, :Name=> mail[:reply_to].display_names.first}
125
132
  else
126
133
  content[:ReplyTo] = {:Email=> mail[:reply_to].addresses.first}
@@ -163,8 +170,13 @@ class Mailjet::APIMailer
163
170
  ccs =[{:Email=>mail[:cc].address.first}]
164
171
  end
165
172
  else
173
+ ccs = []
166
174
  mail[:cc].each do |cc|
167
- ccs << {:Email=> cc.address, :Name=>cc.display_name}
175
+ if cc.display_name
176
+ ccs << {:Email=> cc.address, :Name=>cc.display_name}
177
+ else
178
+ ccs << {:Email=> cc.address}
179
+ end
168
180
  end
169
181
  end
170
182
  end
@@ -177,7 +189,8 @@ class Mailjet::APIMailer
177
189
  payload[:Bcc] = [{:Email=>mail[:bcc].address.first}]
178
190
  end
179
191
  else
180
- mail[:bcc].formatted.each do |bcc|
192
+ bccs = []
193
+ mail[:bcc].each do |bcc|
181
194
  if bcc.display_name
182
195
  bccs << {:Email=> bcc.address, :Name=>bcc.display_name}
183
196
  else
@@ -198,7 +211,26 @@ class Mailjet::APIMailer
198
211
  payload[:Cc] = ccs if mail[:cc]
199
212
  payload[:Bcc] = bccs if mail[:bcc]
200
213
 
201
- payload
214
+ decode_emails_V3_1!(payload)
215
+ end
216
+
217
+ def decode_emails_V3_1!(payload)
218
+ # ActionMailer may have handed us encoded email
219
+ # addresses, mailjet will reject. Therefore we
220
+ # walk through the payload to decode them back.
221
+ payload.each do |key, value|
222
+ if key == :Email
223
+ payload[key] = Mail::Encodings.value_decode(value)
224
+ elsif value.is_a?(Hash)
225
+ decode_emails_V3_1! value
226
+ elsif value.is_a?(Array)
227
+ value.each do |item|
228
+ if item.is_a?(Hash)
229
+ decode_emails_V3_1! item
230
+ end
231
+ end
232
+ end
233
+ end
202
234
  end
203
235
 
204
236
  def setContentV3_0(mail)
@@ -265,7 +297,7 @@ class Mailjet::APIMailer
265
297
  payload.merge(content)
266
298
  .merge(base_from)
267
299
  .merge(@delivery_method_options_v3_0)
268
- end
300
+ end
269
301
  end
270
302
 
271
303
  ActionMailer::Base.add_delivery_method :mailjet_api, Mailjet::APIMailer
@@ -21,7 +21,7 @@ module Mailjet
21
21
  extend ActiveSupport::Concern
22
22
 
23
23
  # define here available options for filtering
24
- OPTIONS = [:version, :url, :perform_api_call, :api_key, :secret_key]
24
+ OPTIONS = [:version, :url, :perform_api_call, :api_key, :secret_key, :read_timeout, :open_timeout]
25
25
 
26
26
  NON_JSON_URLS = ['v3/send/message'] # urls that don't accept JSON input
27
27
 
@@ -40,7 +40,9 @@ module Mailjet
40
40
  options[:secret_key] || Mailjet.config.secret_key,
41
41
  public_operations: public_operations,
42
42
  read_only: read_only,
43
- perform_api_call: options[:perform_api_call])
43
+ perform_api_call: options[:perform_api_call],
44
+ open_timeout: options[:open_timeout],
45
+ read_timeout: options[:read_timeout])
44
46
  end
45
47
 
46
48
  def self.default_headers
@@ -74,11 +76,17 @@ module Mailjet
74
76
  end
75
77
 
76
78
  def find(id, job_id = nil, options = {})
79
+ normalized_id = if id.is_a? String
80
+ URI.encode_www_form_component(id)
81
+ else
82
+ id
83
+ end
84
+
77
85
  # if action method, ammend url to appropriate id
78
86
  opts = define_options(options)
79
- self.resource_path = create_action_resource_path(id, job_id) if self.action
87
+ self.resource_path = create_action_resource_path(normalized_id, job_id) if self.action
80
88
  #
81
- attributes = parse_api_json(connection(opts)[id].get(default_headers)).first
89
+ attributes = parse_api_json(connection(opts)[normalized_id].get(default_headers)).first
82
90
  instanciate_from_api(attributes)
83
91
 
84
92
  rescue Mailjet::ApiError => e
@@ -92,7 +100,7 @@ module Mailjet
92
100
  def create(attributes = {}, options = {})
93
101
  # if action method, ammend url to appropriate id
94
102
  opts = define_options(options)
95
- self.resource_path = create_action_resource_path(attributes[:id]) if self.action
103
+ self.resource_path = create_action_resource_path(attributes[:id]) if (self.action and attributes[:id])
96
104
  attributes.tap { |hs| hs.delete(:id) }
97
105
 
98
106
  if Mailjet.config.default_from and self.resource_path == 'send/'
@@ -106,7 +114,7 @@ module Mailjet
106
114
 
107
115
  self.new(attributes).tap do |resource|
108
116
  resource.save!(opts)
109
- resource.persisted = true
117
+ resource.attributes[:persisted] = true
110
118
  end
111
119
 
112
120
  end
@@ -138,17 +146,17 @@ module Mailjet
138
146
  end
139
147
 
140
148
  def create_action_resource_path(id, job_id = nil)
141
- url_elements = self.resource_path.split("/")
142
- url_elements.delete_at(url_elements.length-1) if url_elements.last.to_i > 0 #if there is a trailing number for the job id from last call, delete it
149
+ url_elements = self.resource_path.split("/")
150
+ url_elements.delete_at(url_elements.length-1) if url_elements.last.to_i > 0 #if there is a trailing number for the job id from last call, delete it
143
151
 
144
- if !(url_elements[1] == "contacts" && self.action == "managemanycontacts")
145
- url_elements[2] = id.to_s
146
- end
152
+ if !(url_elements[1] == "contacts" && self.action == "managemanycontacts")
153
+ url_elements[2] = id.to_s
154
+ end
147
155
 
148
- url_elements << job_id.to_s if job_id #if job_id exists, amend it to end of the URI
149
- url = url_elements.join("/")
156
+ url_elements << job_id.to_s if job_id #if job_id exists, amend it to end of the URI
157
+ url = url_elements.join("/")
150
158
 
151
- return url
159
+ return url
152
160
  end
153
161
 
154
162
 
@@ -292,7 +300,7 @@ module Mailjet
292
300
  save(opts)
293
301
  end
294
302
 
295
- def delete(call)
303
+ def delete
296
304
  self.class.delete(id)
297
305
  end
298
306
 
@@ -309,7 +317,7 @@ module Mailjet
309
317
  def formatted_payload
310
318
  payload = attributes.reject { |k,v| v.blank? }
311
319
  if persisted?
312
- payload = payload.slice(*resourceprop)
320
+ payload = payload.slice(*resourceprop.map(&:to_s))
313
321
  end
314
322
  payload = camelcase_keys(payload)
315
323
  payload.tap { |hs| hs.delete("Persisted") }
@@ -337,20 +345,16 @@ module Mailjet
337
345
 
338
346
  def method_missing(method_symbol, *arguments) #:nodoc:
339
347
  method_name = method_symbol.to_s
340
- if method_name =~ /(=|\?)$/
341
- case $1
342
- when "="
343
- attributes[$`] = arguments.first
344
- when "?"
345
- attributes[$`]
346
- end
347
- else
348
- return attributes[method_name] if attributes.include?(method_name)
349
- # not set right now but we know about it
350
- # return nil if known_attributes.include?(method_name)
351
- super
348
+ if method_name.end_with?("=")
349
+ attributes[method_name.chop] = arguments.first
350
+ return
352
351
  end
353
- end
354
352
 
353
+ if attributes.include?(method_name)
354
+ return attributes[method_name]
355
+ end
356
+
357
+ super
358
+ end
355
359
  end
356
360
  end
@@ -1,7 +1,7 @@
1
1
  module Mailjet
2
2
  class Campaigndraft
3
3
  include Mailjet::Resource
4
- self.resource_path = 'v3/REST/campaigndraft'
4
+ self.resource_path = 'REST/campaigndraft'
5
5
  self.public_operations = [:get, :put, :post]
6
6
  self.filters = [:campaign, :contacts_list, :delivered_at, :edit_mode, :is_archived, :is_campaign, :is_deleted, :is_handled, :is_starred, :modified, :news_letter_template, :segmentation, :status, :subject, :template]
7
7
  self.resourceprop = [:campaign, :contacts_list, :created_at, :delivered_at, :edit_mode, :id, :is_starred, :is_text_part_included, :locale, :modified_at, :preset, :reply_email, :segmentation, :sender, :sender_email, :sender_name, :status, :subject, :template_id, :title, :url, :used, 'CampaignID', 'CampaignALT', 'ContactsListID', 'ContactsListALT', 'SegmentationID', 'SegmentationALT', :contacts_list_id,'TemplateID']
@@ -2,7 +2,7 @@ module Mailjet
2
2
  class Campaigndraft_detailcontent
3
3
  include Mailjet::Resource
4
4
  self.action = "detailcontent"
5
- self.resource_path = "v3/REST/campaigndraft/id/#{self.action}"
5
+ self.resource_path = "REST/campaigndraft/id/#{self.action}"
6
6
  self.public_operations = [:get, :post]
7
7
  self.filters = []
8
8
  self.resourceprop = [:text_part, :html_part, :headers, :mjml_content]
@@ -2,7 +2,7 @@ module Mailjet
2
2
  class Campaigndraft_schedule
3
3
  include Mailjet::Resource
4
4
  self.action = "schedule"
5
- self.resource_path = "v3/REST/campaigndraft/id/#{self.action}"
5
+ self.resource_path = "REST/campaigndraft/id/#{self.action}"
6
6
  self.public_operations = [:post, :delete , :get]
7
7
  self.filters = []
8
8
  self.resourceprop = [:date]
@@ -2,7 +2,7 @@ module Mailjet
2
2
  class Campaigndraft_send
3
3
  include Mailjet::Resource
4
4
  self.action = "send"
5
- self.resource_path = "v3/REST/campaigndraft/id/#{self.action}"
5
+ self.resource_path = "REST/campaigndraft/id/#{self.action}"
6
6
  self.public_operations = [:post]
7
7
  self.filters = []
8
8
  self.resourceprop = []
@@ -2,7 +2,7 @@ module Mailjet
2
2
  class Campaigndraft_status
3
3
  include Mailjet::Resource
4
4
  self.action = 'status'
5
- self.resource_path = "v3/REST/campaigndraft/id/#{self.action}"
5
+ self.resource_path = "REST/campaigndraft/id/#{self.action}"
6
6
  self.public_operations = [:get]
7
7
  self.filters = []
8
8
  self.resourceprop = []
@@ -2,7 +2,7 @@ module Mailjet
2
2
  class Campaigndraft_test
3
3
  include Mailjet::Resource
4
4
  self.action = "test"
5
- self.resource_path = "v3/REST/campaigndraft/id/#{self.action}"
5
+ self.resource_path = "REST/campaigndraft/id/#{self.action}"
6
6
  self.public_operations = [:post]
7
7
  self.filters = []
8
8
  self.resourceprop = [:email, :name]
@@ -6,5 +6,22 @@ module Mailjet
6
6
  self.public_operations = [:get]
7
7
  self.filters = []
8
8
  self.resourceprop = []
9
+
10
+ def self.find(id, job_id = nil, options = {})
11
+ opts = define_options(options)
12
+ self.resource_path = create_action_resource_path(id, job_id) if self.action
13
+
14
+ raw_data = parse_api_json(connection(opts).get(default_headers))
15
+
16
+ raw_data.map do |entity|
17
+ instanciate_from_api(entity)
18
+ end
19
+ rescue Mailjet::ApiError => e
20
+ if e.code == 404
21
+ nil
22
+ else
23
+ raise e
24
+ end
25
+ end
9
26
  end
10
27
  end
@@ -8,5 +8,21 @@ module Mailjet
8
8
 
9
9
  self.read_only = true
10
10
 
11
+ def self.find(id, job_id = nil, options = {})
12
+ opts = define_options(options)
13
+ self.resource_path = create_action_resource_path(id, job_id) if self.action
14
+
15
+ raw_data = parse_api_json(connection(opts)[id].get(default_headers))
16
+
17
+ raw_data.map do |entity|
18
+ instanciate_from_api(entity)
19
+ end
20
+ rescue Mailjet::ApiError => e
21
+ if e.code == 404
22
+ nil
23
+ else
24
+ raise e
25
+ end
26
+ end
11
27
  end
12
28
  end