sendio 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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +31 -0
  3. data/LICENSE +22 -0
  4. data/NOTICE +1 -0
  5. data/README.md +210 -0
  6. data/app/controllers/action_mailbox/ingresses/sendio/inbound_emails_controller.rb +95 -0
  7. data/lib/sendio/action_mailbox/engine.rb +19 -0
  8. data/lib/sendio/action_mailbox/message_builder.rb +162 -0
  9. data/lib/sendio/action_mailbox.rb +5 -0
  10. data/lib/sendio/api_keys.rb +32 -0
  11. data/lib/sendio/automations/runs.rb +22 -0
  12. data/lib/sendio/automations/templates.rb +29 -0
  13. data/lib/sendio/automations.rb +64 -0
  14. data/lib/sendio/batch.rb +36 -0
  15. data/lib/sendio/broadcasts.rb +180 -0
  16. data/lib/sendio/client.rb +19 -0
  17. data/lib/sendio/contact_properties.rb +106 -0
  18. data/lib/sendio/contacts/imports.rb +64 -0
  19. data/lib/sendio/contacts/segments.rb +66 -0
  20. data/lib/sendio/contacts/topics.rb +80 -0
  21. data/lib/sendio/contacts.rb +131 -0
  22. data/lib/sendio/domains.rb +60 -0
  23. data/lib/sendio/emails/attachments.rb +67 -0
  24. data/lib/sendio/emails/receiving/attachments.rb +69 -0
  25. data/lib/sendio/emails/receiving.rb +51 -0
  26. data/lib/sendio/emails.rb +124 -0
  27. data/lib/sendio/errors.rb +59 -0
  28. data/lib/sendio/events.rb +46 -0
  29. data/lib/sendio/logs.rb +20 -0
  30. data/lib/sendio/mailboxes.rb +87 -0
  31. data/lib/sendio/mailer.rb +241 -0
  32. data/lib/sendio/multipart_request.rb +67 -0
  33. data/lib/sendio/oauth_grants.rb +20 -0
  34. data/lib/sendio/pagination_helper.rb +30 -0
  35. data/lib/sendio/railtie.rb +14 -0
  36. data/lib/sendio/request.rb +122 -0
  37. data/lib/sendio/response.rb +154 -0
  38. data/lib/sendio/segments.rb +38 -0
  39. data/lib/sendio/suppressions/batch.rb +44 -0
  40. data/lib/sendio/suppressions.rb +64 -0
  41. data/lib/sendio/templates.rb +50 -0
  42. data/lib/sendio/topics.rb +38 -0
  43. data/lib/sendio/usage.rb +14 -0
  44. data/lib/sendio/version.rb +5 -0
  45. data/lib/sendio/webhooks.rb +329 -0
  46. data/lib/sendio.rb +78 -0
  47. metadata +119 -0
@@ -0,0 +1,329 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+ require "base64"
5
+
6
+ module Sendio
7
+ # The Webhooks module provides methods for managing webhooks via the send.io API.
8
+ # Webhooks allow you to receive real-time notifications about email events.
9
+ #
10
+ # Default tolerance for timestamp validation (5 minutes)
11
+ WEBHOOK_TOLERANCE_SECONDS = 300
12
+ #
13
+ # @example Create a webhook
14
+ # Sendio::Webhooks.create(
15
+ # endpoint: "https://webhook.example.com/handler",
16
+ # events: ["email.sent", "email.delivered", "email.bounced"]
17
+ # )
18
+ #
19
+ # @example List all webhooks
20
+ # Sendio::Webhooks.list
21
+ #
22
+ # @example Retrieve a specific webhook
23
+ # Sendio::Webhooks.get("4dd369bc-aa82-4ff3-97de-514ae3000ee0")
24
+ #
25
+ # @example Update a webhook
26
+ # Sendio::Webhooks.update(
27
+ # webhook_id: "430eed87-632a-4ea6-90db-0aace67ec228",
28
+ # endpoint: "https://new-webhook.example.com/handler",
29
+ # events: ["email.sent", "email.delivered"],
30
+ # status: "enabled"
31
+ # )
32
+ #
33
+ # @example Delete a webhook
34
+ # Sendio::Webhooks.remove("4dd369bc-aa82-4ff3-97de-514ae3000ee0")
35
+ module Webhooks
36
+ class << self
37
+ # Create a new webhook to receive real-time notifications about email events
38
+ #
39
+ # @param params [Hash] The webhook parameters
40
+ # @option params [String] :endpoint The URL where webhook events will be sent (required)
41
+ # @option params [Array<String>] :events Array of event types to subscribe to (required)
42
+ #
43
+ # @return [Hash] The webhook object containing id, object type, and signing_secret
44
+ #
45
+ # @example
46
+ # Sendio::Webhooks.create(
47
+ # endpoint: "https://webhook.example.com/handler",
48
+ # events: ["email.sent", "email.delivered", "email.bounced"]
49
+ # )
50
+ def create(params = {})
51
+ path = "webhooks"
52
+ Sendio::Request.new(path, params, "post").perform
53
+ end
54
+
55
+ # Retrieve a list of webhooks for the authenticated user
56
+ #
57
+ # @param params [Hash] The pagination parameters
58
+ # @option params [Integer] :limit Number of webhooks to retrieve (max: 100, min: 1)
59
+ # @option params [String] :after The ID after which to retrieve more webhooks (for pagination)
60
+ # @option params [String] :before The ID before which to retrieve more webhooks (for pagination)
61
+ #
62
+ # @return [Hash] A paginated list of webhook objects
63
+ #
64
+ # @example
65
+ # Sendio::Webhooks.list
66
+ #
67
+ # @example With pagination
68
+ # Sendio::Webhooks.list(limit: 20, after: "4dd369bc-aa82-4ff3-97de-514ae3000ee0")
69
+ def list(params = {})
70
+ path = Sendio::PaginationHelper.build_paginated_path("webhooks", params)
71
+ Sendio::Request.new(path, {}, "get").perform
72
+ end
73
+
74
+ # Retrieve a single webhook for the authenticated user
75
+ #
76
+ # @param webhook_id [String] The webhook ID
77
+ #
78
+ # @return [Hash] The webhook object with full details
79
+ #
80
+ # @example
81
+ # Sendio::Webhooks.get("4dd369bc-aa82-4ff3-97de-514ae3000ee0")
82
+ def get(webhook_id = "")
83
+ path = "webhooks/#{webhook_id}"
84
+ Sendio::Request.new(path, {}, "get").perform
85
+ end
86
+
87
+ # Retrieve webhook events
88
+ #
89
+ # @param webhook_id [String] The webhook ID
90
+ # @param params [Hash] The pagination parameters
91
+ # @option params [Integer] :limit Number of webhook events to retrieve (max: 100, min: 1)
92
+ # @option params [String] :after The ID after which to retrieve more webhook events
93
+ #
94
+ # @return [Hash] A paginated list of webhook events
95
+ #
96
+ # @example
97
+ # Sendio::Webhooks.list_events("4dd369bc-aa82-4ff3-97de-514ae3000ee0")
98
+ #
99
+ # @example With pagination
100
+ # Sendio::Webhooks.list_events("4dd369bc-aa82-4ff3-97de-514ae3000ee0", limit: 20, after: "msg_123")
101
+ def list_events(webhook_id, params = {})
102
+ path = Sendio::PaginationHelper.build_paginated_path("webhooks/#{webhook_id}/events", params)
103
+ Sendio::Request.new(path, {}, "get").perform
104
+ end
105
+
106
+ # Retrieve a webhook event
107
+ #
108
+ # @param webhook_id [String] The webhook ID
109
+ # @param event_id [String] The webhook event ID
110
+ #
111
+ # @return [Hash] The webhook event with full details
112
+ #
113
+ # @example
114
+ # Sendio::Webhooks.get_event("4dd369bc-aa82-4ff3-97de-514ae3000ee0", "msg_123")
115
+ def get_event(webhook_id, event_id)
116
+ path = "webhooks/#{webhook_id}/events/#{event_id}"
117
+ Sendio::Request.new(path, {}, "get").perform
118
+ end
119
+
120
+ # Replay a webhook event
121
+ #
122
+ # Queues one more delivery of the event to the webhook. Manual replays do not schedule automatic retries.
123
+ #
124
+ # @param webhook_id [String] The webhook ID
125
+ # @param event_id [String] The webhook event ID
126
+ #
127
+ # @return [Hash] The replayed webhook event id and object type
128
+ #
129
+ # @example
130
+ # Sendio::Webhooks.replay_event("4dd369bc-aa82-4ff3-97de-514ae3000ee0", "msg_123")
131
+ def replay_event(webhook_id, event_id)
132
+ path = "webhooks/#{webhook_id}/events/#{event_id}/replay"
133
+ Sendio::Request.new(path, {}, "post").perform
134
+ end
135
+
136
+ # Retrieve delivery attempts for a webhook event
137
+ #
138
+ # @param webhook_id [String] The webhook ID
139
+ # @param event_id [String] The webhook event ID
140
+ # @param params [Hash] The pagination parameters
141
+ # @option params [Integer] :limit Number of delivery attempts to retrieve (max: 100, min: 1)
142
+ # @option params [String] :after The ID after which to retrieve more delivery attempts
143
+ #
144
+ # @return [Hash] A paginated list of webhook event delivery attempts
145
+ #
146
+ # @example
147
+ # Sendio::Webhooks.list_event_attempts("4dd369bc-aa82-4ff3-97de-514ae3000ee0", "msg_123")
148
+ #
149
+ # @example With pagination
150
+ # Sendio::Webhooks.list_event_attempts(
151
+ # "4dd369bc-aa82-4ff3-97de-514ae3000ee0",
152
+ # "msg_123",
153
+ # limit: 20,
154
+ # after: "atmpt_123"
155
+ # )
156
+ def list_event_attempts(webhook_id, event_id, params = {})
157
+ base_path = "webhooks/#{webhook_id}/events/#{event_id}/attempts"
158
+ path = Sendio::PaginationHelper.build_paginated_path(base_path, params)
159
+ Sendio::Request.new(path, {}, "get").perform
160
+ end
161
+
162
+ # Update an existing webhook configuration
163
+ #
164
+ # @param params [Hash] The webhook update parameters
165
+ # @option params [String] :webhook_id The webhook ID (required)
166
+ # @option params [String] :endpoint The URL where webhook events will be sent
167
+ # @option params [Array<String>] :events Array of event types to subscribe to
168
+ # @option params [String] :status The webhook status ("enabled" or "disabled")
169
+ #
170
+ # @return [Hash] The updated webhook object
171
+ #
172
+ # @example
173
+ # Sendio::Webhooks.update(
174
+ # webhook_id: "430eed87-632a-4ea6-90db-0aace67ec228",
175
+ # endpoint: "https://new-webhook.example.com/handler",
176
+ # events: ["email.sent", "email.delivered"],
177
+ # status: "enabled"
178
+ # )
179
+ def update(params = {})
180
+ webhook_id = params.delete(:webhook_id)
181
+ path = "webhooks/#{webhook_id}"
182
+ Sendio::Request.new(path, params, "patch").perform
183
+ end
184
+
185
+ # Rotate the signing secret of a webhook
186
+ #
187
+ # Generates a new signing secret for the webhook. The previous secret keeps working for 24 hours.
188
+ #
189
+ # @param webhook_id [String] The webhook ID
190
+ #
191
+ # @return [Hash] The webhook object containing id, object type, and the new signing_secret
192
+ #
193
+ # @example
194
+ # Sendio::Webhooks.rotate_signing_secret("4dd369bc-aa82-4ff3-97de-514ae3000ee0")
195
+ def rotate_signing_secret(webhook_id)
196
+ path = "webhooks/#{webhook_id}/signing-secret/rotate"
197
+ Sendio::Request.new(path, {}, "post").perform
198
+ end
199
+
200
+ # Remove an existing webhook
201
+ #
202
+ # @param webhook_id [String] The webhook ID
203
+ #
204
+ # @return [Hash] Confirmation object with id, object type, and deleted status
205
+ #
206
+ # @example
207
+ # Sendio::Webhooks.remove("4dd369bc-aa82-4ff3-97de-514ae3000ee0")
208
+ def remove(webhook_id = "")
209
+ path = "webhooks/#{webhook_id}"
210
+ Sendio::Request.new(path, {}, "delete").perform
211
+ end
212
+
213
+ # Verify a webhook payload using HMAC-SHA256 signature validation
214
+ # This validates that the webhook request came from Sendio and hasn't been tampered with
215
+ #
216
+ # @param params [Hash] The webhook verification parameters
217
+ # @option params [String] :payload The raw webhook payload body (required)
218
+ # @option params [Hash] :headers The webhook headers containing svix-id, svix-timestamp,
219
+ # and svix-signature (required)
220
+ # @option params [String] :webhook_secret The signing secret from webhook creation (required)
221
+ #
222
+ # @return [Boolean] true if verification succeeds
223
+ # @raise [StandardError] If verification fails or required parameters are missing
224
+ #
225
+ # @example
226
+ # Sendio::Webhooks.verify(
227
+ # payload: request.body.read,
228
+ # headers: {
229
+ # svix_id: "id_1234567890abcdefghijklmnopqrstuvwxyz",
230
+ # svix_timestamp: "1616161616",
231
+ # svix_signature: "v1,signature_here"
232
+ # },
233
+ # webhook_secret: "whsec_1234567890abcdez"
234
+ # )
235
+ def verify(params = {})
236
+ payload = params[:payload]
237
+ headers = params[:headers] || {}
238
+ webhook_secret = params[:webhook_secret]
239
+
240
+ validate_required_params(payload, headers, webhook_secret)
241
+ validate_timestamp(headers[:svix_timestamp])
242
+
243
+ signed_content = "#{headers[:svix_id]}.#{headers[:svix_timestamp]}.#{payload}"
244
+ decoded_secret = decode_secret(webhook_secret)
245
+ expected_signature = generate_signature(decoded_secret, signed_content)
246
+
247
+ verify_signature(headers[:svix_signature], expected_signature)
248
+ end
249
+
250
+ private
251
+
252
+ # Validate required parameters
253
+ def validate_required_params(payload, headers, webhook_secret)
254
+ validate_payload(payload)
255
+ validate_webhook_secret(webhook_secret)
256
+ validate_headers(headers)
257
+ end
258
+
259
+ # Validate payload is present
260
+ def validate_payload(payload)
261
+ raise "payload cannot be empty" if payload.nil? || payload.empty?
262
+ end
263
+
264
+ # Validate webhook secret is present
265
+ def validate_webhook_secret(webhook_secret)
266
+ raise "webhook_secret cannot be empty" if webhook_secret.nil? || webhook_secret.empty?
267
+ end
268
+
269
+ # Validate required headers are present
270
+ def validate_headers(headers)
271
+ raise "svix-id header is required" if headers[:svix_id].nil? || headers[:svix_id].empty?
272
+ raise "svix-timestamp header is required" if headers[:svix_timestamp].nil? || headers[:svix_timestamp].empty?
273
+ raise "svix-signature header is required" if headers[:svix_signature].nil? || headers[:svix_signature].empty?
274
+ end
275
+
276
+ # Validate timestamp to prevent replay attacks
277
+ def validate_timestamp(timestamp_header)
278
+ timestamp = timestamp_header.to_i
279
+ now = Time.now.to_i
280
+ diff = now - timestamp
281
+
282
+ return unless diff > WEBHOOK_TOLERANCE_SECONDS || diff < -WEBHOOK_TOLERANCE_SECONDS
283
+
284
+ raise "Timestamp outside tolerance window: difference of #{diff} seconds"
285
+ end
286
+
287
+ # Decode the signing secret (strip whsec_ prefix and base64 decode)
288
+ def decode_secret(webhook_secret)
289
+ secret = webhook_secret.sub(/^whsec_/, "")
290
+ Base64.strict_decode64(secret)
291
+ rescue ArgumentError => e
292
+ raise "Failed to decode webhook secret: #{e.message}"
293
+ end
294
+
295
+ # Verify signature using constant-time comparison
296
+ def verify_signature(signature_header, expected_signature)
297
+ signatures = signature_header.split(" ")
298
+ signatures.each do |sig|
299
+ parts = sig.split(",", 2)
300
+ next if parts.length != 2
301
+
302
+ received_signature = parts[1]
303
+ return true if secure_compare(expected_signature, received_signature)
304
+ end
305
+
306
+ raise "No matching signature found"
307
+ end
308
+
309
+ # Generate HMAC-SHA256 signature and return it as base64
310
+ def generate_signature(secret, content)
311
+ digest = OpenSSL::HMAC.digest("sha256", secret, content)
312
+ Base64.strict_encode64(digest)
313
+ end
314
+
315
+ # Constant-time string comparison to prevent timing attacks
316
+ #
317
+ # Note: We implement this manually for Ruby 2.7 compatibility.
318
+ # Ruby 3.0+ could use OpenSSL.fixed_length_secure_compare instead.
319
+ def secure_compare(str_a, str_b)
320
+ return false if str_a.nil? || str_b.nil? || str_a.bytesize != str_b.bytesize
321
+
322
+ bytes_a = str_a.unpack("C*")
323
+ result = 0
324
+ str_b.each_byte.with_index { |byte_b, i| result |= byte_b ^ bytes_a[i] }
325
+ result.zero?
326
+ end
327
+ end
328
+ end
329
+ end
data/lib/sendio.rb ADDED
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Version
4
+ require "sendio/version"
5
+
6
+ # Utils
7
+ require "httparty"
8
+ require "json"
9
+ require "cgi"
10
+ require "erb"
11
+ require "sendio/errors"
12
+ require "sendio/response"
13
+ require "sendio/client"
14
+ require "sendio/request"
15
+ require "sendio/multipart_request"
16
+ require "sendio/pagination_helper"
17
+
18
+ # API Operations
19
+ require "sendio/segments"
20
+ require "sendio/api_keys"
21
+ require "sendio/broadcasts"
22
+ require "sendio/batch"
23
+ require "sendio/contacts"
24
+ require "sendio/contacts/imports"
25
+ require "sendio/contacts/segments"
26
+ require "sendio/contacts/topics"
27
+ require "sendio/contact_properties"
28
+ require "sendio/domains"
29
+ require "sendio/emails"
30
+ require "sendio/templates"
31
+ require "sendio/emails/receiving"
32
+ require "sendio/emails/attachments"
33
+ require "sendio/emails/receiving/attachments"
34
+ require "sendio/logs"
35
+ require "sendio/mailboxes"
36
+ require "sendio/topics"
37
+ require "sendio/webhooks"
38
+ require "sendio/oauth_grants"
39
+ require "sendio/automations"
40
+ require "sendio/automations/runs"
41
+ require "sendio/automations/templates"
42
+ require "sendio/events"
43
+ require "sendio/suppressions"
44
+ require "sendio/suppressions/batch"
45
+ require "sendio/usage"
46
+
47
+ # Rails
48
+ require "sendio/railtie" if defined?(Rails) && defined?(ActionMailer)
49
+ require "sendio/action_mailbox" if defined?(Rails) && defined?(ActionMailbox)
50
+
51
+ # Main Sendio module
52
+ module Sendio
53
+ DEFAULT_BASE_URL = "https://api.send.io"
54
+
55
+ class << self
56
+ attr_accessor :api_key
57
+ attr_writer :base_url
58
+
59
+ # The API base URL, always returned with a trailing slash.
60
+ # Resolution order: Sendio.base_url = "...", then ENV["SENDIO_BASE_URL"],
61
+ # then https://api.send.io. A trailing slash is optional everywhere.
62
+ def base_url
63
+ url = @base_url.to_s.strip
64
+ url = ENV["SENDIO_BASE_URL"].to_s.strip if url.empty?
65
+ url = DEFAULT_BASE_URL if url.empty?
66
+ "#{url.sub(%r{/+\z}, "")}/"
67
+ end
68
+
69
+ def configure
70
+ yield self if block_given?
71
+ true
72
+ end
73
+ alias config configure
74
+ end
75
+
76
+ # @deprecated Use Segments instead
77
+ Audiences = Segments
78
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sendio
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - send.io
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-11-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: base64
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: httparty
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.22.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 0.22.0
41
+ description: Send and manage email with the send.io API from Ruby and Rails (ActionMailer
42
+ delivery method included).
43
+ email: support@send.io
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - CHANGELOG.md
49
+ - LICENSE
50
+ - NOTICE
51
+ - README.md
52
+ - app/controllers/action_mailbox/ingresses/sendio/inbound_emails_controller.rb
53
+ - lib/sendio.rb
54
+ - lib/sendio/action_mailbox.rb
55
+ - lib/sendio/action_mailbox/engine.rb
56
+ - lib/sendio/action_mailbox/message_builder.rb
57
+ - lib/sendio/api_keys.rb
58
+ - lib/sendio/automations.rb
59
+ - lib/sendio/automations/runs.rb
60
+ - lib/sendio/automations/templates.rb
61
+ - lib/sendio/batch.rb
62
+ - lib/sendio/broadcasts.rb
63
+ - lib/sendio/client.rb
64
+ - lib/sendio/contact_properties.rb
65
+ - lib/sendio/contacts.rb
66
+ - lib/sendio/contacts/imports.rb
67
+ - lib/sendio/contacts/segments.rb
68
+ - lib/sendio/contacts/topics.rb
69
+ - lib/sendio/domains.rb
70
+ - lib/sendio/emails.rb
71
+ - lib/sendio/emails/attachments.rb
72
+ - lib/sendio/emails/receiving.rb
73
+ - lib/sendio/emails/receiving/attachments.rb
74
+ - lib/sendio/errors.rb
75
+ - lib/sendio/events.rb
76
+ - lib/sendio/logs.rb
77
+ - lib/sendio/mailboxes.rb
78
+ - lib/sendio/mailer.rb
79
+ - lib/sendio/multipart_request.rb
80
+ - lib/sendio/oauth_grants.rb
81
+ - lib/sendio/pagination_helper.rb
82
+ - lib/sendio/railtie.rb
83
+ - lib/sendio/request.rb
84
+ - lib/sendio/response.rb
85
+ - lib/sendio/segments.rb
86
+ - lib/sendio/suppressions.rb
87
+ - lib/sendio/suppressions/batch.rb
88
+ - lib/sendio/templates.rb
89
+ - lib/sendio/topics.rb
90
+ - lib/sendio/usage.rb
91
+ - lib/sendio/version.rb
92
+ - lib/sendio/webhooks.rb
93
+ homepage: https://send.io
94
+ licenses:
95
+ - MIT
96
+ metadata:
97
+ homepage_uri: https://send.io
98
+ documentation_uri: https://developer.send.io/docs/api
99
+ rubygems_mfa_required: 'true'
100
+ post_install_message:
101
+ rdoc_options: []
102
+ require_paths:
103
+ - lib
104
+ required_ruby_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '3.2'
109
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ requirements: []
115
+ rubygems_version: 3.0.3.1
116
+ signing_key:
117
+ specification_version: 4
118
+ summary: The official Ruby and Rails SDK for send.io
119
+ test_files: []