reloop 1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5a923721bade7783b51456350a1b02bd5a39f1ca07d99c17e78b359bf27459a7
4
+ data.tar.gz: b3246aa3911e52834cb6a3e4ab7053467222b2918598c550c670681b127f640d
5
+ SHA512:
6
+ metadata.gz: 950029dadbf2e7fe00f38a7edcf0144e5205715d69757f2fae5d9371a07d367e6d083f2e29ed89415a00a309dfc6413ccc57502d297c8aac293d26c6b685450b
7
+ data.tar.gz: c9b11a03e2c41de33f3e82e6877ff0f70b970ea33e85890e4dac826668197fa941e24186ab3f377963e5b746bc27c6869ff52a758aaa07135b54a256b6b5618b
data/LICENSE ADDED
@@ -0,0 +1,36 @@
1
+ Copyright (c) 2025 Reloop Labs
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
14
+
15
+ ----------------------------------------------------------------------
16
+ ADDITIONAL USE RESTRICTIONS (Custom Clause by Reloop Labs)
17
+ ----------------------------------------------------------------------
18
+
19
+ 1. You are free to use, copy, modify, and distribute this software for
20
+ personal use and internal company purposes.
21
+
22
+ 2. You are NOT permitted to:
23
+ - Sell, sublicense, or otherwise commercially redistribute this software.
24
+ - Offer this software, or any modified version of it, as a hosted service
25
+ (including but not limited to Software-as-a-Service, Platform-as-a-Service,
26
+ or any similar commercial hosting model).
27
+ - Use this software in any product or service whose primary purpose is to
28
+ compete with Reloop Labs.
29
+
30
+ 3. Reloop Labs offers Reloop as a hosted email service, or you may
31
+ self-host the open-source software on your own infrastructure.
32
+ There is no commercial license for third parties to resell or
33
+ offer competing hosted services using this software.
34
+
35
+ For questions about the license or project, contact:
36
+ reloop.sh@gmail.com
@@ -0,0 +1,58 @@
1
+ module Reloop
2
+ class Error < StandardError; end
3
+
4
+ class Client
5
+ attr_reader :api_key, :base_url
6
+
7
+ def initialize(api_key:, base_url: "https://reloop.sh")
8
+ raise ArgumentError, "Reloop SDK requires an api_key" if api_key.nil? || api_key.empty?
9
+
10
+ @api_key = api_key
11
+ @base_url = base_url
12
+ end
13
+
14
+ def fetch(method, path, body = nil, params = nil)
15
+ conn = Faraday.new(url: @base_url) do |f|
16
+ f.headers["x-api-key"] = @api_key
17
+ f.headers["Content-Type"] = "application/json"
18
+ f.headers["Accept"] = "application/json"
19
+ f.adapter Faraday.default_adapter
20
+ end
21
+
22
+ response = conn.send(method) do |req|
23
+ req.url path
24
+ req.params = params if params
25
+ req.body = body.to_json if body
26
+ end
27
+
28
+ unless response.success?
29
+ error_body = begin
30
+ JSON.parse(response.body)
31
+ rescue StandardError
32
+ {}
33
+ end
34
+ raise Error, "Reloop API Error: #{response.status} #{response.reason_phrase}. #{error_body}"
35
+ end
36
+
37
+ return {} if response.status == 204
38
+
39
+ JSON.parse(response.body)
40
+ end
41
+
42
+ def api_keys
43
+ @api_keys ||= Services::ApiKey.new(self)
44
+ end
45
+
46
+ def domain
47
+ @domain ||= Services::Domain.new(self)
48
+ end
49
+
50
+ def contacts
51
+ @contacts ||= Services::Contacts.new(self)
52
+ end
53
+
54
+ def mail
55
+ @mail ||= Services::Mail.new(self)
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,52 @@
1
+ require_relative "../support/parameters"
2
+
3
+ module Reloop
4
+ module Services
5
+ class ApiKey
6
+ def initialize(client)
7
+ @client = client
8
+ end
9
+
10
+ def create(params)
11
+ @client.fetch(:post, "/api/api-key/v1/", params)
12
+ end
13
+
14
+ def list(params = {})
15
+ @client.fetch(
16
+ :get,
17
+ "/api/api-key/v1/",
18
+ nil,
19
+ Support::Parameters.for_query(params),
20
+ )
21
+ end
22
+
23
+ def get(id)
24
+ @client.fetch(:get, "/api/api-key/v1/#{id}")
25
+ end
26
+
27
+ def update(id, params)
28
+ @client.fetch(:patch, "/api/api-key/v1/#{id}", params)
29
+ end
30
+
31
+ def delete(id)
32
+ @client.fetch(:delete, "/api/api-key/v1/#{id}")
33
+ end
34
+
35
+ def rotate(id)
36
+ @client.fetch(:post, "/api/api-key/v1/rotate/#{id}")
37
+ end
38
+
39
+ def enable(id)
40
+ @client.fetch(:post, "/api/api-key/v1/enable/#{id}")
41
+ end
42
+
43
+ def disable(id)
44
+ @client.fetch(:post, "/api/api-key/v1/disable/#{id}")
45
+ end
46
+
47
+ def pause(id)
48
+ disable(id)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,60 @@
1
+ require_relative "../support/parameters"
2
+
3
+ module Reloop
4
+ module Services
5
+ class ContactChannels
6
+ def initialize(client)
7
+ @client = client
8
+ end
9
+
10
+ def create(params)
11
+ @client.fetch(
12
+ :post,
13
+ "/api/contacts/v1/channels/create",
14
+ Support::Parameters.for_request(params),
15
+ )
16
+ end
17
+
18
+ def list(params = {})
19
+ @client.fetch(
20
+ :get,
21
+ "/api/contacts/v1/channels/list",
22
+ nil,
23
+ Support::Parameters.for_query(params),
24
+ )
25
+ end
26
+
27
+ def get(channel_id)
28
+ @client.fetch(:get, "/api/contacts/v1/channels/#{channel_id}")
29
+ end
30
+
31
+ def update(channel_id, params)
32
+ @client.fetch(
33
+ :patch,
34
+ "/api/contacts/v1/channels/#{channel_id}",
35
+ Support::Parameters.for_request(params),
36
+ )
37
+ end
38
+
39
+ def delete(channel_id)
40
+ @client.fetch(:delete, "/api/contacts/v1/channels/#{channel_id}")
41
+ end
42
+
43
+ def add_contact(channel_id, params)
44
+ @client.fetch(
45
+ :post,
46
+ "/api/contacts/channel/#{channel_id}",
47
+ Support::Parameters.for_request(params),
48
+ )
49
+ end
50
+
51
+ def update_subscription(channel_id, params)
52
+ @client.fetch(
53
+ :patch,
54
+ "/api/contacts/channel/#{channel_id}",
55
+ Support::Parameters.for_request(params),
56
+ )
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,36 @@
1
+ require_relative "../support/parameters"
2
+
3
+ module Reloop
4
+ module Services
5
+ class ContactGroups
6
+ def initialize(client)
7
+ @client = client
8
+ end
9
+
10
+ def add_contact(group_id, params)
11
+ @client.fetch(
12
+ :post,
13
+ "/api/contacts/group/#{group_id}",
14
+ Support::Parameters.for_request(params),
15
+ )
16
+ end
17
+
18
+ def remove_contact(group_id, params)
19
+ @client.fetch(
20
+ :delete,
21
+ "/api/contacts/group/#{group_id}",
22
+ Support::Parameters.for_request(params),
23
+ )
24
+ end
25
+
26
+ def list_contacts(group_id, params = {})
27
+ @client.fetch(
28
+ :get,
29
+ "/api/contacts/v1/groups/#{group_id}/contacts",
30
+ nil,
31
+ Support::Parameters.for_query(params),
32
+ )
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,115 @@
1
+ require_relative "../support/parameters"
2
+ require_relative "contact_groups"
3
+ require_relative "contact_channels"
4
+
5
+ module Reloop
6
+ module Services
7
+ class Contacts
8
+ attr_reader :groups, :channels
9
+
10
+ def initialize(client)
11
+ @client = client
12
+ @groups = ContactGroups.new(client)
13
+ @channels = ContactChannels.new(client)
14
+ end
15
+
16
+ def create(params)
17
+ @client.fetch(
18
+ :post,
19
+ "/api/contacts/create",
20
+ Support::Parameters.for_request(params),
21
+ )
22
+ end
23
+
24
+ def get(contact_id)
25
+ @client.fetch(:get, "/api/contacts/retrieve/#{contact_id}")
26
+ end
27
+
28
+ def list(params = {})
29
+ query = Support::Parameters.for_query(params)
30
+ group_id = query["groupId"] || query["group_id"]
31
+
32
+ if group_id
33
+ filtered = query.reject { |key, _| %w[groupId group_id].include?(key) }
34
+ return @groups.list_contacts(group_id, filtered)
35
+ end
36
+
37
+ @client.fetch(:get, "/api/contacts/list", nil, query)
38
+ end
39
+
40
+ def update(contact_id, params)
41
+ @client.fetch(
42
+ :patch,
43
+ "/api/contacts/#{contact_id}",
44
+ Support::Parameters.for_request(params),
45
+ )
46
+ end
47
+
48
+ def delete(contact_id)
49
+ @client.fetch(:delete, "/api/contacts/#{contact_id}")
50
+ end
51
+
52
+ def create_property(params)
53
+ @client.fetch(
54
+ :post,
55
+ "/api/contacts/v1/properties/create",
56
+ Support::Parameters.for_request(params),
57
+ )
58
+ end
59
+
60
+ def list_properties(params = {})
61
+ @client.fetch(
62
+ :get,
63
+ "/api/contacts/v1/properties/list",
64
+ nil,
65
+ Support::Parameters.for_query(params),
66
+ )
67
+ end
68
+
69
+ def update_property(property_id, params)
70
+ @client.fetch(
71
+ :patch,
72
+ "/api/contacts/v1/properties/#{property_id}",
73
+ Support::Parameters.for_request(params),
74
+ )
75
+ end
76
+
77
+ def delete_property(property_id)
78
+ @client.fetch(:delete, "/api/contacts/v1/properties/#{property_id}")
79
+ end
80
+
81
+ def create_group(params)
82
+ @client.fetch(
83
+ :post,
84
+ "/api/contacts/v1/groups/create",
85
+ Support::Parameters.for_request(params),
86
+ )
87
+ end
88
+
89
+ def list_groups(params = {})
90
+ @client.fetch(
91
+ :get,
92
+ "/api/contacts/v1/groups/list",
93
+ nil,
94
+ Support::Parameters.for_query(params),
95
+ )
96
+ end
97
+
98
+ def get_group(group_id)
99
+ @client.fetch(:get, "/api/contacts/v1/groups/#{group_id}")
100
+ end
101
+
102
+ def update_group(group_id, params)
103
+ @client.fetch(
104
+ :patch,
105
+ "/api/contacts/v1/groups/#{group_id}",
106
+ Support::Parameters.for_request(params),
107
+ )
108
+ end
109
+
110
+ def delete_group(group_id)
111
+ @client.fetch(:delete, "/api/contacts/v1/groups/#{group_id}")
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,60 @@
1
+ require_relative "../support/parameters"
2
+
3
+ module Reloop
4
+ module Services
5
+ class Domain
6
+ def initialize(client)
7
+ @client = client
8
+ end
9
+
10
+ def create(params)
11
+ @client.fetch(
12
+ :post,
13
+ "/api/domain/v1/create",
14
+ Support::Parameters.for_snake_request(params),
15
+ )
16
+ end
17
+
18
+ def list(params = {})
19
+ @client.fetch(
20
+ :get,
21
+ "/api/domain/v1/list",
22
+ nil,
23
+ Support::Parameters.for_query(params),
24
+ )
25
+ end
26
+
27
+ def get(domain_id)
28
+ @client.fetch(:get, "/api/domain/v1/#{domain_id}")
29
+ end
30
+
31
+ def get_nameservers(domain_id)
32
+ @client.fetch(:get, "/api/domain/v1/nameservers/#{domain_id}")
33
+ end
34
+
35
+ def update(domain_id, params)
36
+ @client.fetch(
37
+ :patch,
38
+ "/api/domain/v1/#{domain_id}",
39
+ Support::Parameters.for_snake_request(params),
40
+ )
41
+ end
42
+
43
+ def delete(domain_id)
44
+ @client.fetch(:delete, "/api/domain/v1/#{domain_id}")
45
+ end
46
+
47
+ def verify(domain_id)
48
+ @client.fetch(:post, "/api/domain/v1/verify/#{domain_id}")
49
+ end
50
+
51
+ def forward_dns(domain_id, params)
52
+ @client.fetch(
53
+ :post,
54
+ "/api/domain/v1/verify/#{domain_id}/forward-dns",
55
+ Support::Parameters.for_snake_request(params),
56
+ )
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,19 @@
1
+ require_relative "../support/parameters"
2
+
3
+ module Reloop
4
+ module Services
5
+ class Mail
6
+ def initialize(client)
7
+ @client = client
8
+ end
9
+
10
+ def send(params)
11
+ @client.fetch(
12
+ :post,
13
+ "/api/mail/v1/send",
14
+ Support::Parameters.for_snake_request(params),
15
+ )
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,67 @@
1
+ module Reloop
2
+ module Support
3
+ module Parameters
4
+ REQUEST_KEY_MAP = {
5
+ "first_name" => "firstName",
6
+ "last_name" => "lastName",
7
+ "group_ids" => "groupIds",
8
+ "group_id" => "groupId",
9
+ "fallback_value" => "fallbackValue",
10
+ "default_subscription" => "defaultSubscription",
11
+ "channel_id" => "channelId",
12
+ "property_name" => "propertyName",
13
+ "property_type" => "propertyType",
14
+ "contact_id" => "contactId",
15
+ "rate_limit_enabled" => "rateLimitEnabled",
16
+ "user_id" => "userId",
17
+ }.freeze
18
+
19
+ def self.for_snake_request(params)
20
+ params.each_with_object({}) do |(key, value), normalized|
21
+ next if value.nil?
22
+
23
+ normalized[key.to_s] = value
24
+ end
25
+ end
26
+
27
+ def self.for_request(params)
28
+ normalized = {}
29
+
30
+ params.each do |key, value|
31
+ if key.to_s == "unsubscribed"
32
+ normalized["status"] = value ? "unsubscribed" : "subscribed" unless params.key?(:status) || params.key?("status")
33
+ next
34
+ end
35
+
36
+ api_key = REQUEST_KEY_MAP[key.to_s] || to_camel_case(key.to_s)
37
+ normalized[api_key] = normalize_value(value, for_request: true)
38
+ end
39
+
40
+ normalized.reject { |_, v| v.nil? }
41
+ end
42
+
43
+ def self.for_query(options)
44
+ for_request(options)
45
+ end
46
+
47
+ def self.normalize_value(value, for_request:)
48
+ return value unless value.is_a?(Hash)
49
+
50
+ if value.keys.all? { |key| key.is_a?(Integer) }
51
+ return value.map { |item| item.is_a?(Hash) ? normalize_value(item, for_request: for_request) : item }
52
+ end
53
+
54
+ for_request ? for_request(value) : value
55
+ end
56
+
57
+ def self.to_camel_case(key)
58
+ return REQUEST_KEY_MAP[key] if REQUEST_KEY_MAP.key?(key)
59
+ return key unless key.include?("_")
60
+
61
+ parts = key.split("_")
62
+ parts.first + parts.drop(1).map(&:capitalize).join
63
+ end
64
+ private_class_method :normalize_value, :to_camel_case
65
+ end
66
+ end
67
+ end
data/lib/reloop.rb ADDED
@@ -0,0 +1,12 @@
1
+ require "faraday"
2
+ require "json"
3
+
4
+ require_relative "reloop/client"
5
+ require_relative "reloop/support/parameters"
6
+ require_relative "reloop/services/api_key"
7
+ require_relative "reloop/services/domain"
8
+ require_relative "reloop/services/contacts"
9
+ require_relative "reloop/services/mail"
10
+
11
+ module Reloop
12
+ end
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: reloop
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Reloop Labs
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-06-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: json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
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.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ description: Reloop Ruby SDK for interacting with the Reloop API
56
+ email:
57
+ - support@reloop.sh
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - LICENSE
63
+ - lib/reloop.rb
64
+ - lib/reloop/client.rb
65
+ - lib/reloop/services/api_key.rb
66
+ - lib/reloop/services/contact_channels.rb
67
+ - lib/reloop/services/contact_groups.rb
68
+ - lib/reloop/services/contacts.rb
69
+ - lib/reloop/services/domain.rb
70
+ - lib/reloop/services/mail.rb
71
+ - lib/reloop/support/parameters.rb
72
+ homepage: https://reloop.sh
73
+ licenses:
74
+ - Apache-2.0
75
+ metadata: {}
76
+ post_install_message:
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubygems_version: 3.2.33
92
+ signing_key:
93
+ specification_version: 4
94
+ summary: Reloop Ruby SDK
95
+ test_files: []