sendrly 1.0.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 +133 -0
- data/lib/sendrly/client.rb +204 -0
- data/lib/sendrly/contracts.rb +142 -0
- data/lib/sendrly/errors.rb +37 -0
- data/lib/sendrly/version.rb +6 -0
- data/lib/sendrly.rb +15 -0
- metadata +181 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0201aa9b578217b1acb1afe695d4df1ce4865dbe329eedf3a6557fb653a73717
|
4
|
+
data.tar.gz: cc89e9529c5a1cda7a9a6b57e7ebbfb0fdfe50fb8866494dad6b8fe7dbf56a2f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1ac118a09873a02564b47f394894edaf1e3e7d15cdf54be209f1bb3837aa69a7f81ed3053e643247f26360be4e99da0d35c9067bf57ca9ed8600111a932ce8d6
|
7
|
+
data.tar.gz: 9814a5ed7bb17b565cfb4ce13eb02faf7623023eaaa80b0279a9eeeae3ca6328dca7a89386f861a46cba2ac15e006db9073bc29a9fbc47ae8f2e71a7b4d0b216
|
data/README.md
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
# Sendrly Ruby SDK
|
2
|
+
|
3
|
+
The official Ruby SDK for [Sendrly](https://sendrly.com).
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'sendrly'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
```bash
|
16
|
+
$ bundle install
|
17
|
+
```
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
```bash
|
22
|
+
$ gem install sendrly
|
23
|
+
```
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
### Initialize the client
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
require 'sendrly'
|
31
|
+
|
32
|
+
# Create a new client instance
|
33
|
+
client = Sendrly.new(api_key: 'your_api_key')
|
34
|
+
|
35
|
+
# Enable debug logging
|
36
|
+
client = Sendrly.new(api_key: 'your_api_key', debug: true)
|
37
|
+
```
|
38
|
+
|
39
|
+
### Send an event
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
# Simple event
|
43
|
+
client.send_event(
|
44
|
+
email: 'user@example.com',
|
45
|
+
event: 'user.signup'
|
46
|
+
)
|
47
|
+
|
48
|
+
# Event with properties
|
49
|
+
client.send_event(
|
50
|
+
email: 'user@example.com',
|
51
|
+
event: 'order.completed',
|
52
|
+
contact_properties: {
|
53
|
+
name: 'John Smith',
|
54
|
+
plan: 'enterprise'
|
55
|
+
},
|
56
|
+
event_properties: {
|
57
|
+
order_id: 'ORD-123',
|
58
|
+
amount: 499.99
|
59
|
+
}
|
60
|
+
)
|
61
|
+
```
|
62
|
+
|
63
|
+
### Get contact details
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
begin
|
67
|
+
contact = client.get_contact('user@example.com')
|
68
|
+
puts contact[:properties][:name]
|
69
|
+
rescue Sendrly::APIError => e
|
70
|
+
if e.code == 'NOT_FOUND'
|
71
|
+
puts 'Contact not found'
|
72
|
+
else
|
73
|
+
puts "API error: #{e.message}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
### List events
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
# Get all signup events
|
82
|
+
signups = client.list_events('user.signup')
|
83
|
+
signups.each do |event|
|
84
|
+
puts event[:properties][:source]
|
85
|
+
end
|
86
|
+
|
87
|
+
# Calculate total revenue from orders
|
88
|
+
orders = client.list_events('order.completed')
|
89
|
+
total_revenue = orders.sum { |order| order[:properties][:amount] }
|
90
|
+
```
|
91
|
+
|
92
|
+
### Delete a contact
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
begin
|
96
|
+
client.delete_contact('user@example.com')
|
97
|
+
puts 'Contact deleted successfully'
|
98
|
+
rescue Sendrly::APIError => e
|
99
|
+
puts "Failed to delete contact: #{e.message}"
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
### Error handling
|
104
|
+
|
105
|
+
The SDK can raise the following errors:
|
106
|
+
|
107
|
+
- `Sendrly::ValidationError`: When input validation fails
|
108
|
+
- `Sendrly::APIError`: When the API returns an error response
|
109
|
+
- `Sendrly::NetworkError`: When there's a network-related error
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
begin
|
113
|
+
client.send_event(email: 'invalid', event: 'test')
|
114
|
+
rescue Sendrly::ValidationError => e
|
115
|
+
puts "Validation failed: #{e.errors}"
|
116
|
+
rescue Sendrly::APIError => e
|
117
|
+
puts "API error (#{e.code}): #{e.message}"
|
118
|
+
rescue Sendrly::NetworkError => e
|
119
|
+
puts "Network error: #{e.message}"
|
120
|
+
end
|
121
|
+
```
|
122
|
+
|
123
|
+
## Development
|
124
|
+
|
125
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
126
|
+
|
127
|
+
## Contributing
|
128
|
+
|
129
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/sendrly/sendrly-ruby.
|
130
|
+
|
131
|
+
## License
|
132
|
+
|
133
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -0,0 +1,204 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "faraday"
|
4
|
+
require "json"
|
5
|
+
require "logger"
|
6
|
+
require_relative "errors"
|
7
|
+
require_relative "contracts"
|
8
|
+
|
9
|
+
module Sendrly
|
10
|
+
class Client
|
11
|
+
BASE_URL = "https://stebgknkomdhjikpcytk.supabase.co/functions/v1"
|
12
|
+
|
13
|
+
attr_reader :api_key, :debug
|
14
|
+
|
15
|
+
def initialize(api_key:, debug: false)
|
16
|
+
@api_key = api_key
|
17
|
+
@debug = debug
|
18
|
+
@logger = Logger.new($stdout)
|
19
|
+
@logger.level = debug ? Logger::DEBUG : Logger::INFO
|
20
|
+
@http_client = Faraday.new(url: BASE_URL) do |f|
|
21
|
+
f.request :json
|
22
|
+
f.response :json
|
23
|
+
f.headers["Content-Type"] = "application/json"
|
24
|
+
f.headers["apikey"] = api_key
|
25
|
+
end
|
26
|
+
|
27
|
+
validate_config!
|
28
|
+
log "Initialized with config: #{config_for_log}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def send_event(email:, event:, marketing_opt_out: false, contact_properties: {}, event_properties: {})
|
32
|
+
result = Contracts::EventPayload.new.call(
|
33
|
+
email: email,
|
34
|
+
event: event,
|
35
|
+
marketing_opt_out: marketing_opt_out,
|
36
|
+
contact_properties: contact_properties,
|
37
|
+
event_properties: event_properties
|
38
|
+
)
|
39
|
+
raise ValidationError, result.errors.to_h unless result.success?
|
40
|
+
|
41
|
+
# Match the exact payload structure expected by the Edge Function
|
42
|
+
payload = {
|
43
|
+
contact: {
|
44
|
+
email: email,
|
45
|
+
marketing_opt_out: marketing_opt_out,
|
46
|
+
properties: contact_properties
|
47
|
+
},
|
48
|
+
event: {
|
49
|
+
name: event,
|
50
|
+
properties: event_properties
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
log "=== Request Details ==="
|
55
|
+
log "Endpoint: #{BASE_URL}/event"
|
56
|
+
log "Headers: #{@http_client.headers.inspect}"
|
57
|
+
log "Payload: #{payload.inspect}"
|
58
|
+
|
59
|
+
response = make_request(:post, "event", payload)
|
60
|
+
|
61
|
+
log "Event sent successfully"
|
62
|
+
response
|
63
|
+
end
|
64
|
+
|
65
|
+
def get_contact(email)
|
66
|
+
log "Getting contact: #{email}"
|
67
|
+
|
68
|
+
result = Contracts::Email.new.call(email: email)
|
69
|
+
raise ValidationError, result.errors.to_h unless result.success?
|
70
|
+
|
71
|
+
log "Making GET request to: #{BASE_URL}/find-contact with email: #{email}"
|
72
|
+
response = make_request(:get, "find-contact", email: email)
|
73
|
+
|
74
|
+
if !response["success"]
|
75
|
+
raise NotFoundError, response["error"] || "Contact not found"
|
76
|
+
end
|
77
|
+
|
78
|
+
response["contact"]
|
79
|
+
end
|
80
|
+
|
81
|
+
def list_events(event_name)
|
82
|
+
log "Listing events: #{event_name}"
|
83
|
+
|
84
|
+
result = Contracts::EventName.new.call(event: event_name)
|
85
|
+
raise ValidationError, result.errors.to_h unless result.success?
|
86
|
+
|
87
|
+
log "Making GET request to: #{BASE_URL}/get-event with event: #{event_name}"
|
88
|
+
response = make_request(:get, "get-event", event: event_name)
|
89
|
+
|
90
|
+
if !response["success"]
|
91
|
+
raise APIError.new(response["error"] || "Failed to list events", "API_ERROR")
|
92
|
+
end
|
93
|
+
|
94
|
+
response["events"] || []
|
95
|
+
end
|
96
|
+
|
97
|
+
def delete_contact(email)
|
98
|
+
log "Deleting contact: #{email}"
|
99
|
+
|
100
|
+
result = Contracts::Email.new.call(email: email)
|
101
|
+
raise ValidationError, result.errors.to_h unless result.success?
|
102
|
+
|
103
|
+
log "Making DELETE request to: #{BASE_URL}/delete-contact with email: #{email}"
|
104
|
+
response = make_request(:delete, "delete-contact", email: email)
|
105
|
+
|
106
|
+
if !response["success"]
|
107
|
+
if response["error"]&.include?("not found")
|
108
|
+
raise NotFoundError, "Contact not found"
|
109
|
+
else
|
110
|
+
raise APIError.new(response["error"] || "Failed to delete contact", "API_ERROR")
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
response
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
def validate_config!
|
120
|
+
result = Contracts::Config.new.call(api_key: api_key, debug: debug)
|
121
|
+
raise ValidationError, result.errors.to_h unless result.success?
|
122
|
+
end
|
123
|
+
|
124
|
+
def make_request(method, path, params = {})
|
125
|
+
log "=== Request Details ==="
|
126
|
+
log "Method: #{method.upcase}"
|
127
|
+
log "Path: #{path}"
|
128
|
+
log "Headers: #{@http_client.headers.inspect}"
|
129
|
+
log "Params: #{params.inspect}"
|
130
|
+
|
131
|
+
response = case method
|
132
|
+
when :get, :delete
|
133
|
+
query = URI.encode_www_form(params)
|
134
|
+
full_url = "#{path}?#{query}"
|
135
|
+
log "Full URL: #{full_url}"
|
136
|
+
@http_client.public_send(method, full_url)
|
137
|
+
else
|
138
|
+
@http_client.public_send(method, path, params)
|
139
|
+
end
|
140
|
+
|
141
|
+
log "=== Response Details ==="
|
142
|
+
log "Status: #{response.status}"
|
143
|
+
log "Headers: #{response.headers.inspect}"
|
144
|
+
log "Body: #{response.body.inspect}"
|
145
|
+
|
146
|
+
handle_response(response)
|
147
|
+
rescue Faraday::Error => e
|
148
|
+
log "=== Network Error ==="
|
149
|
+
log "Error class: #{e.class}"
|
150
|
+
log "Error message: #{e.message}"
|
151
|
+
log "Backtrace:"
|
152
|
+
e.backtrace.each { |line| log " #{line}" }
|
153
|
+
raise NetworkError, "Network error: #{e.message} (#{e.class})"
|
154
|
+
end
|
155
|
+
|
156
|
+
def handle_response(response)
|
157
|
+
data = response.body
|
158
|
+
|
159
|
+
unless response.success?
|
160
|
+
error_code = case response.status
|
161
|
+
when 401 then "INVALID_API_KEY"
|
162
|
+
when 403 then "FORBIDDEN"
|
163
|
+
when 404 then "NOT_FOUND"
|
164
|
+
when 429 then "RATE_LIMITED"
|
165
|
+
else "API_ERROR"
|
166
|
+
end
|
167
|
+
|
168
|
+
error_msg = if data.is_a?(Hash)
|
169
|
+
data["error"] || data["message"] || "Unknown API error"
|
170
|
+
else
|
171
|
+
"Invalid response format: #{data.inspect}"
|
172
|
+
end
|
173
|
+
|
174
|
+
log "=== Error Response ==="
|
175
|
+
log "Status code: #{response.status}"
|
176
|
+
log "Error code: #{error_code}"
|
177
|
+
log "Error message: #{error_msg}"
|
178
|
+
|
179
|
+
case error_code
|
180
|
+
when "NOT_FOUND"
|
181
|
+
raise NotFoundError, error_msg
|
182
|
+
when "INVALID_API_KEY"
|
183
|
+
raise InvalidAPIKeyError, error_msg
|
184
|
+
when "FORBIDDEN"
|
185
|
+
raise InvalidAPIKeyError, "Invalid or missing API key"
|
186
|
+
when "RATE_LIMITED"
|
187
|
+
raise APIError.new("Rate limit exceeded", error_code)
|
188
|
+
else
|
189
|
+
raise APIError.new(error_msg, error_code)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
data
|
194
|
+
end
|
195
|
+
|
196
|
+
def log(message)
|
197
|
+
@logger&.debug("[Sendrly] #{message}")
|
198
|
+
end
|
199
|
+
|
200
|
+
def config_for_log
|
201
|
+
{ api_key: "#{api_key[0..5]}...#{api_key[-5..]}", debug: debug }
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/validation"
|
4
|
+
|
5
|
+
module Sendrly
|
6
|
+
module Contracts
|
7
|
+
class Config < Dry::Validation::Contract
|
8
|
+
params do
|
9
|
+
required(:api_key).filled(:string)
|
10
|
+
optional(:debug).filled(:bool)
|
11
|
+
end
|
12
|
+
|
13
|
+
rule(:api_key) do
|
14
|
+
key.failure("must not be empty") if value.strip.empty?
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.call(params)
|
18
|
+
new.call(params)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Email < Dry::Validation::Contract
|
23
|
+
EMAIL_REGEX = /\A[^@\s]+@[^@\s]+\.[^@\s]+\z/
|
24
|
+
|
25
|
+
params do
|
26
|
+
required(:email).filled(:string)
|
27
|
+
end
|
28
|
+
|
29
|
+
rule(:email) do
|
30
|
+
key.failure("must be a valid email address") unless EMAIL_REGEX.match?(value)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.call(params)
|
34
|
+
new.call(params)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class EventName < Dry::Validation::Contract
|
39
|
+
params do
|
40
|
+
required(:event).filled(:string)
|
41
|
+
end
|
42
|
+
|
43
|
+
rule(:event) do
|
44
|
+
key.failure("must not be empty") if value.strip.empty?
|
45
|
+
key.failure("must be a valid event name (e.g., 'category.event')") unless value.include?(".")
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.call(params)
|
49
|
+
new.call(params)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class EventPayload < Dry::Validation::Contract
|
54
|
+
EMAIL_REGEX = /\A[^@\s]+@[^@\s]+\.[^@\s]+\z/
|
55
|
+
|
56
|
+
ALLOWED_PROPERTY_TYPES = [
|
57
|
+
String,
|
58
|
+
Integer,
|
59
|
+
Float,
|
60
|
+
TrueClass,
|
61
|
+
FalseClass,
|
62
|
+
NilClass
|
63
|
+
].freeze
|
64
|
+
|
65
|
+
params do
|
66
|
+
required(:email).filled(:string)
|
67
|
+
required(:event).filled(:string)
|
68
|
+
optional(:marketing_opt_out).filled(:bool)
|
69
|
+
optional(:contact_properties).maybe(:hash)
|
70
|
+
optional(:event_properties).maybe(:hash)
|
71
|
+
end
|
72
|
+
|
73
|
+
rule(:email) do
|
74
|
+
key.failure("must be a valid email address") unless EMAIL_REGEX.match?(value)
|
75
|
+
end
|
76
|
+
|
77
|
+
rule(:event) do
|
78
|
+
key.failure("must not be empty") if value.strip.empty?
|
79
|
+
key.failure("must be a valid event name (e.g., 'category.event')") unless value.include?(".")
|
80
|
+
end
|
81
|
+
|
82
|
+
rule(:contact_properties) do
|
83
|
+
if value && !value.empty?
|
84
|
+
validate_properties(key, value, "contact_properties")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
rule(:event_properties) do
|
89
|
+
if value && !value.empty?
|
90
|
+
validate_properties(key, value, "event_properties")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.call(params)
|
95
|
+
params = {
|
96
|
+
marketing_opt_out: false,
|
97
|
+
contact_properties: {},
|
98
|
+
event_properties: {}
|
99
|
+
}.merge(params)
|
100
|
+
new.call(params)
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def validate_properties(key, value, field_name)
|
106
|
+
unless value.is_a?(Hash)
|
107
|
+
key.failure("#{field_name} must be a hash")
|
108
|
+
return
|
109
|
+
end
|
110
|
+
|
111
|
+
value.each do |prop_key, prop_value|
|
112
|
+
# Check key format
|
113
|
+
unless prop_key.is_a?(String) || prop_key.is_a?(Symbol)
|
114
|
+
key.failure("#{field_name} keys must be strings or symbols, got #{prop_key.class} for '#{prop_key}'")
|
115
|
+
next
|
116
|
+
end
|
117
|
+
|
118
|
+
# Check value type
|
119
|
+
unless ALLOWED_PROPERTY_TYPES.any? { |type| prop_value.is_a?(type) }
|
120
|
+
key.failure(
|
121
|
+
"#{field_name}['#{prop_key}'] has invalid type: #{prop_value.class}. " \
|
122
|
+
"Allowed types are: #{ALLOWED_PROPERTY_TYPES.map(&:name).join(', ')}"
|
123
|
+
)
|
124
|
+
next
|
125
|
+
end
|
126
|
+
|
127
|
+
# Additional validation for strings and numbers
|
128
|
+
case prop_value
|
129
|
+
when String
|
130
|
+
if prop_value.length > 1000
|
131
|
+
key.failure("#{field_name}['#{prop_key}'] string is too long (max 1000 characters)")
|
132
|
+
end
|
133
|
+
when Integer, Float
|
134
|
+
if prop_value.abs > 1e15
|
135
|
+
key.failure("#{field_name}['#{prop_key}'] number is too large (max 1e15)")
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sendrly
|
4
|
+
class Error < StandardError; end
|
5
|
+
|
6
|
+
class ValidationError < Error
|
7
|
+
attr_reader :errors
|
8
|
+
|
9
|
+
def initialize(errors)
|
10
|
+
@errors = errors
|
11
|
+
super("Validation failed: #{errors}")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class APIError < Error
|
16
|
+
attr_reader :code
|
17
|
+
|
18
|
+
def initialize(message, code)
|
19
|
+
@code = code
|
20
|
+
super(message)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class NetworkError < Error; end
|
25
|
+
|
26
|
+
class NotFoundError < Error
|
27
|
+
def code
|
28
|
+
"NOT_FOUND"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class InvalidAPIKeyError < Error
|
33
|
+
def code
|
34
|
+
"INVALID_API_KEY"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/sendrly.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "zeitwerk"
|
4
|
+
require_relative "sendrly/errors"
|
5
|
+
require_relative "sendrly/contracts"
|
6
|
+
require_relative "sendrly/client"
|
7
|
+
|
8
|
+
loader = Zeitwerk::Loader.for_gem
|
9
|
+
loader.setup
|
10
|
+
|
11
|
+
module Sendrly
|
12
|
+
def self.new(api_key:, debug: false)
|
13
|
+
Client.new(api_key: api_key, debug: debug)
|
14
|
+
end
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sendrly
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sendrly Team
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-05-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: faraday
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: dry-validation
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.10'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.10'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: zeitwerk
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.6'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.6'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '13.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '13.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: dotenv
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '2.8'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '2.8'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rubocop
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '1.50'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '1.50'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: yard
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0.9'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0.9'
|
139
|
+
description: Official Ruby SDK for Sendrly - Simple, type-safe email automation for
|
140
|
+
developers
|
141
|
+
email:
|
142
|
+
- team@sendrly.com
|
143
|
+
executables: []
|
144
|
+
extensions: []
|
145
|
+
extra_rdoc_files: []
|
146
|
+
files:
|
147
|
+
- README.md
|
148
|
+
- lib/sendrly.rb
|
149
|
+
- lib/sendrly/client.rb
|
150
|
+
- lib/sendrly/contracts.rb
|
151
|
+
- lib/sendrly/errors.rb
|
152
|
+
- lib/sendrly/version.rb
|
153
|
+
homepage: https://github.com/Sendrly/sendrly-sdk-ruby
|
154
|
+
licenses:
|
155
|
+
- MIT
|
156
|
+
metadata:
|
157
|
+
bug_tracker_uri: https://github.com/Sendrly/sendrly-sdk-ruby/issues
|
158
|
+
changelog_uri: https://github.com/Sendrly/sendrly-sdk-ruby/blob/main/CHANGELOG.md
|
159
|
+
documentation_uri: https://github.com/Sendrly/sendrly-sdk-ruby#readme
|
160
|
+
homepage_uri: https://github.com/Sendrly/sendrly-sdk-ruby
|
161
|
+
source_code_uri: https://github.com/Sendrly/sendrly-sdk-ruby
|
162
|
+
post_install_message:
|
163
|
+
rdoc_options: []
|
164
|
+
require_paths:
|
165
|
+
- lib
|
166
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
167
|
+
requirements:
|
168
|
+
- - ">="
|
169
|
+
- !ruby/object:Gem::Version
|
170
|
+
version: 2.6.0
|
171
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
172
|
+
requirements:
|
173
|
+
- - ">="
|
174
|
+
- !ruby/object:Gem::Version
|
175
|
+
version: '0'
|
176
|
+
requirements: []
|
177
|
+
rubygems_version: 3.5.22
|
178
|
+
signing_key:
|
179
|
+
specification_version: 4
|
180
|
+
summary: Official Sendrly SDK - Simple, type-safe email automation for developers
|
181
|
+
test_files: []
|