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