paubox 0.2.3 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +13 -12
- data/.rspec +2 -2
- data/.travis.yml +5 -5
- data/Gemfile +6 -4
- data/LICENSE.txt +12 -12
- data/README.md +230 -171
- data/Rakefile +8 -6
- data/bin/console +15 -14
- data/bin/setup +8 -8
- data/lib/mail/paubox.rb +21 -19
- data/lib/paubox.rb +24 -22
- data/lib/paubox/client.rb +85 -81
- data/lib/paubox/email_disposition.rb +62 -57
- data/lib/paubox/format_helper.rb +60 -55
- data/lib/paubox/mail_to_message.rb +94 -70
- data/lib/paubox/message.rb +98 -73
- data/lib/paubox/version.rb +5 -3
- data/lib/paubox_ruby.rb +3 -1
- data/paubox_ruby.gemspec +34 -32
- metadata +19 -20
data/Rakefile
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
7
|
+
|
8
|
+
task default: :spec
|
data/bin/console
CHANGED
@@ -1,14 +1,15 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
|
4
|
-
require
|
5
|
-
|
6
|
-
|
7
|
-
#
|
8
|
-
|
9
|
-
|
10
|
-
#
|
11
|
-
#
|
12
|
-
|
13
|
-
|
14
|
-
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'paubox_ruby'
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require 'irb'
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
#!/usr/bin/env bash
|
2
|
-
set -euo pipefail
|
3
|
-
IFS=$'\n\t'
|
4
|
-
set -vx
|
5
|
-
|
6
|
-
bundle install
|
7
|
-
|
8
|
-
# Do any other automated setup that you need to do here
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
set -euo pipefail
|
3
|
+
IFS=$'\n\t'
|
4
|
+
set -vx
|
5
|
+
|
6
|
+
bundle install
|
7
|
+
|
8
|
+
# Do any other automated setup that you need to do here
|
data/lib/mail/paubox.rb
CHANGED
@@ -1,19 +1,21 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mail
|
4
|
+
class Paubox
|
5
|
+
attr_accessor :settings
|
6
|
+
|
7
|
+
def initialize(settings)
|
8
|
+
@settings = settings
|
9
|
+
end
|
10
|
+
|
11
|
+
def deliver!(mail)
|
12
|
+
client = ::Paubox::Client.new(settings)
|
13
|
+
response = client.send_mail(mail)
|
14
|
+
puts response
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Message
|
19
|
+
attr_accessor :source_tracking_id, :status, :allow_non_tls, :force_secure_notification
|
20
|
+
end
|
21
|
+
end
|
data/lib/paubox.rb
CHANGED
@@ -1,22 +1,24 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require 'paubox/
|
4
|
-
require 'paubox/
|
5
|
-
require 'paubox/
|
6
|
-
require 'paubox/
|
7
|
-
require '
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'paubox/version'
|
4
|
+
require 'paubox/client'
|
5
|
+
require 'paubox/format_helper'
|
6
|
+
require 'paubox/mail_to_message'
|
7
|
+
require 'paubox/message'
|
8
|
+
require 'paubox/email_disposition'
|
9
|
+
require 'mail/paubox'
|
10
|
+
|
11
|
+
module Paubox
|
12
|
+
class << self
|
13
|
+
attr_accessor :configuration
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.configure
|
17
|
+
self.configuration ||= Configuration.new
|
18
|
+
yield(configuration)
|
19
|
+
end
|
20
|
+
|
21
|
+
class Configuration
|
22
|
+
attr_accessor :api_key, :api_user
|
23
|
+
end
|
24
|
+
end
|
data/lib/paubox/client.rb
CHANGED
@@ -1,81 +1,85 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
@
|
13
|
-
@
|
14
|
-
@
|
15
|
-
@
|
16
|
-
@
|
17
|
-
@
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
end
|
60
|
-
|
61
|
-
def
|
62
|
-
"#{
|
63
|
-
end
|
64
|
-
|
65
|
-
def
|
66
|
-
{
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Paubox
|
4
|
+
# Client sends API requests to Paubox API
|
5
|
+
class Client
|
6
|
+
require 'rest-client'
|
7
|
+
require 'ostruct'
|
8
|
+
attr_reader :api_key, :api_user, :api_host, :api_protocol, :api_version
|
9
|
+
|
10
|
+
def initialize(args = {})
|
11
|
+
args = defaults.merge(args)
|
12
|
+
@api_key = args[:api_key]
|
13
|
+
@api_user = args[:api_user]
|
14
|
+
@api_host = args[:api_host]
|
15
|
+
@api_protocol = args[:api_protocol]
|
16
|
+
@api_version = args[:api_version]
|
17
|
+
@test_mode = args[:test_mode]
|
18
|
+
@api_base_endpoint = api_base_endpoint
|
19
|
+
end
|
20
|
+
|
21
|
+
def api_status
|
22
|
+
url = request_endpoint('status')
|
23
|
+
RestClient.get(url, accept: :json)
|
24
|
+
end
|
25
|
+
|
26
|
+
def send_mail(mail)
|
27
|
+
case mail
|
28
|
+
when Mail::Message
|
29
|
+
allow_non_tls = mail.allow_non_tls.nil? ? false : mail.allow_non_tls
|
30
|
+
payload = MailToMessage.new(mail, allow_non_tls: allow_non_tls)
|
31
|
+
.send_message_payload
|
32
|
+
when Hash
|
33
|
+
payload = Message.new(mail).send_message_payload
|
34
|
+
when Paubox::Message
|
35
|
+
payload = mail.send_message_payload
|
36
|
+
end
|
37
|
+
url = request_endpoint('messages')
|
38
|
+
response = RestClient.post(url, payload, auth_header)
|
39
|
+
if mail.class == Mail::Message
|
40
|
+
mail.source_tracking_id = JSON.parse(response.body)['sourceTrackingId']
|
41
|
+
end
|
42
|
+
JSON.parse(response.body)
|
43
|
+
end
|
44
|
+
alias deliver_mail send_mail
|
45
|
+
|
46
|
+
def email_disposition(source_tracking_id)
|
47
|
+
url = "#{request_endpoint('message_receipt')}?sourceTrackingId=#{source_tracking_id}"
|
48
|
+
response = RestClient.get(url, auth_header)
|
49
|
+
email_disposition = Paubox::EmailDisposition.new(JSON.parse(response.body))
|
50
|
+
end
|
51
|
+
alias message_receipt email_disposition
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def auth_header
|
56
|
+
{ accept: :json,
|
57
|
+
content_type: :json,
|
58
|
+
Authorization: "Token token=#{@api_key}" }
|
59
|
+
end
|
60
|
+
|
61
|
+
def api_base_endpoint
|
62
|
+
"#{api_protocol}#{api_host}/#{api_version}/#{api_user}"
|
63
|
+
end
|
64
|
+
|
65
|
+
def request_endpoint(endpoint)
|
66
|
+
"#{api_base_endpoint}/#{endpoint}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def defaults
|
70
|
+
{ api_key: Paubox.configuration.api_key,
|
71
|
+
api_user: Paubox.configuration.api_user,
|
72
|
+
api_host: 'api.paubox.net',
|
73
|
+
api_protocol: 'https://',
|
74
|
+
api_version: 'v1',
|
75
|
+
test_mode: false }
|
76
|
+
end
|
77
|
+
|
78
|
+
# recursively converts a nested Hash into OpenStruct
|
79
|
+
def to_open_struct(hash)
|
80
|
+
OpenStruct.new(hash.each_with_object({}) do |(key, val), memo|
|
81
|
+
memo[key] = val.is_a?(Hash) ? to_open_struct(val) : val
|
82
|
+
end)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -1,57 +1,62 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
@
|
17
|
-
@
|
18
|
-
@
|
19
|
-
@
|
20
|
-
@
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Paubox
|
4
|
+
# Parses email dispositions from /v1/message_reciept response to friendly Ruby
|
5
|
+
class EmailDisposition
|
6
|
+
require 'time'
|
7
|
+
attr_reader :response, :raw_json_response, :source_tracking_id, :message_id,
|
8
|
+
:message_deliveries, :errors
|
9
|
+
MessageDelivery = Struct.new(:recipient, :status)
|
10
|
+
MessageDeliveryStatus = Struct.new(:delivery_status, :delivery_time,
|
11
|
+
:opened_status, :opened_time)
|
12
|
+
MessageMultiDeliveryStatus = Struct.new(:delivery_status, :delivery_time)
|
13
|
+
ResponseError = Struct.new(:code, :status, :title, :details)
|
14
|
+
|
15
|
+
def initialize(response)
|
16
|
+
@response = response
|
17
|
+
@raw_json_response = response.to_json
|
18
|
+
@source_tracking_id = response.dig('sourceTrackingId')
|
19
|
+
@message_data = response.dig('data', 'message')
|
20
|
+
@message_id = @message_data ? @message_data['id'] : nil
|
21
|
+
@message_deliveries ||= build_message_deliveries
|
22
|
+
@errors ||= build_errors
|
23
|
+
end
|
24
|
+
|
25
|
+
def errors?
|
26
|
+
errors.any?
|
27
|
+
end
|
28
|
+
|
29
|
+
def build_errors
|
30
|
+
return [] unless response['errors']
|
31
|
+
|
32
|
+
errors = response['errors']
|
33
|
+
errors.map { |e| ResponseError.new(e['code'], e['status'], e['title'], e['details']) }
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def build_message_deliveries
|
39
|
+
return [] unless @message_data
|
40
|
+
|
41
|
+
deliveries = @message_data.fetch('message_deliveries', [])
|
42
|
+
deliveries.map do |delivery|
|
43
|
+
status = build_message_delivery_status(delivery['status'])
|
44
|
+
MessageDelivery.new(delivery['recipient'], status)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def build_message_delivery_status(stat)
|
49
|
+
delivery_status = stat['deliveryStatus']
|
50
|
+
delivery_time = stat['deliveryTime'].to_s.empty? ? nil : DateTime.parse(stat['deliveryTime'])
|
51
|
+
opened_status = stat['openedStatus'].to_s.empty? ? 'unopened' : stat['openedStatus']
|
52
|
+
opened_time = stat['openedTime'].to_s.empty? ? nil : DateTime.parse(stat['openedTime'])
|
53
|
+
return MessageMultiDeliveryStatus.new(delivery_status, delivery_time) if multi_recipient?
|
54
|
+
|
55
|
+
MessageDeliveryStatus.new(delivery_status, delivery_time, opened_status, opened_time)
|
56
|
+
end
|
57
|
+
|
58
|
+
def multi_recipient?
|
59
|
+
@message_data.fetch('message_deliveries', []).length > 1
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/paubox/format_helper.rb
CHANGED
@@ -1,55 +1,60 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Paubox
|
4
|
+
# Utility methods for Message and MailToMessage
|
5
|
+
module FormatHelper
|
6
|
+
require 'base64'
|
7
|
+
BASE64_REGEX = %r(/^(?:[A-Za-z0-9+\/]{4}\n?)*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/).freeze
|
8
|
+
|
9
|
+
def base64_encoded?(value)
|
10
|
+
return false unless value.is_a?(String)
|
11
|
+
|
12
|
+
!value.strip.match(BASE64_REGEX).nil?
|
13
|
+
end
|
14
|
+
|
15
|
+
def base64_encode_if_needed(str)
|
16
|
+
return str if base64_encoded?(str.to_s)
|
17
|
+
|
18
|
+
Base64.encode64(str.to_s)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Converts hash keys to strings and maps them to expected JSON key.
|
22
|
+
# Also converts hashes in shallow arrays.
|
23
|
+
def convert_keys_to_json_version(hash)
|
24
|
+
converted = {}
|
25
|
+
hash.each_pair do |key, val|
|
26
|
+
converted[ruby_to_json_key(key)] = val.is_a?(Hash) ? convert_keys_to_json_version(val) : val
|
27
|
+
next unless val.is_a?(Array)
|
28
|
+
|
29
|
+
val.each_with_index { |el, i| val[i] = convert_keys_to_json_version(el) if el.is_a?(Hash) }
|
30
|
+
end
|
31
|
+
converted
|
32
|
+
end
|
33
|
+
|
34
|
+
def ruby_to_json_key(key)
|
35
|
+
{ reply_to: 'reply-to', html_content: 'text/html', text_content: 'text/plain',
|
36
|
+
filename: 'fileName', file_name: 'fileName', content_type: 'contentType',
|
37
|
+
allow_non_tls: 'allowNonTLS', force_secure_notification: 'forceSecureNotification' }[key] || key.to_s
|
38
|
+
end
|
39
|
+
|
40
|
+
# def get_values_whitelist(*vals)
|
41
|
+
# vals.map { |k| next unless mail[k]; [ruby_to_json_key(k), mail[k]] }.to_h
|
42
|
+
# end
|
43
|
+
|
44
|
+
def string_or_array_to_array(object)
|
45
|
+
case object
|
46
|
+
when String
|
47
|
+
a = object.split(',').map { |str| squish(str) }
|
48
|
+
when Array
|
49
|
+
a = object.map { |s| squish(s) }
|
50
|
+
else
|
51
|
+
return []
|
52
|
+
end
|
53
|
+
a.reject(&:empty?)
|
54
|
+
end
|
55
|
+
|
56
|
+
def squish(str)
|
57
|
+
str.to_s.split.join(' ')
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|