sevk 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 +75 -0
- data/Rakefile +42 -0
- data/lib/sevk/client.rb +164 -0
- data/lib/sevk/error.rb +40 -0
- data/lib/sevk/markup/renderer.rb +516 -0
- data/lib/sevk/resources/audiences.rb +41 -0
- data/lib/sevk/resources/base.rb +22 -0
- data/lib/sevk/resources/broadcasts.rb +18 -0
- data/lib/sevk/resources/contacts.rb +40 -0
- data/lib/sevk/resources/domains.rb +18 -0
- data/lib/sevk/resources/emails.rb +24 -0
- data/lib/sevk/resources/segments.rb +35 -0
- data/lib/sevk/resources/subscriptions.rb +23 -0
- data/lib/sevk/resources/templates.rb +37 -0
- data/lib/sevk/resources/topics.rb +35 -0
- data/lib/sevk/version.rb +5 -0
- data/lib/sevk.rb +213 -0
- data/sevk.gemspec +40 -0
- metadata +174 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 6c7f36600a0d24e9b0125300b35f125d85c3c2d116902386c4336c1969c539b3
|
|
4
|
+
data.tar.gz: 99ca2a8b06fba11e1d604ea021cd8664b5cd5cd48a01eaef4a0fd38219acf9d2
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ede0ed66474c75ed2544e0e5c1d41013946af97d81728ecdccedc7b1022a2d9ccd5bc8a47f67c99bc5b8a1038f4bb1fb84983024a12d8f08e070565f61bcb70b
|
|
7
|
+
data.tar.gz: eab455f069c6d246b19385ca7842c66f14d16781fd837d0c5d05c2bcf894973b81eee0ff1d96886c1a4a2e4c6b9b6cad18062ca8a5c87b26c5a2937f2923fbfb
|
data/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
> [!WARNING]
|
|
2
|
+
> Sevk is currently in private beta. This SDK is not yet available for public use.
|
|
3
|
+
> Join the waitlist at [sevk.io](https://sevk.io) to get early access.
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<img src="https://sevk.io/logo.png" alt="Sevk" width="120" />
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<h1 align="center">Sevk Ruby SDK</h1>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
Official Ruby SDK for <a href="https://sevk.io">Sevk</a> email platform.
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
<p align="center">
|
|
16
|
+
<a href="https://docs.sevk.io">Documentation</a> •
|
|
17
|
+
<a href="https://sevk.io">Website</a>
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
gem install sevk
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Send Email
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
require 'sevk'
|
|
30
|
+
|
|
31
|
+
client = Sevk::Client.new('your-api-key')
|
|
32
|
+
|
|
33
|
+
client.emails.send(
|
|
34
|
+
to: 'recipient@example.com',
|
|
35
|
+
from: 'hello@yourdomain.com',
|
|
36
|
+
subject: 'Hello from Sevk!',
|
|
37
|
+
html: '<h1>Welcome!</h1>'
|
|
38
|
+
)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Send Email with Markup
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
require 'sevk'
|
|
45
|
+
|
|
46
|
+
client = Sevk::Client.new('your-api-key')
|
|
47
|
+
|
|
48
|
+
html = Sevk::Markup.render(<<~MARKUP
|
|
49
|
+
<section padding="40px 20px" background-color="#f8f9fa">
|
|
50
|
+
<container max-width="600px">
|
|
51
|
+
<heading level="1" color="#1a1a1a">Welcome!</heading>
|
|
52
|
+
<paragraph color="#666666">Thanks for signing up.</paragraph>
|
|
53
|
+
<button href="https://example.com" background-color="#5227FF" color="#ffffff" padding="12px 24px">
|
|
54
|
+
Get Started
|
|
55
|
+
</button>
|
|
56
|
+
</container>
|
|
57
|
+
</section>
|
|
58
|
+
MARKUP
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
client.emails.send(
|
|
62
|
+
to: 'recipient@example.com',
|
|
63
|
+
from: 'hello@yourdomain.com',
|
|
64
|
+
subject: 'Welcome!',
|
|
65
|
+
html: html
|
|
66
|
+
)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Documentation
|
|
70
|
+
|
|
71
|
+
For full documentation, visit [docs.sevk.io](https://docs.sevk.io)
|
|
72
|
+
|
|
73
|
+
## License
|
|
74
|
+
|
|
75
|
+
MIT
|
data/Rakefile
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "rspec/core/rake_task"
|
|
5
|
+
|
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
7
|
+
|
|
8
|
+
task default: :spec
|
|
9
|
+
|
|
10
|
+
desc "Update all dependencies to latest versions"
|
|
11
|
+
task :update do
|
|
12
|
+
sh "bundle update"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
desc "Update dependencies and show outdated gems"
|
|
16
|
+
task :outdated do
|
|
17
|
+
sh "bundle outdated"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
desc "Run tests"
|
|
21
|
+
task :test => :spec
|
|
22
|
+
|
|
23
|
+
desc "Build the gem"
|
|
24
|
+
task :build do
|
|
25
|
+
sh "gem build sevk.gemspec"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
desc "Install the gem locally"
|
|
29
|
+
task :install => :build do
|
|
30
|
+
sh "gem install sevk-*.gem"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
desc "Release the gem to RubyGems"
|
|
34
|
+
task :release => :build do
|
|
35
|
+
sh "gem push sevk-*.gem"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
desc "Clean up build artifacts"
|
|
39
|
+
task :clean do
|
|
40
|
+
FileUtils.rm_f Dir.glob("sevk-*.gem")
|
|
41
|
+
FileUtils.rm_rf "pkg"
|
|
42
|
+
end
|
data/lib/sevk/client.rb
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sevk
|
|
4
|
+
class Client
|
|
5
|
+
attr_reader :api_key, :base_url
|
|
6
|
+
|
|
7
|
+
def initialize(api_key:, base_url: nil)
|
|
8
|
+
@api_key = api_key
|
|
9
|
+
@base_url = base_url || DEFAULT_BASE_URL
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def contacts
|
|
13
|
+
@contacts ||= Resources::Contacts.new(self)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def audiences
|
|
17
|
+
@audiences ||= Resources::Audiences.new(self)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def templates
|
|
21
|
+
@templates ||= Resources::Templates.new(self)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def broadcasts
|
|
25
|
+
@broadcasts ||= Resources::Broadcasts.new(self)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def domains
|
|
29
|
+
@domains ||= Resources::Domains.new(self)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def topics
|
|
33
|
+
@topics ||= Resources::Topics.new(self)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def segments
|
|
37
|
+
@segments ||= Resources::Segments.new(self)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def subscriptions
|
|
41
|
+
@subscriptions ||= Resources::Subscriptions.new(self)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def emails
|
|
45
|
+
@emails ||= Resources::Emails.new(self)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def get(path, params = {})
|
|
49
|
+
request(:get, path, params)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def post(path, body = {})
|
|
53
|
+
request(:post, path, body)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def patch(path, body = {})
|
|
57
|
+
request(:patch, path, body)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def put(path, body = {})
|
|
61
|
+
request(:put, path, body)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def delete(path)
|
|
65
|
+
request(:delete, path)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def connection
|
|
71
|
+
@connection ||= Faraday.new do |conn|
|
|
72
|
+
conn.request :json
|
|
73
|
+
conn.response :json, content_type: /\bjson$/
|
|
74
|
+
conn.request :retry, max: 2, interval: 0.5, backoff_factor: 2
|
|
75
|
+
conn.headers["Authorization"] = "Bearer #{api_key}"
|
|
76
|
+
conn.headers["Content-Type"] = "application/json"
|
|
77
|
+
conn.headers["Accept"] = "application/json"
|
|
78
|
+
conn.adapter Faraday.default_adapter
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def build_url(path)
|
|
83
|
+
# Ensure base_url ends without slash and path starts with slash
|
|
84
|
+
base = base_url.chomp("/")
|
|
85
|
+
path = "/#{path}" unless path.start_with?("/")
|
|
86
|
+
"#{base}#{path}"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def request(method, path, body = nil)
|
|
90
|
+
url = build_url(path)
|
|
91
|
+
response = case method
|
|
92
|
+
when :get
|
|
93
|
+
connection.get(url, body)
|
|
94
|
+
when :post
|
|
95
|
+
connection.post(url, body)
|
|
96
|
+
when :patch
|
|
97
|
+
connection.patch(url, body)
|
|
98
|
+
when :put
|
|
99
|
+
connection.put(url, body)
|
|
100
|
+
when :delete
|
|
101
|
+
connection.delete(url)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
handle_response(response)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def handle_response(response)
|
|
108
|
+
case response.status
|
|
109
|
+
when 200..299
|
|
110
|
+
response.body
|
|
111
|
+
when 400
|
|
112
|
+
raise ValidationError.new(
|
|
113
|
+
error_message(response),
|
|
114
|
+
status_code: response.status,
|
|
115
|
+
response: response.body
|
|
116
|
+
)
|
|
117
|
+
when 401
|
|
118
|
+
raise AuthenticationError.new(
|
|
119
|
+
error_message(response),
|
|
120
|
+
status_code: response.status,
|
|
121
|
+
response: response.body
|
|
122
|
+
)
|
|
123
|
+
when 403
|
|
124
|
+
raise Error.new(
|
|
125
|
+
error_message(response),
|
|
126
|
+
status_code: response.status,
|
|
127
|
+
response: response.body
|
|
128
|
+
)
|
|
129
|
+
when 404
|
|
130
|
+
raise NotFoundError.new(
|
|
131
|
+
error_message(response),
|
|
132
|
+
status_code: response.status,
|
|
133
|
+
response: response.body
|
|
134
|
+
)
|
|
135
|
+
when 429
|
|
136
|
+
raise RateLimitError.new(
|
|
137
|
+
error_message(response),
|
|
138
|
+
status_code: response.status,
|
|
139
|
+
response: response.body
|
|
140
|
+
)
|
|
141
|
+
when 500..599
|
|
142
|
+
raise ServerError.new(
|
|
143
|
+
error_message(response),
|
|
144
|
+
status_code: response.status,
|
|
145
|
+
response: response.body
|
|
146
|
+
)
|
|
147
|
+
else
|
|
148
|
+
raise Error.new(
|
|
149
|
+
error_message(response),
|
|
150
|
+
status_code: response.status,
|
|
151
|
+
response: response.body
|
|
152
|
+
)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def error_message(response)
|
|
157
|
+
if response.body.is_a?(Hash) && response.body["message"]
|
|
158
|
+
"#{response.status}: #{response.body['message']}"
|
|
159
|
+
else
|
|
160
|
+
"#{response.status}: #{response.body}"
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
data/lib/sevk/error.rb
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sevk
|
|
4
|
+
class Error < StandardError
|
|
5
|
+
attr_reader :status_code, :code, :response
|
|
6
|
+
|
|
7
|
+
def initialize(message = nil, status_code: nil, code: nil, response: nil)
|
|
8
|
+
@status_code = status_code
|
|
9
|
+
@code = code
|
|
10
|
+
@response = response
|
|
11
|
+
super(message)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def not_found?
|
|
15
|
+
status_code == 404
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def unauthorized?
|
|
19
|
+
status_code == 401
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def forbidden?
|
|
23
|
+
status_code == 403
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def bad_request?
|
|
27
|
+
status_code == 400
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def server_error?
|
|
31
|
+
status_code.to_i >= 500 && status_code.to_i < 600
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class AuthenticationError < Error; end
|
|
36
|
+
class NotFoundError < Error; end
|
|
37
|
+
class ValidationError < Error; end
|
|
38
|
+
class RateLimitError < Error; end
|
|
39
|
+
class ServerError < Error; end
|
|
40
|
+
end
|