mailjet 1.5.4 → 1.6.0

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