supersendtx 0.8.2 → 0.8.3
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 +4 -4
- data/Gemfile +2 -0
- data/README.md +9 -0
- data/lib/supersendtx/delivery_method.rb +46 -0
- data/lib/supersendtx/message_mapper.rb +147 -0
- data/lib/supersendtx/railtie.rb +14 -0
- data/lib/supersendtx/resources.rb +4 -2
- data/lib/supersendtx/version.rb +1 -1
- data/lib/supersendtx.rb +18 -0
- data/supersendtx.gemspec +4 -0
- metadata +48 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bef605be553360ce670dcf5514b404abbbc9bf9759d33b88de04928932a57ace
|
|
4
|
+
data.tar.gz: 3732c01fa5d8a27343d950da44d8ffa5c59c06c89e7c24ceb0a4454fa854abb5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7f33d8b05598877aa6671c38c43f067ed9baf01d008b982b9447a5639e73bc37f0f018304aa712eb6a5fc050e0536bf2e385b3de15fcaf5a3d2c101c9df6c8e6
|
|
7
|
+
data.tar.gz: fb26b4039af3e90a2d961f9eec4d8af7cab14ba9d5f232f5eb0b89a1a5a566e67218350021daa6edf27e84cd86aa03809ad010ee694a44427e03390bf92dc995
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -21,4 +21,13 @@ result = tx.emails.send(
|
|
|
21
21
|
puts result["id"], result["status"]
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
+
## Rails ActionMailer
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
config.action_mailer.delivery_method = :supersendtx
|
|
28
|
+
config.action_mailer.supersendtx_settings = {
|
|
29
|
+
api_key: ENV.fetch("SUPERSENDTX_API_KEY")
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
24
33
|
Docs: https://docs.supersendtx.com/sdks/ruby
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "mail"
|
|
4
|
+
require_relative "message_mapper"
|
|
5
|
+
|
|
6
|
+
module SuperSendTX
|
|
7
|
+
# ActionMailer delivery method: config.action_mailer.delivery_method = :supersendtx
|
|
8
|
+
class DeliveryMethod
|
|
9
|
+
def initialize(settings = {})
|
|
10
|
+
@settings = settings || {}
|
|
11
|
+
@client = @settings[:client] || build_client
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def deliver!(mail)
|
|
15
|
+
params = MessageMapper.to_send_params(mail)
|
|
16
|
+
result = @client.emails.send(**params)
|
|
17
|
+
message_id = result.is_a?(Hash) ? (result["id"] || result[:id]) : nil
|
|
18
|
+
mail.message_id = message_id if message_id && mail.respond_to?(:message_id=)
|
|
19
|
+
result
|
|
20
|
+
rescue SuperSendTX::Error => e
|
|
21
|
+
# Re-raise as SuperSendTX::Error (not a bare String → RuntimeError) so
|
|
22
|
+
# ActionMailer / rescue_from can read status, details, and error_code.
|
|
23
|
+
raise SuperSendTX::Error.new(
|
|
24
|
+
"SuperSend TX API error: #{e.message}",
|
|
25
|
+
status: e.status,
|
|
26
|
+
details: e.details,
|
|
27
|
+
error_code: e.error_code,
|
|
28
|
+
upgrade_url: e.upgrade_url
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def build_client
|
|
35
|
+
api_key = @settings[:api_key] || @settings["api_key"] || ENV["SUPERSENDTX_API_KEY"]
|
|
36
|
+
raise ArgumentError, "SUPERSENDTX_API_KEY is not configured." if api_key.nil? || api_key.to_s.empty?
|
|
37
|
+
|
|
38
|
+
base_url = @settings[:base_url] || @settings["base_url"] || ENV["SUPERSENDTX_BASE_URL"]
|
|
39
|
+
if base_url && !base_url.to_s.empty?
|
|
40
|
+
Client.new(api_key, base_url: base_url)
|
|
41
|
+
else
|
|
42
|
+
Client.new(api_key)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "base64"
|
|
4
|
+
|
|
5
|
+
module SuperSendTX
|
|
6
|
+
# Maps a Mail::Message into emails.send keyword args.
|
|
7
|
+
module MessageMapper
|
|
8
|
+
HEADER_IDEMPOTENCY = "X-SuperSendTX-Idempotency-Key"
|
|
9
|
+
HEADER_SCHEDULED_AT = "X-SuperSendTX-Scheduled-At"
|
|
10
|
+
HEADER_TAG = "X-SuperSendTX-Tag"
|
|
11
|
+
|
|
12
|
+
RESERVED_HEADERS = %w[
|
|
13
|
+
from to cc bcc reply-to subject content-type mime-version date message-id
|
|
14
|
+
x-supersendtx-idempotency-key x-supersendtx-scheduled-at x-supersendtx-tag
|
|
15
|
+
idempotency-key
|
|
16
|
+
].freeze
|
|
17
|
+
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
def to_send_params(mail)
|
|
21
|
+
from = format_addresses(mail[:from])
|
|
22
|
+
raise ArgumentError, "Email is missing a From address." if from.empty?
|
|
23
|
+
|
|
24
|
+
to = format_addresses(mail[:to])
|
|
25
|
+
raise ArgumentError, "Email is missing a To recipient." if to.empty?
|
|
26
|
+
|
|
27
|
+
html = html_body(mail)
|
|
28
|
+
text = text_body(mail)
|
|
29
|
+
raise ArgumentError, "Email must include html or text content." if html.nil? && text.nil?
|
|
30
|
+
|
|
31
|
+
params = {
|
|
32
|
+
from: from.length == 1 ? from[0] : from,
|
|
33
|
+
to: to.length == 1 ? to[0] : to
|
|
34
|
+
}
|
|
35
|
+
params[:subject] = mail.subject if mail.subject && !mail.subject.empty?
|
|
36
|
+
params[:html] = html if html
|
|
37
|
+
params[:text] = text if text
|
|
38
|
+
|
|
39
|
+
reply_to = format_addresses(mail[:reply_to])
|
|
40
|
+
params[:reply_to] = reply_to.length == 1 ? reply_to[0] : reply_to unless reply_to.empty?
|
|
41
|
+
|
|
42
|
+
cc = format_addresses(mail[:cc])
|
|
43
|
+
params[:cc] = cc unless cc.empty?
|
|
44
|
+
bcc = format_addresses(mail[:bcc])
|
|
45
|
+
params[:bcc] = bcc unless bcc.empty?
|
|
46
|
+
|
|
47
|
+
attachments = map_attachments(mail)
|
|
48
|
+
params[:attachments] = attachments unless attachments.empty?
|
|
49
|
+
|
|
50
|
+
tags = extract_tags(mail)
|
|
51
|
+
params[:tags] = tags unless tags.empty?
|
|
52
|
+
|
|
53
|
+
headers = extract_forward_headers(mail)
|
|
54
|
+
params[:headers] = headers unless headers.empty?
|
|
55
|
+
|
|
56
|
+
idem = header_value(mail, HEADER_IDEMPOTENCY) || header_value(mail, "Idempotency-Key")
|
|
57
|
+
params[:idempotency_key] = idem if idem
|
|
58
|
+
|
|
59
|
+
scheduled = header_value(mail, HEADER_SCHEDULED_AT)
|
|
60
|
+
params[:scheduled_at] = scheduled if scheduled
|
|
61
|
+
|
|
62
|
+
params
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def format_addresses(field)
|
|
66
|
+
return [] if field.nil?
|
|
67
|
+
|
|
68
|
+
Array(field.addrs).filter_map do |addr|
|
|
69
|
+
email = addr.address.to_s.strip
|
|
70
|
+
next if email.empty?
|
|
71
|
+
|
|
72
|
+
name = addr.display_name.to_s.strip
|
|
73
|
+
name.empty? ? email : "#{name} <#{email}>"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def html_body(mail)
|
|
78
|
+
if mail.html_part
|
|
79
|
+
mail.html_part.decoded
|
|
80
|
+
elsif mail.mime_type == "text/html"
|
|
81
|
+
mail.decoded
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def text_body(mail)
|
|
86
|
+
if mail.text_part
|
|
87
|
+
mail.text_part.decoded
|
|
88
|
+
elsif mail.mime_type == "text/plain" || (!mail.multipart? && mail.mime_type.nil?)
|
|
89
|
+
body = mail.body.decoded
|
|
90
|
+
body && !body.empty? ? body : nil
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def map_attachments(mail)
|
|
95
|
+
mail.attachments.map do |attachment|
|
|
96
|
+
{
|
|
97
|
+
"filename" => attachment.filename.to_s.empty? ? "attachment" : attachment.filename,
|
|
98
|
+
"content_type" => attachment.mime_type || "application/octet-stream",
|
|
99
|
+
"content" => Base64.strict_encode64(attachment.body.decoded)
|
|
100
|
+
}
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def extract_tags(mail)
|
|
105
|
+
values = mail.header[HEADER_TAG]
|
|
106
|
+
return [] unless values
|
|
107
|
+
|
|
108
|
+
list = values.respond_to?(:map) ? values.map(&:to_s) : [values.to_s]
|
|
109
|
+
list.filter_map do |raw|
|
|
110
|
+
trimmed = raw.strip
|
|
111
|
+
next if trimmed.empty?
|
|
112
|
+
|
|
113
|
+
if trimmed.include?("=")
|
|
114
|
+
name, value = trimmed.split("=", 2)
|
|
115
|
+
next if name.strip.empty? || value.strip.empty?
|
|
116
|
+
|
|
117
|
+
{ "name" => name.strip, "value" => value.strip }
|
|
118
|
+
else
|
|
119
|
+
{ "name" => "tag", "value" => trimmed }
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def extract_forward_headers(mail)
|
|
125
|
+
out = {}
|
|
126
|
+
mail.header.fields.each do |field|
|
|
127
|
+
name = field.name.to_s
|
|
128
|
+
lower = name.downcase
|
|
129
|
+
next if RESERVED_HEADERS.include?(lower)
|
|
130
|
+
next if lower.start_with?("content-")
|
|
131
|
+
|
|
132
|
+
value = field.value.to_s.strip
|
|
133
|
+
out[name] = value unless value.empty?
|
|
134
|
+
end
|
|
135
|
+
out
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def header_value(mail, name)
|
|
139
|
+
field = mail.header[name]
|
|
140
|
+
return nil unless field
|
|
141
|
+
|
|
142
|
+
value = field.respond_to?(:last) ? field.last.to_s : field.to_s
|
|
143
|
+
value = value.strip
|
|
144
|
+
value.empty? ? nil : value
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/railtie"
|
|
4
|
+
require_relative "delivery_method"
|
|
5
|
+
|
|
6
|
+
module SuperSendTX
|
|
7
|
+
class Railtie < ::Rails::Railtie
|
|
8
|
+
initializer "supersendtx.add_delivery_method" do
|
|
9
|
+
ActiveSupport.on_load(:action_mailer) do
|
|
10
|
+
ActionMailer::Base.add_delivery_method :supersendtx, SuperSendTX::DeliveryMethod
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -19,7 +19,9 @@ module SuperSendTX
|
|
|
19
19
|
params["to"] = to if to
|
|
20
20
|
body = serialize_send_params(params)
|
|
21
21
|
headers = {}
|
|
22
|
-
idempotency_key =
|
|
22
|
+
idempotency_key =
|
|
23
|
+
params["idempotency_key"] || params[:idempotency_key] ||
|
|
24
|
+
params["idempotencyKey"] || params[:idempotencyKey]
|
|
23
25
|
headers["Idempotency-Key"] = idempotency_key.to_s if idempotency_key
|
|
24
26
|
|
|
25
27
|
@http.request("POST", "/emails", body: body, headers: headers)
|
|
@@ -54,7 +56,7 @@ module SuperSendTX
|
|
|
54
56
|
"to" => params["to"] || params[:to]
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
%w[subject html text reply_to replyTo cc bcc tags headers tag template].each do |key|
|
|
59
|
+
%w[subject html text reply_to replyTo cc bcc tags headers tag template attachments].each do |key|
|
|
58
60
|
value = params[key] || params[key.to_sym]
|
|
59
61
|
next if value.nil?
|
|
60
62
|
|
data/lib/supersendtx/version.rb
CHANGED
data/lib/supersendtx.rb
CHANGED
|
@@ -8,3 +8,21 @@ require_relative "supersendtx/client"
|
|
|
8
8
|
|
|
9
9
|
module SuperSendTX
|
|
10
10
|
end
|
|
11
|
+
|
|
12
|
+
begin
|
|
13
|
+
require "base64"
|
|
14
|
+
require "mail"
|
|
15
|
+
rescue LoadError
|
|
16
|
+
# base64/mail are optional; required for ActionMailer delivery method.
|
|
17
|
+
else
|
|
18
|
+
require_relative "supersendtx/message_mapper"
|
|
19
|
+
require_relative "supersendtx/delivery_method"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
begin
|
|
23
|
+
require "rails"
|
|
24
|
+
rescue LoadError
|
|
25
|
+
# Rails is optional.
|
|
26
|
+
else
|
|
27
|
+
require_relative "supersendtx/railtie"
|
|
28
|
+
end
|
data/supersendtx.gemspec
CHANGED
metadata
CHANGED
|
@@ -1,15 +1,57 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: supersendtx
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.8.
|
|
4
|
+
version: 0.8.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- SuperSend TX
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
12
|
-
dependencies:
|
|
11
|
+
date: 2026-08-04 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: base64
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0.2'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0.2'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: mail
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '2.8'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '2.8'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: minitest
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '5.25'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '5.25'
|
|
13
55
|
description: Official Ruby client for the SuperSend TX REST API.
|
|
14
56
|
email:
|
|
15
57
|
- support@supersendtx.com
|
|
@@ -22,8 +64,11 @@ files:
|
|
|
22
64
|
- README.md
|
|
23
65
|
- lib/supersendtx.rb
|
|
24
66
|
- lib/supersendtx/client.rb
|
|
67
|
+
- lib/supersendtx/delivery_method.rb
|
|
25
68
|
- lib/supersendtx/error.rb
|
|
26
69
|
- lib/supersendtx/http.rb
|
|
70
|
+
- lib/supersendtx/message_mapper.rb
|
|
71
|
+
- lib/supersendtx/railtie.rb
|
|
27
72
|
- lib/supersendtx/resources.rb
|
|
28
73
|
- lib/supersendtx/version.rb
|
|
29
74
|
- supersendtx.gemspec
|