mailjet 1.5.4 → 1.8.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 +298 -183
- data/Rakefile +23 -14
- data/lib/mailjet/configuration.rb +56 -4
- data/lib/mailjet/connection.rb +68 -33
- data/lib/mailjet/exception/errors.rb +94 -0
- data/lib/mailjet/mailer.rb +45 -16
- data/lib/mailjet/rack/endpoint.rb +2 -3
- data/lib/mailjet/resource.rb +105 -72
- data/lib/mailjet/resources/campaigndraft.rb +1 -1
- data/lib/mailjet/resources/campaigndraft_detailcontent.rb +17 -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.rb +11 -2
- data/lib/mailjet/resources/contact_getcontactslists.rb +17 -0
- data/lib/mailjet/resources/contact_pii.rb +8 -0
- data/lib/mailjet/resources/contactslist_csv.rb +8 -0
- data/lib/mailjet/resources/csvimport.rb +0 -1
- data/lib/mailjet/resources/listrecipient.rb +2 -1
- data/lib/mailjet/resources/messagehistory.rb +16 -0
- data/lib/mailjet/resources/messageinformation.rb +17 -0
- data/lib/mailjet/resources/newsletter.rb +1 -2
- data/lib/mailjet/resources/openinformation.rb +17 -0
- data/lib/mailjet/resources/retrieve_errors_csv.rb +8 -0
- data/lib/mailjet/resources/send.rb +1 -1
- data/lib/mailjet/resources/statcounters.rb +33 -0
- data/lib/mailjet/resources/statistics_linkclick.rb +12 -0
- data/lib/mailjet/resources/template_detailcontent.rb +18 -2
- data/lib/mailjet/version.rb +1 -1
- data/lib/mailjet.rb +4 -6
- metadata +25 -120
- data/lib/mailjet/api_error.rb +0 -29
- data/lib/mailjet/core_extensions/ostruct.rb +0 -9
- data/lib/mailjet/gem_extensions/rest_client.rb +0 -19
@@ -1,17 +1,69 @@
|
|
1
|
-
require 'active_support/core_ext/module/attribute_accessors'
|
2
|
-
|
3
1
|
module Mailjet
|
4
2
|
module Configuration
|
5
|
-
|
3
|
+
def self.api_key
|
4
|
+
@api_key
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.api_key=(api_key)
|
8
|
+
@api_key = api_key
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.secret_key
|
12
|
+
@secret_key
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.secret_key=(secret_key)
|
16
|
+
@secret_key = secret_key
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.default_from
|
20
|
+
@default_from
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.default_from=(default_from)
|
24
|
+
@default_from = default_from
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.api_version
|
28
|
+
@api_version
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.api_version=(api_version)
|
32
|
+
@api_version = api_version
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.sandbox_mode
|
36
|
+
@sandbox_mode
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.sandbox_mode=(sandbox_mode)
|
40
|
+
@sandbox_mode = sandbox_mode
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.end_point
|
44
|
+
@end_point
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.end_point=(end_point)
|
48
|
+
@end_point = end_point
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.perform_api_call
|
52
|
+
@perform_api_call
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.perform_api_call=(perform_api_call)
|
56
|
+
@perform_api_call = perform_api_call
|
57
|
+
end
|
6
58
|
|
7
59
|
DEFAULT = {
|
8
60
|
api_version: 'v3',
|
61
|
+
sandbox_mode: false,
|
9
62
|
end_point: 'https://api.mailjet.com',
|
10
63
|
perform_api_call: true,
|
11
64
|
}
|
12
65
|
|
13
66
|
DEFAULT.each do |param, default_value|
|
14
|
-
mattr_accessor param
|
15
67
|
self.send("#{param}=", default_value)
|
16
68
|
end
|
17
69
|
end
|
data/lib/mailjet/connection.rb
CHANGED
@@ -1,42 +1,32 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
3
|
-
require 'active_support/core_ext/module/delegation'
|
4
|
-
require 'json'
|
1
|
+
require 'faraday'
|
2
|
+
require 'yajl'
|
5
3
|
|
6
4
|
module Mailjet
|
7
5
|
class Connection
|
8
6
|
|
9
|
-
attr_accessor :adapter, :public_operations, :read_only, :perform_api_call
|
7
|
+
attr_accessor :adapter, :public_operations, :read_only, :perform_api_call, :api_key, :secret_key, :options
|
10
8
|
alias :read_only? :read_only
|
11
9
|
|
12
|
-
delegate :options, :concat_urls, :url, to: :adapter
|
13
|
-
|
14
10
|
def [](suburl, &new_block)
|
15
|
-
broken_url =
|
11
|
+
broken_url = uri.path.split("/")
|
16
12
|
if broken_url.include?("contactslist") && broken_url.include?("managemanycontacts") && broken_url.last.to_i > 0
|
17
|
-
self.class.new(
|
13
|
+
self.class.new(uri, api_key, secret_key, options)
|
18
14
|
else
|
19
|
-
self.class.new(concat_urls(
|
15
|
+
self.class.new(concat_urls(suburl), api_key, secret_key, options)
|
20
16
|
end
|
21
17
|
end
|
22
18
|
|
23
19
|
def initialize(end_point, api_key, secret_key, options = {})
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
# #Output for debugging
|
28
|
-
# RestClient.log =
|
29
|
-
# Object.new.tap do |proxy|
|
30
|
-
# def proxy.<<(message)
|
31
|
-
# Rails.logger.info message
|
32
|
-
# end
|
33
|
-
# end
|
34
|
-
# #
|
35
|
-
adapter_class = options[:adapter_class] || RestClient::Resource
|
20
|
+
self.options = options
|
21
|
+
self.api_key = api_key
|
22
|
+
self.secret_key = secret_key
|
36
23
|
self.public_operations = options[:public_operations] || []
|
37
24
|
self.read_only = options[:read_only]
|
38
|
-
|
39
|
-
|
25
|
+
self.adapter = Faraday.new(end_point, ssl: { verify: false }) do |conn|
|
26
|
+
conn.response :raise_error, include_request: true
|
27
|
+
conn.request :authorization, :basic, api_key, secret_key
|
28
|
+
conn.headers['Content-Type'] = 'application/json'
|
29
|
+
end
|
40
30
|
self.perform_api_call = options.key?(:perform_api_call) ? options[:perform_api_call] : true
|
41
31
|
end
|
42
32
|
|
@@ -56,22 +46,31 @@ module Mailjet
|
|
56
46
|
handle_api_call(:delete, additional_headers, &block)
|
57
47
|
end
|
58
48
|
|
49
|
+
def concat_urls(suburl)
|
50
|
+
self.adapter.build_url(suburl.to_s)
|
51
|
+
end
|
52
|
+
|
53
|
+
def uri
|
54
|
+
self.adapter.build_url
|
55
|
+
end
|
56
|
+
|
59
57
|
private
|
60
58
|
|
61
59
|
def handle_api_call(method, additional_headers = {}, payload = {}, &block)
|
62
|
-
formatted_payload = (additional_headers[
|
60
|
+
formatted_payload = (additional_headers["Content-Type"] == 'application/json') ? Yajl::Encoder.encode(payload) : payload
|
63
61
|
raise Mailjet::MethodNotAllowed unless method_allowed(method)
|
64
62
|
|
65
63
|
if self.perform_api_call
|
66
64
|
if [:get, :delete].include?(method)
|
67
|
-
@adapter.send(method, additional_headers, &block)
|
65
|
+
@adapter.send(method, nil, additional_headers[:params], &block)
|
68
66
|
else
|
69
|
-
@adapter.send(method, formatted_payload, additional_headers, &block)
|
67
|
+
@adapter.send(method, nil, formatted_payload, additional_headers, &block)
|
70
68
|
end
|
71
69
|
else
|
72
|
-
return {'Count' => 0, 'Data' => [mock_api_call: true], 'Total' => 0}
|
70
|
+
return Yajl::Encoder.encode({'Count' => 0, 'Data' => [mock_api_call: true], 'Total' => 0})
|
73
71
|
end
|
74
|
-
|
72
|
+
|
73
|
+
rescue Faraday::Error => e
|
75
74
|
handle_exception(e, additional_headers, formatted_payload)
|
76
75
|
end
|
77
76
|
|
@@ -81,16 +80,52 @@ module Mailjet
|
|
81
80
|
end
|
82
81
|
|
83
82
|
def handle_exception(e, additional_headers, payload = {})
|
83
|
+
return e.response_body if e.response_headers[:content_type].include?("text/plain")
|
84
|
+
|
84
85
|
params = additional_headers[:params] || {}
|
85
|
-
formatted_payload = (additional_headers[:content_type] == :json) ?
|
86
|
-
params = params.merge(formatted_payload)
|
86
|
+
formatted_payload = (additional_headers[:content_type] == :json) ? Yajl::Parser.parse(payload) : payload
|
87
|
+
params = params.merge!(formatted_payload) if formatted_payload.is_a?(Hash)
|
88
|
+
|
89
|
+
response_body = if e.response_headers[:content_type].include?("application/json")
|
90
|
+
e.response_body
|
91
|
+
else
|
92
|
+
"{}"
|
93
|
+
end
|
87
94
|
|
88
|
-
|
95
|
+
if sent_invalid_email?(e.response_body, @adapter.build_url)
|
96
|
+
return e.response_body
|
97
|
+
else
|
98
|
+
raise communication_error(e)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def communication_error(e)
|
103
|
+
if e.respond_to?(:response) && e.response
|
104
|
+
return case e.response_status
|
105
|
+
when Unauthorized::CODE
|
106
|
+
Unauthorized.new(e.message, e)
|
107
|
+
when BadRequest::CODE
|
108
|
+
BadRequest.new(e.message, e)
|
109
|
+
else
|
110
|
+
CommunicationError.new(e.message, e)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
CommunicationError.new(e.message)
|
114
|
+
end
|
115
|
+
|
116
|
+
def sent_invalid_email?(error_http_body, uri)
|
117
|
+
return false unless uri.path.include?('v3.1/send')
|
118
|
+
return unless error_http_body
|
119
|
+
|
120
|
+
parsed_body = Yajl::Parser.parse(error_http_body)
|
121
|
+
error_message = parsed_body.dig('Messages')&.first&.dig('Errors')&.first&.dig('ErrorMessage') || []
|
122
|
+
error_message.include?('is an invalid email address.')
|
123
|
+
rescue
|
124
|
+
false
|
89
125
|
end
|
90
126
|
|
91
127
|
end
|
92
128
|
|
93
129
|
class MethodNotAllowed < StandardError
|
94
|
-
|
95
130
|
end
|
96
131
|
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'yajl'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Mailjet
|
5
|
+
class Error < StandardError
|
6
|
+
attr_reader :object
|
7
|
+
|
8
|
+
def initialize(message = nil, object = nil)
|
9
|
+
super(message)
|
10
|
+
@object = object
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class ApiError < StandardError
|
15
|
+
attr_reader :code, :reason
|
16
|
+
|
17
|
+
# @param code [Integer] HTTP response status code
|
18
|
+
# @param body [String] JSON response body
|
19
|
+
# @param request [Object] any request object
|
20
|
+
# @param url [String] request URL
|
21
|
+
# @param params [Hash] request headers and parameters
|
22
|
+
def initialize(code, body, request, url, params)
|
23
|
+
@code = code
|
24
|
+
@reason = begin
|
25
|
+
resdec = JSON.parse(body)
|
26
|
+
resdec['ErrorMessage']
|
27
|
+
rescue JSON::ParserError
|
28
|
+
body
|
29
|
+
end
|
30
|
+
|
31
|
+
if request.respond_to?(:options)
|
32
|
+
request.options[:user] = '***'
|
33
|
+
request.options[:password] = '***'
|
34
|
+
end
|
35
|
+
|
36
|
+
message = "error #{code} while sending #{request.inspect} to #{url} with #{params.inspect}"
|
37
|
+
error_details = body.inspect
|
38
|
+
hint = "Please see https://dev.mailjet.com/email/reference/overview/errors/ for more informations on error numbers."
|
39
|
+
|
40
|
+
super("#{message}\n\n#{error_details}\n\n#{hint}\n\n")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class CommunicationError < Error
|
45
|
+
attr_reader :code
|
46
|
+
|
47
|
+
NOCODE = 000
|
48
|
+
|
49
|
+
def initialize(message = nil, response = nil)
|
50
|
+
@response = response
|
51
|
+
@code = if response.nil?
|
52
|
+
NOCODE
|
53
|
+
else
|
54
|
+
response.response_status
|
55
|
+
end
|
56
|
+
|
57
|
+
api_message = begin
|
58
|
+
Yajl::Parser.parse(response.response_body)['ErrorMessage']
|
59
|
+
rescue Yajl::ParseError
|
60
|
+
response.response_body
|
61
|
+
rescue NoMethodError
|
62
|
+
"Unknown API error"
|
63
|
+
rescue
|
64
|
+
'Unknown API error'
|
65
|
+
end
|
66
|
+
|
67
|
+
message ||= ''
|
68
|
+
api_message ||= ''
|
69
|
+
message = message + ': ' + api_message
|
70
|
+
|
71
|
+
super(message, response)
|
72
|
+
rescue NoMethodError, JSON::ParserError
|
73
|
+
@code = NOCODE
|
74
|
+
super(message, response)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class Unauthorized < CommunicationError
|
79
|
+
CODE = 401
|
80
|
+
|
81
|
+
def initialize(error_message, response)
|
82
|
+
error_message = error_message + ' - Invalid Domain or API key'
|
83
|
+
super(error_message, response)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class BadRequest < CommunicationError
|
88
|
+
CODE = 400
|
89
|
+
|
90
|
+
def initialize(error_message, response)
|
91
|
+
super(error_message, response)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/mailjet/mailer.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
require 'action_mailer'
|
2
2
|
require 'mail'
|
3
3
|
require 'base64'
|
4
|
-
require '
|
4
|
+
require 'yajl'
|
5
|
+
|
5
6
|
|
6
7
|
# Mailjet::Mailer enables to send a Mail::Message via Mailjet SMTP relay servers
|
7
8
|
# User is the API key, and password the API secret
|
@@ -15,7 +16,7 @@ class Mailjet::Mailer < ::Mail::SMTP
|
|
15
16
|
user_name: options.delete(:api_key) || Mailjet.config.api_key,
|
16
17
|
password: options.delete(:secret_key) || Mailjet.config.secret_key,
|
17
18
|
enable_starttls_auto: true
|
18
|
-
}.merge(options))
|
19
|
+
}.merge!(options))
|
19
20
|
end
|
20
21
|
end
|
21
22
|
|
@@ -42,6 +43,13 @@ class Mailjet::APIMailer
|
|
42
43
|
|
43
44
|
CONNECTION_PERMITTED_OPTIONS = [:api_key, :secret_key]
|
44
45
|
|
46
|
+
HEADER_BLACKLIST = [
|
47
|
+
'from', 'sender', 'subject', 'to', 'cc', 'bcc', 'return-path', 'delivered-to', 'dkim-signature',
|
48
|
+
'domainkey-status', 'received-spf', 'authentication-results', 'received', 'user-agent', 'x-mailer',
|
49
|
+
'x-feedback-id', 'list-id', 'date', 'x-csa-complaints', 'message-id', 'reply-to', 'content-type',
|
50
|
+
'mime-version', 'content-transfer-encoding'
|
51
|
+
]
|
52
|
+
|
45
53
|
def initialize(opts = {})
|
46
54
|
options = HashWithIndifferentAccess.new(opts)
|
47
55
|
|
@@ -67,7 +75,7 @@ class Mailjet::APIMailer
|
|
67
75
|
version = options[:version] || Mailjet.config.api_version
|
68
76
|
|
69
77
|
if (version == 'v3.1')
|
70
|
-
Mailjet::Send.create({ :Messages => [setContentV3_1(mail)] }, options)
|
78
|
+
Mailjet::Send.create({ :Messages => [setContentV3_1(mail)], SandboxMode: Mailjet.config.sandbox_mode }, options)
|
71
79
|
else
|
72
80
|
Mailjet::Send.create(setContentV3_0(mail), options)
|
73
81
|
end
|
@@ -110,7 +118,7 @@ class Mailjet::APIMailer
|
|
110
118
|
if mail.header && mail.header.fields.any?
|
111
119
|
content[:Headers] = {}
|
112
120
|
mail.header.fields.each do |header|
|
113
|
-
if header.name.start_with?('X-') && !header.name.start_with?('X-
|
121
|
+
if !header.name.start_with?('X-MJ') && !header.name.start_with?('X-Mailjet') && !HEADER_BLACKLIST.include?(header.name.downcase)
|
114
122
|
content[:Headers][header.name] = header.value
|
115
123
|
end
|
116
124
|
end
|
@@ -119,8 +127,8 @@ class Mailjet::APIMailer
|
|
119
127
|
# ReplyTo property was added in v3.1
|
120
128
|
# Passing it as an header if mail.reply_to
|
121
129
|
|
122
|
-
if mail
|
123
|
-
if mail
|
130
|
+
if mail[:reply_to]
|
131
|
+
if mail[:reply_to].respond_to?(:display_names) && mail[:reply_to].display_names.first
|
124
132
|
content[:ReplyTo] = {:Email=> mail[:reply_to].addresses.first, :Name=> mail[:reply_to].display_names.first}
|
125
133
|
else
|
126
134
|
content[:ReplyTo] = {:Email=> mail[:reply_to].addresses.first}
|
@@ -163,8 +171,13 @@ class Mailjet::APIMailer
|
|
163
171
|
ccs =[{:Email=>mail[:cc].address.first}]
|
164
172
|
end
|
165
173
|
else
|
174
|
+
ccs = []
|
166
175
|
mail[:cc].each do |cc|
|
167
|
-
|
176
|
+
if cc.display_name
|
177
|
+
ccs << {:Email=> cc.address, :Name=>cc.display_name}
|
178
|
+
else
|
179
|
+
ccs << {:Email=> cc.address}
|
180
|
+
end
|
168
181
|
end
|
169
182
|
end
|
170
183
|
end
|
@@ -177,7 +190,8 @@ class Mailjet::APIMailer
|
|
177
190
|
payload[:Bcc] = [{:Email=>mail[:bcc].address.first}]
|
178
191
|
end
|
179
192
|
else
|
180
|
-
|
193
|
+
bccs = []
|
194
|
+
mail[:bcc].each do |bcc|
|
181
195
|
if bcc.display_name
|
182
196
|
bccs << {:Email=> bcc.address, :Name=>bcc.display_name}
|
183
197
|
else
|
@@ -189,16 +203,33 @@ class Mailjet::APIMailer
|
|
189
203
|
|
190
204
|
payload = {
|
191
205
|
:To=> to,
|
192
|
-
}.merge(content)
|
193
|
-
.merge(base_from)
|
194
|
-
.merge(@delivery_method_options_v3_1)
|
206
|
+
}.merge!(content, base_from, @delivery_method_options_v3_1)
|
195
207
|
|
196
208
|
payload[:Subject] = mail.subject if !mail.subject.blank?
|
197
209
|
payload[:Sender] = mail[:sender] if !mail[:sender].blank?
|
198
210
|
payload[:Cc] = ccs if mail[:cc]
|
199
211
|
payload[:Bcc] = bccs if mail[:bcc]
|
200
212
|
|
201
|
-
payload
|
213
|
+
decode_emails_V3_1!(payload)
|
214
|
+
end
|
215
|
+
|
216
|
+
def decode_emails_V3_1!(payload)
|
217
|
+
# ActionMailer may have handed us encoded email
|
218
|
+
# addresses, mailjet will reject. Therefore we
|
219
|
+
# walk through the payload to decode them back.
|
220
|
+
payload.each do |key, value|
|
221
|
+
if key == :Email
|
222
|
+
payload[key] = Mail::Encodings.value_decode(value)
|
223
|
+
elsif value.is_a?(Hash)
|
224
|
+
decode_emails_V3_1! value
|
225
|
+
elsif value.is_a?(Array)
|
226
|
+
value.each do |item|
|
227
|
+
if item.is_a?(Hash)
|
228
|
+
decode_emails_V3_1! item
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
202
233
|
end
|
203
234
|
|
204
235
|
def setContentV3_0(mail)
|
@@ -262,10 +293,8 @@ class Mailjet::APIMailer
|
|
262
293
|
payload[:bcc] = mail[:bcc].formatted.join(', ') if mail[:bcc]
|
263
294
|
|
264
295
|
# Send the final payload to Mailjet Send API
|
265
|
-
payload.merge(content)
|
266
|
-
|
267
|
-
.merge(@delivery_method_options_v3_0)
|
268
|
-
end
|
296
|
+
payload.merge!(content, base_from, @delivery_method_options_v3_0)
|
297
|
+
end
|
269
298
|
end
|
270
299
|
|
271
300
|
ActionMailer::Base.add_delivery_method :mailjet_api, Mailjet::APIMailer
|
@@ -1,6 +1,5 @@
|
|
1
|
-
require 'active_support'
|
2
1
|
require 'rack/request'
|
3
|
-
|
2
|
+
require 'yajl'
|
4
3
|
|
5
4
|
module Mailjet
|
6
5
|
module Rack
|
@@ -13,7 +12,7 @@ module Mailjet
|
|
13
12
|
|
14
13
|
def call(env)
|
15
14
|
if env['PATH_INFO'] == @path && (content = env['rack.input'].read)
|
16
|
-
@block.call(
|
15
|
+
@block.call(Yajl::Parser.parse(content))
|
17
16
|
[200, { 'Content-Type' => 'text/html', 'Content-Length' => '0' }, []]
|
18
17
|
else
|
19
18
|
@app.call(env)
|