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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +31 -0
- data/LICENSE +22 -0
- data/NOTICE +1 -0
- data/README.md +210 -0
- data/app/controllers/action_mailbox/ingresses/sendio/inbound_emails_controller.rb +95 -0
- data/lib/sendio/action_mailbox/engine.rb +19 -0
- data/lib/sendio/action_mailbox/message_builder.rb +162 -0
- data/lib/sendio/action_mailbox.rb +5 -0
- data/lib/sendio/api_keys.rb +32 -0
- data/lib/sendio/automations/runs.rb +22 -0
- data/lib/sendio/automations/templates.rb +29 -0
- data/lib/sendio/automations.rb +64 -0
- data/lib/sendio/batch.rb +36 -0
- data/lib/sendio/broadcasts.rb +180 -0
- data/lib/sendio/client.rb +19 -0
- data/lib/sendio/contact_properties.rb +106 -0
- data/lib/sendio/contacts/imports.rb +64 -0
- data/lib/sendio/contacts/segments.rb +66 -0
- data/lib/sendio/contacts/topics.rb +80 -0
- data/lib/sendio/contacts.rb +131 -0
- data/lib/sendio/domains.rb +60 -0
- data/lib/sendio/emails/attachments.rb +67 -0
- data/lib/sendio/emails/receiving/attachments.rb +69 -0
- data/lib/sendio/emails/receiving.rb +51 -0
- data/lib/sendio/emails.rb +124 -0
- data/lib/sendio/errors.rb +59 -0
- data/lib/sendio/events.rb +46 -0
- data/lib/sendio/logs.rb +20 -0
- data/lib/sendio/mailboxes.rb +87 -0
- data/lib/sendio/mailer.rb +241 -0
- data/lib/sendio/multipart_request.rb +67 -0
- data/lib/sendio/oauth_grants.rb +20 -0
- data/lib/sendio/pagination_helper.rb +30 -0
- data/lib/sendio/railtie.rb +14 -0
- data/lib/sendio/request.rb +122 -0
- data/lib/sendio/response.rb +154 -0
- data/lib/sendio/segments.rb +38 -0
- data/lib/sendio/suppressions/batch.rb +44 -0
- data/lib/sendio/suppressions.rb +64 -0
- data/lib/sendio/templates.rb +50 -0
- data/lib/sendio/topics.rb +38 -0
- data/lib/sendio/usage.rb +14 -0
- data/lib/sendio/version.rb +5 -0
- data/lib/sendio/webhooks.rb +329 -0
- data/lib/sendio.rb +78 -0
- 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: []
|