opensend 0.1.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 +7 -0
- data/README.md +124 -0
- data/lib/opensend/version.rb +5 -0
- data/lib/opensend.rb +186 -0
- data/opensend.gemspec +25 -0
- metadata +51 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 349126cf4fb71e6ae38091a0ca4e7a800031bbcf11b0284f033206fd16331f67
|
|
4
|
+
data.tar.gz: 01b7aec53d77983da3e737bed3f3575f45871eca0ce9f03dc7eaf18d92920fd9
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a6ab3ec4098d2b45771e13ac5fa830eb8d910a5142591496fceb2c49f1da693c26441bc0b826730e6a88f2024d956278dab3d8532a3a70b3e745b2a5d49ae2a4
|
|
7
|
+
data.tar.gz: 657a432a912b857007d05b8cd133ad73b69e7f07016642fb877d5a2acfcc0daf1401cb4ae9c24ef59658bbdbd3cffcccbe74ee4363a1867ca39377f57f114989
|
data/README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# opensend Ruby SDK
|
|
2
|
+
|
|
3
|
+
Minimal first-party Ruby SDK for OpenSend transactional email sends with a
|
|
4
|
+
familiar API surface.
|
|
5
|
+
|
|
6
|
+
Use your OpenSend API key (`os_...`) with OpenSend's Ruby API surface.
|
|
7
|
+
Existing Resend-style send calls can migrate through the alias documented
|
|
8
|
+
below.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
This package is ready for RubyGems publishing. Until the RubyGems publish is
|
|
13
|
+
complete, install the built gem from this repository:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
cd packages/ruby-sdk
|
|
17
|
+
gem build opensend.gemspec
|
|
18
|
+
gem install ./opensend-0.1.0.gem
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
After `opensend` is published to RubyGems, install it with:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
gem install opensend
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Setup
|
|
28
|
+
|
|
29
|
+
Use an environment variable instead of hardcoding API keys:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
export OPENSEND_API_KEY="os_your_api_key"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
For self-hosted OpenSend, point the SDK at your deployment origin. The default
|
|
36
|
+
hosted origin is `https://opensend.namuh.co`.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
export OPENSEND_BASE_URL="http://localhost:3015"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Send an email
|
|
43
|
+
|
|
44
|
+
The module-level surface uses OpenSend while keeping familiar Ruby email-send
|
|
45
|
+
ergonomics:
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
require "opensend"
|
|
49
|
+
|
|
50
|
+
OpenSend.api_key ENV.fetch("OPENSEND_API_KEY")
|
|
51
|
+
OpenSend.base_url ENV.fetch("OPENSEND_BASE_URL", OpenSend::DEFAULT_BASE_URL)
|
|
52
|
+
|
|
53
|
+
email = OpenSend::Emails.send(
|
|
54
|
+
from: "hello@yourdomain.com",
|
|
55
|
+
to: "recipient@example.com",
|
|
56
|
+
subject: "Hello from OpenSend",
|
|
57
|
+
html: "<h1>It works!</h1>"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
puts email.fetch("id")
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`Resend` is also exported as an alias constant for existing send code migrating
|
|
64
|
+
to OpenSend:
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
require "opensend"
|
|
68
|
+
|
|
69
|
+
Resend.api_key ENV.fetch("OPENSEND_API_KEY")
|
|
70
|
+
email = Resend::Emails.send(
|
|
71
|
+
from: "hello@yourdomain.com",
|
|
72
|
+
to: ["recipient@example.com"],
|
|
73
|
+
subject: "Hello from OpenSend",
|
|
74
|
+
text: "It works!"
|
|
75
|
+
)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Instance client
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
client = OpenSend::Client.new(
|
|
82
|
+
api_key: ENV.fetch("OPENSEND_API_KEY"),
|
|
83
|
+
base_url: ENV.fetch("OPENSEND_BASE_URL", OpenSend::DEFAULT_BASE_URL)
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
email = client.emails.send(
|
|
87
|
+
from: "hello@yourdomain.com",
|
|
88
|
+
to: "recipient@example.com",
|
|
89
|
+
subject: "Hello from OpenSend",
|
|
90
|
+
html: "<h1>It works!</h1>"
|
|
91
|
+
)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Errors
|
|
95
|
+
|
|
96
|
+
Non-2xx API responses raise `OpenSend::Error` (also exported as
|
|
97
|
+
`OpenSend::APIError`). The exception keeps OpenSend's public error envelope
|
|
98
|
+
fields when present.
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
begin
|
|
102
|
+
OpenSend::Emails.send(params)
|
|
103
|
+
rescue OpenSend::Error => error
|
|
104
|
+
warn [error.status_code, error.code, error.message, error.details].inspect
|
|
105
|
+
end
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Supported first slice
|
|
109
|
+
|
|
110
|
+
- `OpenSend::Emails.send(params)` → `POST /emails`
|
|
111
|
+
- `OpenSend::Client#emails.send(params)` → `POST /emails`
|
|
112
|
+
- Bearer API key auth
|
|
113
|
+
- Configurable base URL
|
|
114
|
+
- Structured API error envelope parsing
|
|
115
|
+
- Resend alias constant for existing send code migrating to OpenSend
|
|
116
|
+
|
|
117
|
+
This first package intentionally does not implement batch sends, async jobs,
|
|
118
|
+
attachments helpers, or full resource-surface parity yet.
|
|
119
|
+
|
|
120
|
+
## Tests
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
ruby -I packages/ruby-sdk/lib packages/ruby-sdk/test/opensend_test.rb
|
|
124
|
+
```
|
data/lib/opensend.rb
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "net/http"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
require_relative "opensend/version"
|
|
8
|
+
|
|
9
|
+
module OpenSend
|
|
10
|
+
DEFAULT_BASE_URL = "https://opensend.namuh.co"
|
|
11
|
+
USER_AGENT = "opensend-ruby/#{VERSION}"
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
attr_writer :api_key, :base_url
|
|
15
|
+
|
|
16
|
+
def api_key(value = nil)
|
|
17
|
+
@api_key = value unless value.nil?
|
|
18
|
+
@api_key
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def base_url(value = nil)
|
|
22
|
+
@base_url = value unless value.nil?
|
|
23
|
+
@base_url || DEFAULT_BASE_URL
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def emails
|
|
27
|
+
EmailsResource.new(Client.new(api_key: configured_api_key, base_url: base_url))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def configured_api_key
|
|
33
|
+
key = api_key
|
|
34
|
+
raise ArgumentError, "set OpenSend.api_key before making API requests" if key.nil? || key.to_s.strip.empty?
|
|
35
|
+
|
|
36
|
+
key
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class Error < StandardError
|
|
41
|
+
attr_reader :status_code, :name, :code, :details, :body
|
|
42
|
+
|
|
43
|
+
def initialize(message, status_code:, name: nil, code: nil, details: nil, body: nil)
|
|
44
|
+
super(message)
|
|
45
|
+
@status_code = status_code
|
|
46
|
+
@name = name
|
|
47
|
+
@code = code
|
|
48
|
+
@details = details
|
|
49
|
+
@body = body
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
APIError = Error
|
|
54
|
+
|
|
55
|
+
class Client
|
|
56
|
+
attr_reader :api_key, :base_url
|
|
57
|
+
|
|
58
|
+
def initialize(api_key:, base_url: DEFAULT_BASE_URL)
|
|
59
|
+
@api_key = normalize_api_key(api_key)
|
|
60
|
+
@base_uri = normalize_base_url(base_url)
|
|
61
|
+
@base_url = @base_uri.to_s
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def emails
|
|
65
|
+
EmailsResource.new(self)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def post(path, payload)
|
|
69
|
+
uri = endpoint(path)
|
|
70
|
+
request = Net::HTTP::Post.new(uri)
|
|
71
|
+
request["Authorization"] = "Bearer #{api_key}"
|
|
72
|
+
request["Content-Type"] = "application/json"
|
|
73
|
+
request["Accept"] = "application/json"
|
|
74
|
+
request["User-Agent"] = USER_AGENT
|
|
75
|
+
request.body = JSON.generate(payload)
|
|
76
|
+
|
|
77
|
+
response = perform_request(uri, request)
|
|
78
|
+
body = response.body.to_s
|
|
79
|
+
|
|
80
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
81
|
+
raise api_error(response, body)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
body.empty? ? {} : JSON.parse(body)
|
|
85
|
+
rescue JSON::ParserError => error
|
|
86
|
+
raise Error.new(
|
|
87
|
+
"Invalid JSON response from OpenSend",
|
|
88
|
+
status_code: 0,
|
|
89
|
+
name: "parse_error",
|
|
90
|
+
code: "parse_error",
|
|
91
|
+
body: error.message
|
|
92
|
+
)
|
|
93
|
+
rescue IOError, SystemCallError, Timeout::Error, SocketError => error
|
|
94
|
+
raise Error.new(
|
|
95
|
+
error.message,
|
|
96
|
+
status_code: 0,
|
|
97
|
+
name: "request_error",
|
|
98
|
+
code: "request_error"
|
|
99
|
+
)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
def normalize_api_key(value)
|
|
105
|
+
key = value.to_s.strip
|
|
106
|
+
raise ArgumentError, "API key is required" if key.empty?
|
|
107
|
+
|
|
108
|
+
key
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def normalize_base_url(value)
|
|
112
|
+
raw = value.to_s.strip
|
|
113
|
+
raise ArgumentError, "base URL must be a non-empty string" if raw.empty?
|
|
114
|
+
|
|
115
|
+
uri = URI.parse(raw)
|
|
116
|
+
unless uri.is_a?(URI::HTTP) && uri.host
|
|
117
|
+
raise ArgumentError, "base URL must be a valid absolute http or https URL"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
uri.path = uri.path.to_s.sub(%r{/+\z}, "")
|
|
121
|
+
uri.query = nil
|
|
122
|
+
uri.fragment = nil
|
|
123
|
+
uri
|
|
124
|
+
rescue URI::InvalidURIError
|
|
125
|
+
raise ArgumentError, "base URL must be a valid absolute http or https URL"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def endpoint(path)
|
|
129
|
+
uri = @base_uri.dup
|
|
130
|
+
base_path = uri.path.to_s.sub(%r{/+\z}, "")
|
|
131
|
+
request_path = "/#{path.to_s.sub(%r{\A/+}, "")}"
|
|
132
|
+
uri.path = base_path.empty? ? request_path : "#{base_path}#{request_path}"
|
|
133
|
+
uri.query = nil
|
|
134
|
+
uri.fragment = nil
|
|
135
|
+
uri
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def perform_request(uri, request)
|
|
139
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http|
|
|
140
|
+
http.request(request)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def api_error(response, body)
|
|
145
|
+
envelope = parse_json_object(body)
|
|
146
|
+
message = envelope["message"] || response.message || "OpenSend API request failed"
|
|
147
|
+
|
|
148
|
+
Error.new(
|
|
149
|
+
message,
|
|
150
|
+
status_code: response.code.to_i,
|
|
151
|
+
name: envelope["name"],
|
|
152
|
+
code: envelope["code"],
|
|
153
|
+
details: envelope["details"],
|
|
154
|
+
body: body
|
|
155
|
+
)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def parse_json_object(body)
|
|
159
|
+
parsed = body.empty? ? {} : JSON.parse(body)
|
|
160
|
+
parsed.is_a?(Hash) ? parsed : {}
|
|
161
|
+
rescue JSON::ParserError
|
|
162
|
+
{}
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
class EmailsResource
|
|
167
|
+
def initialize(client)
|
|
168
|
+
@client = client
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def send(params)
|
|
172
|
+
@client.post("/emails", params)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
module Emails
|
|
177
|
+
def self.send(params, api_key: nil, base_url: nil)
|
|
178
|
+
key = api_key || OpenSend.api_key
|
|
179
|
+
raise ArgumentError, "set OpenSend.api_key before making API requests" if key.nil? || key.to_s.strip.empty?
|
|
180
|
+
|
|
181
|
+
Client.new(api_key: key, base_url: base_url || OpenSend.base_url).emails.send(params)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
Resend = OpenSend unless defined?(Resend)
|
data/opensend.gemspec
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/opensend/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "opensend"
|
|
7
|
+
spec.version = OpenSend::VERSION
|
|
8
|
+
spec.summary = "Ruby SDK for the OpenSend email API"
|
|
9
|
+
spec.description = "Minimal first-party Ruby SDK for OpenSend transactional email sends with a familiar API surface."
|
|
10
|
+
spec.authors = ["OpenSend"]
|
|
11
|
+
spec.homepage = "https://github.com/namuh-eng/opensend"
|
|
12
|
+
# RubyGems 3.0 does not recognize Elastic-2.0 as SPDX, so keep builds warning-free
|
|
13
|
+
# while linking to the repository license below.
|
|
14
|
+
spec.license = "Nonstandard"
|
|
15
|
+
spec.required_ruby_version = ">= 2.6"
|
|
16
|
+
spec.metadata = {
|
|
17
|
+
"homepage_uri" => spec.homepage,
|
|
18
|
+
"source_code_uri" => "https://github.com/namuh-eng/opensend",
|
|
19
|
+
"bug_tracker_uri" => "https://github.com/namuh-eng/opensend/issues",
|
|
20
|
+
"license_uri" => "https://github.com/namuh-eng/opensend/blob/main/LICENSE"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
spec.files = Dir["lib/**/*.rb", "README.md", "opensend.gemspec"]
|
|
24
|
+
spec.require_paths = ["lib"]
|
|
25
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: opensend
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- OpenSend
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-05-23 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: Minimal first-party Ruby SDK for OpenSend transactional email sends with
|
|
14
|
+
a familiar API surface.
|
|
15
|
+
email:
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- README.md
|
|
21
|
+
- lib/opensend.rb
|
|
22
|
+
- lib/opensend/version.rb
|
|
23
|
+
- opensend.gemspec
|
|
24
|
+
homepage: https://github.com/namuh-eng/opensend
|
|
25
|
+
licenses:
|
|
26
|
+
- Nonstandard
|
|
27
|
+
metadata:
|
|
28
|
+
homepage_uri: https://github.com/namuh-eng/opensend
|
|
29
|
+
source_code_uri: https://github.com/namuh-eng/opensend
|
|
30
|
+
bug_tracker_uri: https://github.com/namuh-eng/opensend/issues
|
|
31
|
+
license_uri: https://github.com/namuh-eng/opensend/blob/main/LICENSE
|
|
32
|
+
post_install_message:
|
|
33
|
+
rdoc_options: []
|
|
34
|
+
require_paths:
|
|
35
|
+
- lib
|
|
36
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '2.6'
|
|
41
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
42
|
+
requirements:
|
|
43
|
+
- - ">="
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '0'
|
|
46
|
+
requirements: []
|
|
47
|
+
rubygems_version: 3.0.3.1
|
|
48
|
+
signing_key:
|
|
49
|
+
specification_version: 4
|
|
50
|
+
summary: Ruby SDK for the OpenSend email API
|
|
51
|
+
test_files: []
|