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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eca4d44a3a130a37fb7055f295cd6f37ed780c145189a1ce40d4318f7fd5d309
4
- data.tar.gz: 5a4728dd67281d5323c284c32e21ef03943f602987428c2c76452fee6c80e904
3
+ metadata.gz: bef605be553360ce670dcf5514b404abbbc9bf9759d33b88de04928932a57ace
4
+ data.tar.gz: 3732c01fa5d8a27343d950da44d8ffa5c59c06c89e7c24ceb0a4454fa854abb5
5
5
  SHA512:
6
- metadata.gz: 303ae619a3dc9bc631c2938acf710bf25d63639c2612a96fe1229e6e3d3e5d40ef0a9a24d0a4c8ed837963f41814e6012e1f7eef8c160c0c1494adce1f6d4e7e
7
- data.tar.gz: dda61e8e034f98c653c0ca8adb4f75de2226065a6f9126d416487e3f5d20c48f7d94bfd10bea34229dff32f054c5402673d0326cbb8d9bf2e2378a1397a5a51c
6
+ metadata.gz: 7f33d8b05598877aa6671c38c43f067ed9baf01d008b982b9447a5639e73bc37f0f018304aa712eb6a5fc050e0536bf2e385b3de15fcaf5a3d2c101c9df6c8e6
7
+ data.tar.gz: fb26b4039af3e90a2d961f9eec4d8af7cab14ba9d5f232f5eb0b89a1a5a566e67218350021daa6edf27e84cd86aa03809ad010ee694a44427e03390bf92dc995
data/Gemfile CHANGED
@@ -6,4 +6,6 @@ gemspec
6
6
 
7
7
  group :development, :test do
8
8
  gem "minitest", "~> 5.25"
9
+ gem "mail", ">= 2.8"
10
+ gem "base64", ">= 0.2"
9
11
  end
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 = params["idempotency_key"] || params["idempotencyKey"]
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SuperSendTX
4
- VERSION = "0.8.2"
4
+ VERSION = "0.8.3"
5
5
  end
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
@@ -28,4 +28,8 @@ Gem::Specification.new do |spec|
28
28
  end
29
29
 
30
30
  spec.require_paths = ["lib"]
31
+
32
+ spec.add_dependency "base64", ">= 0.2"
33
+ spec.add_development_dependency "mail", ">= 2.8"
34
+ spec.add_development_dependency "minitest", "~> 5.25"
31
35
  end
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.2
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-07-29 00:00:00.000000000 Z
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