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.
- checksums.yaml +5 -5
- data/README.md +293 -189
- data/Rakefile +19 -14
- data/lib/mailjet/api_error.rb +19 -20
- data/lib/mailjet/configuration.rb +1 -0
- data/lib/mailjet/connection.rb +11 -3
- data/lib/mailjet/mailer.rb +40 -8
- data/lib/mailjet/resource.rb +33 -29
- data/lib/mailjet/resources/campaigndraft.rb +1 -1
- data/lib/mailjet/resources/campaigndraft_detailcontent.rb +1 -1
- data/lib/mailjet/resources/campaigndraft_schedule.rb +1 -1
- data/lib/mailjet/resources/campaigndraft_send.rb +1 -1
- data/lib/mailjet/resources/campaigndraft_status.rb +1 -1
- data/lib/mailjet/resources/campaigndraft_test.rb +1 -1
- data/lib/mailjet/resources/contact_getcontactslists.rb +17 -0
- data/lib/mailjet/resources/messagehistory.rb +16 -0
- data/lib/mailjet/resources/newsletter.rb +1 -2
- data/lib/mailjet/resources/statcounters.rb +33 -0
- data/lib/mailjet/resources/template_detailcontent.rb +18 -2
- data/lib/mailjet/version.rb +1 -1
- metadata +10 -121
data/Rakefile
CHANGED
@@ -1,17 +1,22 @@
|
|
1
|
-
|
2
|
-
require 'rake/testtask'
|
3
|
-
require 'bundler'
|
4
|
-
Bundler::GemHelper.install_tasks
|
1
|
+
require "rspec/core/rake_task"
|
5
2
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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]
|
data/lib/mailjet/api_error.rb
CHANGED
@@ -1,29 +1,28 @@
|
|
1
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/mailjet/connection.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/mailjet/mailer.rb
CHANGED
@@ -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-
|
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
|
123
|
-
if mail
|
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
|
-
|
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
|
-
|
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
|
-
|
300
|
+
end
|
269
301
|
end
|
270
302
|
|
271
303
|
ActionMailer::Base.add_delivery_method :mailjet_api, Mailjet::APIMailer
|
data/lib/mailjet/resource.rb
CHANGED
@@ -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(
|
87
|
+
self.resource_path = create_action_resource_path(normalized_id, job_id) if self.action
|
80
88
|
#
|
81
|
-
attributes = parse_api_json(connection(opts)[
|
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
|
-
|
142
|
-
|
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
|
-
|
145
|
-
|
146
|
-
|
152
|
+
if !(url_elements[1] == "contacts" && self.action == "managemanycontacts")
|
153
|
+
url_elements[2] = id.to_s
|
154
|
+
end
|
147
155
|
|
148
|
-
|
149
|
-
|
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
|
-
|
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
|
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
|
-
|
341
|
-
|
342
|
-
|
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 = '
|
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 = "
|
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 = "
|
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 = "
|
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 = "
|
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 = "
|
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
|