sendeez 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/LICENSE +21 -0
- data/README.md +63 -0
- data/lib/sendeez/admin.rb +13 -0
- data/lib/sendeez/api_keys.rb +27 -0
- data/lib/sendeez/client.rb +144 -0
- data/lib/sendeez/domains.rb +29 -0
- data/lib/sendeez/emails.rb +37 -0
- data/lib/sendeez/errors.rb +20 -0
- data/lib/sendeez/suppressions.rb +24 -0
- data/lib/sendeez/version.rb +5 -0
- data/lib/sendeez/webhooks.rb +36 -0
- data/lib/sendeez.rb +16 -0
- metadata +85 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 85eb442ceccc29ce2672d7f9667e622770bfbbd7fdc8cfbc627fab9b94587258
|
|
4
|
+
data.tar.gz: b5a13fa32a5c612f95d3f0277d687faf432dc3886b0ddd60073dc35f6cc304b1
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: da4c9029648fd21898a1be35db3f44f8f71a28d62e5cc1728b09ee2b522912de0a3321dacc73e011d7d9840eba34ff75de296f4eeef9cd709d45f797443ba86b
|
|
7
|
+
data.tar.gz: 5009b9ebc827fb3770196f73062e0f3ffcb0f0a02d8c0d08f4f5b2809539a3dfed05311dc553b4604a44c3857cfe061a0e9eb5f84560a801bf2533e3f6ed9eaf
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sendeez
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# sendeez
|
|
2
|
+
|
|
3
|
+
Zero-dependency Ruby SDK for the Sendeez transactional email API.
|
|
4
|
+
|
|
5
|
+
```ruby
|
|
6
|
+
require "sendeez"
|
|
7
|
+
|
|
8
|
+
sendeez = Sendeez::Client.new(ENV.fetch("SENDEEZ_API_KEY"))
|
|
9
|
+
|
|
10
|
+
email = sendeez.emails.send_email(
|
|
11
|
+
{
|
|
12
|
+
"from" => { "email" => "hello@example.com", "name" => "Example" },
|
|
13
|
+
"to" => [{ "email" => "developer@example.com" }],
|
|
14
|
+
"subject" => "Welcome",
|
|
15
|
+
"text" => "Your account is ready."
|
|
16
|
+
},
|
|
17
|
+
idempotency_key: "welcome-user-123" # Optional. The SDK generates one when omitted.
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
puts "#{email['id']} #{email['status']}"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Note the input hash needs explicit `{ }` braces at the call site: `emails`
|
|
24
|
+
methods take keyword arguments too (`idempotency_key:`, `timeout:`), and
|
|
25
|
+
Ruby 3+ only treats a bare trailing hash as positional when it's wrapped in
|
|
26
|
+
braces — otherwise it tries (and fails) to parse it as keywords.
|
|
27
|
+
|
|
28
|
+
The send method is named `send_email`, not `send` — overriding `Kernel#send`
|
|
29
|
+
with a method that also declares keyword parameters hits a real Ruby VM bug
|
|
30
|
+
(argument count coming out as 0), not just a style concern.
|
|
31
|
+
|
|
32
|
+
## Errors and agent actions
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
begin
|
|
36
|
+
sendeez.emails.send_email(message)
|
|
37
|
+
rescue Sendeez::Error => e
|
|
38
|
+
puts [e.code, e.param, e.request_id, e.action, e.retry_after].inspect
|
|
39
|
+
end
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The SDK retries GET requests and mutations carrying an idempotency key after
|
|
43
|
+
network errors, `429`, or `5xx`. It never automatically retries an unsafe
|
|
44
|
+
mutation.
|
|
45
|
+
|
|
46
|
+
Every method accepts a `timeout:` (seconds). This lets applications and AI
|
|
47
|
+
agents enforce their own execution deadline:
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
sendeez.emails.list(timeout: 5)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Testing
|
|
54
|
+
|
|
55
|
+
Pass `transport:` to substitute a fake transport, the same role `fetch`/
|
|
56
|
+
`opener`/`transport` injection plays in the Node, Python, and PHP SDKs:
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
fake_transport = lambda do |method, url, headers, body, timeout|
|
|
60
|
+
{ status: 200, headers: {}, body: "{}" }
|
|
61
|
+
end
|
|
62
|
+
client = Sendeez::Client.new("sendeez_example_secret", transport: fake_transport)
|
|
63
|
+
```
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sendeez
|
|
4
|
+
class ApiKeys
|
|
5
|
+
def initialize(client)
|
|
6
|
+
@client = client
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def create(name, scopes, expires_at: nil, timeout: nil)
|
|
10
|
+
body = { "name" => name, "scopes" => scopes }
|
|
11
|
+
body["expires_at"] = expires_at if expires_at
|
|
12
|
+
@client.request("POST", "/v1/api-keys", body: body, timeout: timeout)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def list(timeout: nil)
|
|
16
|
+
@client.request("GET", "/v1/api-keys", timeout: timeout)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def rotate(id, timeout: nil)
|
|
20
|
+
@client.request("POST", "/v1/api-keys/#{URI.encode_www_form_component(id)}/rotate", timeout: timeout)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def remove(id, timeout: nil)
|
|
24
|
+
@client.request("DELETE", "/v1/api-keys/#{URI.encode_www_form_component(id)}", timeout: timeout)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sendeez
|
|
4
|
+
# Client for the Sendeez transactional email API.
|
|
5
|
+
#
|
|
6
|
+
# Zero runtime dependencies: HTTP is done with Net::HTTP. Pass +transport+
|
|
7
|
+
# to substitute a fake transport in tests (same role as injecting +fetch+/
|
|
8
|
+
# +opener+ in the Node and Python SDKs).
|
|
9
|
+
class Client
|
|
10
|
+
DEFAULT_BASE_URL = "https://api.sendeez.com"
|
|
11
|
+
|
|
12
|
+
attr_reader :emails, :domains, :suppressions, :webhooks, :api_keys, :admin
|
|
13
|
+
|
|
14
|
+
def initialize(api_key, base_url: DEFAULT_BASE_URL, max_retries: 2, timeout: 30, transport: nil)
|
|
15
|
+
raise ArgumentError, "Sendeez api_key is required" if api_key.nil? || api_key.empty?
|
|
16
|
+
|
|
17
|
+
@api_key = api_key
|
|
18
|
+
@base_url = base_url.chomp("/")
|
|
19
|
+
@max_retries = max_retries
|
|
20
|
+
@timeout = timeout
|
|
21
|
+
@transport = transport || method(:default_transport)
|
|
22
|
+
|
|
23
|
+
@emails = Emails.new(self)
|
|
24
|
+
@domains = Domains.new(self)
|
|
25
|
+
@suppressions = Suppressions.new(self)
|
|
26
|
+
@webhooks = Webhooks.new(self)
|
|
27
|
+
@api_keys = ApiKeys.new(self)
|
|
28
|
+
@admin = Admin.new(self)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.new_idempotency_key
|
|
32
|
+
"sdk_#{SecureRandom.uuid}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @api private used by Sendeez::{Emails,Domains,...}; not part of the public API.
|
|
36
|
+
def request(method, path, body: nil, query: nil, idempotency_key: nil, timeout: nil)
|
|
37
|
+
url = "#{@base_url}#{path}"
|
|
38
|
+
if query
|
|
39
|
+
filtered = query.reject { |_, v| v.nil? }
|
|
40
|
+
url += "?#{URI.encode_www_form(filtered)}" unless filtered.empty?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
headers = {
|
|
44
|
+
"Authorization" => "Bearer #{@api_key}",
|
|
45
|
+
"Accept" => "application/json"
|
|
46
|
+
}
|
|
47
|
+
payload = nil
|
|
48
|
+
unless body.nil?
|
|
49
|
+
payload = JSON.generate(body)
|
|
50
|
+
headers["Content-Type"] = "application/json"
|
|
51
|
+
end
|
|
52
|
+
headers["Idempotency-Key"] = idempotency_key if idempotency_key
|
|
53
|
+
|
|
54
|
+
effective_timeout = timeout || @timeout
|
|
55
|
+
attempt = 0
|
|
56
|
+
|
|
57
|
+
loop do
|
|
58
|
+
begin
|
|
59
|
+
response = @transport.call(method, url, headers, payload, effective_timeout)
|
|
60
|
+
rescue StandardError => e
|
|
61
|
+
raise e if attempt >= @max_retries || (method != "GET" && idempotency_key.nil?)
|
|
62
|
+
|
|
63
|
+
sleep(backoff(attempt))
|
|
64
|
+
attempt += 1
|
|
65
|
+
next
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
status = response[:status]
|
|
69
|
+
if status >= 200 && status < 300
|
|
70
|
+
body_str = response[:body]
|
|
71
|
+
return nil if status == 204 || body_str.nil? || body_str.empty?
|
|
72
|
+
|
|
73
|
+
return JSON.parse(body_str)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
error_payload = parse_error_body(response[:body])
|
|
77
|
+
retry_after = parse_retry_after(response[:headers])
|
|
78
|
+
|
|
79
|
+
if attempt < @max_retries && retryable?(method, idempotency_key, status)
|
|
80
|
+
sleep(retry_after || backoff(attempt))
|
|
81
|
+
attempt += 1
|
|
82
|
+
next
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
raise Sendeez::Error.new(status, error_payload["error"] || {}, retry_after: retry_after)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def retryable?(method, idempotency_key, status)
|
|
92
|
+
(method == "GET" || !idempotency_key.nil?) && (status == 429 || status >= 500)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def parse_error_body(body)
|
|
96
|
+
return {} if body.nil? || body.empty?
|
|
97
|
+
|
|
98
|
+
JSON.parse(body)
|
|
99
|
+
rescue JSON::ParserError
|
|
100
|
+
{}
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def parse_retry_after(headers)
|
|
104
|
+
return nil unless headers
|
|
105
|
+
|
|
106
|
+
value = headers["retry-after"] || headers["Retry-After"]
|
|
107
|
+
return nil unless value
|
|
108
|
+
|
|
109
|
+
seconds = Float(value)
|
|
110
|
+
[seconds, 60].min
|
|
111
|
+
rescue ArgumentError, TypeError
|
|
112
|
+
nil
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def backoff(attempt)
|
|
116
|
+
2**attempt
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def default_transport(method, url, headers, body, timeout)
|
|
120
|
+
uri = URI(url)
|
|
121
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
122
|
+
http.use_ssl = uri.scheme == "https"
|
|
123
|
+
http.read_timeout = timeout
|
|
124
|
+
http.open_timeout = timeout
|
|
125
|
+
|
|
126
|
+
request_class = {
|
|
127
|
+
"GET" => Net::HTTP::Get,
|
|
128
|
+
"POST" => Net::HTTP::Post,
|
|
129
|
+
"DELETE" => Net::HTTP::Delete
|
|
130
|
+
}.fetch(method) { raise ArgumentError, "Unsupported method #{method}" }
|
|
131
|
+
|
|
132
|
+
req = request_class.new(uri)
|
|
133
|
+
headers.each { |key, value| req[key] = value }
|
|
134
|
+
req.body = body if body
|
|
135
|
+
|
|
136
|
+
response = http.request(req)
|
|
137
|
+
{
|
|
138
|
+
status: response.code.to_i,
|
|
139
|
+
headers: response.to_hash.transform_values(&:first),
|
|
140
|
+
body: response.body || ""
|
|
141
|
+
}
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sendeez
|
|
4
|
+
class Domains
|
|
5
|
+
def initialize(client)
|
|
6
|
+
@client = client
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def create(name, timeout: nil)
|
|
10
|
+
@client.request("POST", "/v1/domains", body: { "name" => name }, timeout: timeout)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def list(timeout: nil)
|
|
14
|
+
@client.request("GET", "/v1/domains", timeout: timeout)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def retrieve(id, timeout: nil)
|
|
18
|
+
@client.request("GET", "/v1/domains/#{URI.encode_www_form_component(id)}", timeout: timeout)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def verify(id, timeout: nil)
|
|
22
|
+
@client.request("POST", "/v1/domains/#{URI.encode_www_form_component(id)}/verify", timeout: timeout)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def remove(id, timeout: nil)
|
|
26
|
+
@client.request("DELETE", "/v1/domains/#{URI.encode_www_form_component(id)}", timeout: timeout)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sendeez
|
|
4
|
+
class Emails
|
|
5
|
+
def initialize(client)
|
|
6
|
+
@client = client
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def send_email(input, idempotency_key: nil, timeout: nil)
|
|
10
|
+
@client.request(
|
|
11
|
+
"POST",
|
|
12
|
+
"/v1/emails",
|
|
13
|
+
body: input,
|
|
14
|
+
idempotency_key: idempotency_key || Client.new_idempotency_key,
|
|
15
|
+
timeout: timeout
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def retrieve(id, timeout: nil)
|
|
20
|
+
@client.request("GET", "/v1/emails/#{URI.encode_www_form_component(id)}", timeout: timeout)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def list(limit: nil, after: nil, timeout: nil)
|
|
24
|
+
@client.request("GET", "/v1/emails", query: { "limit" => limit, "after" => after }, timeout: timeout)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def simulate_event(email_id, input, idempotency_key: nil, timeout: nil)
|
|
28
|
+
@client.request(
|
|
29
|
+
"POST",
|
|
30
|
+
"/v1/emails/#{URI.encode_www_form_component(email_id)}/events",
|
|
31
|
+
body: input,
|
|
32
|
+
idempotency_key: idempotency_key || Client.new_idempotency_key,
|
|
33
|
+
timeout: timeout
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sendeez
|
|
4
|
+
# Raised for any non-2xx response from the Sendeez API.
|
|
5
|
+
class Error < StandardError
|
|
6
|
+
attr_reader :status, :type, :code, :param, :request_id, :details, :action, :retry_after
|
|
7
|
+
|
|
8
|
+
def initialize(status, error, retry_after: nil)
|
|
9
|
+
@status = status
|
|
10
|
+
@type = error["type"] || "api_error"
|
|
11
|
+
@code = error["code"] || "unknown_error"
|
|
12
|
+
@param = error["param"]
|
|
13
|
+
@request_id = error["request_id"]
|
|
14
|
+
@details = error["details"]
|
|
15
|
+
@action = error["action"]
|
|
16
|
+
@retry_after = retry_after
|
|
17
|
+
super(error["message"] || "Sendeez request failed with HTTP #{status}")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sendeez
|
|
4
|
+
class Suppressions
|
|
5
|
+
def initialize(client)
|
|
6
|
+
@client = client
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def create(recipient, reason: nil, expires_at: nil, timeout: nil)
|
|
10
|
+
body = { "recipient" => recipient }
|
|
11
|
+
body["reason"] = reason if reason
|
|
12
|
+
body["expires_at"] = expires_at if expires_at
|
|
13
|
+
@client.request("POST", "/v1/suppressions", body: body, timeout: timeout)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def list(timeout: nil)
|
|
17
|
+
@client.request("GET", "/v1/suppressions", timeout: timeout)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def remove(id, timeout: nil)
|
|
21
|
+
@client.request("DELETE", "/v1/suppressions/#{URI.encode_www_form_component(id)}", timeout: timeout)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sendeez
|
|
4
|
+
class Webhooks
|
|
5
|
+
def initialize(client)
|
|
6
|
+
@client = client
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def create(url, description: nil, events: nil, timeout: nil)
|
|
10
|
+
body = { "url" => url }
|
|
11
|
+
body["description"] = description if description
|
|
12
|
+
body["events"] = events if events
|
|
13
|
+
@client.request("POST", "/v1/webhooks", body: body, timeout: timeout)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def list(timeout: nil)
|
|
17
|
+
@client.request("GET", "/v1/webhooks", timeout: timeout)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def retrieve(id, timeout: nil)
|
|
21
|
+
@client.request("GET", "/v1/webhooks/#{URI.encode_www_form_component(id)}", timeout: timeout)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def test(id, timeout: nil)
|
|
25
|
+
@client.request("POST", "/v1/webhooks/#{URI.encode_www_form_component(id)}/test", timeout: timeout)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def deliveries(id, timeout: nil)
|
|
29
|
+
@client.request("GET", "/v1/webhooks/#{URI.encode_www_form_component(id)}/deliveries", timeout: timeout)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def remove(id, timeout: nil)
|
|
33
|
+
@client.request("DELETE", "/v1/webhooks/#{URI.encode_www_form_component(id)}", timeout: timeout)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/sendeez.rb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "net/http"
|
|
5
|
+
require "uri"
|
|
6
|
+
require "securerandom"
|
|
7
|
+
|
|
8
|
+
require_relative "sendeez/version"
|
|
9
|
+
require_relative "sendeez/errors"
|
|
10
|
+
require_relative "sendeez/client"
|
|
11
|
+
require_relative "sendeez/emails"
|
|
12
|
+
require_relative "sendeez/domains"
|
|
13
|
+
require_relative "sendeez/suppressions"
|
|
14
|
+
require_relative "sendeez/webhooks"
|
|
15
|
+
require_relative "sendeez/api_keys"
|
|
16
|
+
require_relative "sendeez/admin"
|
metadata
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: sendeez
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Sendeez
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-07-27 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: minitest
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '5.0'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '5.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '13.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '13.0'
|
|
41
|
+
description: Sendeez::Client wraps the Sendeez transactional email API with no gem
|
|
42
|
+
runtime dependencies, using Net::HTTP directly.
|
|
43
|
+
email:
|
|
44
|
+
executables: []
|
|
45
|
+
extensions: []
|
|
46
|
+
extra_rdoc_files: []
|
|
47
|
+
files:
|
|
48
|
+
- LICENSE
|
|
49
|
+
- README.md
|
|
50
|
+
- lib/sendeez.rb
|
|
51
|
+
- lib/sendeez/admin.rb
|
|
52
|
+
- lib/sendeez/api_keys.rb
|
|
53
|
+
- lib/sendeez/client.rb
|
|
54
|
+
- lib/sendeez/domains.rb
|
|
55
|
+
- lib/sendeez/emails.rb
|
|
56
|
+
- lib/sendeez/errors.rb
|
|
57
|
+
- lib/sendeez/suppressions.rb
|
|
58
|
+
- lib/sendeez/version.rb
|
|
59
|
+
- lib/sendeez/webhooks.rb
|
|
60
|
+
homepage: https://github.com/sendeezhq/sendeez-ruby
|
|
61
|
+
licenses:
|
|
62
|
+
- MIT
|
|
63
|
+
metadata:
|
|
64
|
+
source_code_uri: https://github.com/sendeezhq/sendeez-ruby
|
|
65
|
+
bug_tracker_uri: https://github.com/sendeezhq/sendeez-ruby/issues
|
|
66
|
+
post_install_message:
|
|
67
|
+
rdoc_options: []
|
|
68
|
+
require_paths:
|
|
69
|
+
- lib
|
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '3.0'
|
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
76
|
+
requirements:
|
|
77
|
+
- - ">="
|
|
78
|
+
- !ruby/object:Gem::Version
|
|
79
|
+
version: '0'
|
|
80
|
+
requirements: []
|
|
81
|
+
rubygems_version: 3.5.22
|
|
82
|
+
signing_key:
|
|
83
|
+
specification_version: 4
|
|
84
|
+
summary: Zero-dependency Ruby SDK for the Sendeez transactional email API.
|
|
85
|
+
test_files: []
|