sendly 3.15.3 → 3.18.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 +4 -4
- data/Gemfile.lock +1 -1
- data/lib/sendly/client.rb +62 -0
- data/lib/sendly/media.rb +16 -0
- data/lib/sendly/messages.rb +2 -1
- data/lib/sendly/types.rb +35 -1
- data/lib/sendly/version.rb +1 -1
- data/lib/sendly.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b45492ba74b05f9d480cea6a2a911c8b6ec9b66c5e197230a0a10ea81e4692f9
|
|
4
|
+
data.tar.gz: 981f6d67200e9725c9e0f546d65a9b575a3cfc4a1d5b935bd8f0c5c2034a1dd1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7724bb3862a1b21aab27d4470b41b468585bfbce1fc73eb4f62c851afd710b8e24b54362cd2da64d6e3833b859717510220f4ccf502786330d04950f056fbe8f
|
|
7
|
+
data.tar.gz: e4319bb9b9a322d2b119e1dd1c6da32e68e4c178e2006a2987058a7a5af6112fde9a590aaeee18691ea7427b3b06f0e31be1a351d5e1d2b4ba51c9c89f12987f
|
data/Gemfile.lock
CHANGED
data/lib/sendly/client.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "net/http"
|
|
4
4
|
require "uri"
|
|
5
5
|
require "json"
|
|
6
|
+
require "securerandom"
|
|
6
7
|
|
|
7
8
|
module Sendly
|
|
8
9
|
# Main Sendly API client
|
|
@@ -73,6 +74,13 @@ module Sendly
|
|
|
73
74
|
@templates ||= TemplatesResource.new(self)
|
|
74
75
|
end
|
|
75
76
|
|
|
77
|
+
# Access the Media resource
|
|
78
|
+
#
|
|
79
|
+
# @return [Sendly::Media]
|
|
80
|
+
def media
|
|
81
|
+
@media ||= Media.new(self)
|
|
82
|
+
end
|
|
83
|
+
|
|
76
84
|
# Access the Campaigns resource
|
|
77
85
|
#
|
|
78
86
|
# @return [Sendly::CampaignsResource]
|
|
@@ -122,6 +130,60 @@ module Sendly
|
|
|
122
130
|
request(:delete, path)
|
|
123
131
|
end
|
|
124
132
|
|
|
133
|
+
# Make a multipart POST request for file uploads
|
|
134
|
+
#
|
|
135
|
+
# @param path [String] API path
|
|
136
|
+
# @param file [String, IO] File path or IO object
|
|
137
|
+
# @param content_type [String] MIME type of the file
|
|
138
|
+
# @param filename [String] Name for the uploaded file
|
|
139
|
+
# @return [Hash] Response body
|
|
140
|
+
def post_multipart(path, file, content_type: "image/jpeg", filename: "upload.jpg")
|
|
141
|
+
uri = build_uri(path, {})
|
|
142
|
+
http = build_http(uri)
|
|
143
|
+
|
|
144
|
+
boundary = "SendlyRuby#{SecureRandom.hex(16)}"
|
|
145
|
+
|
|
146
|
+
file_data = file.is_a?(String) ? File.binread(file) : file.read
|
|
147
|
+
|
|
148
|
+
body = []
|
|
149
|
+
body << "--#{boundary}\r\n"
|
|
150
|
+
body << "Content-Disposition: form-data; name=\"file\"; filename=\"#{filename}\"\r\n"
|
|
151
|
+
body << "Content-Type: #{content_type}\r\n\r\n"
|
|
152
|
+
body << file_data
|
|
153
|
+
body << "\r\n--#{boundary}--\r\n"
|
|
154
|
+
|
|
155
|
+
req = Net::HTTP::Post.new(uri)
|
|
156
|
+
req["Authorization"] = "Bearer #{api_key}"
|
|
157
|
+
req["Accept"] = "application/json"
|
|
158
|
+
req["User-Agent"] = "sendly-ruby/#{VERSION}"
|
|
159
|
+
req["Content-Type"] = "multipart/form-data; boundary=#{boundary}"
|
|
160
|
+
req.body = body.join
|
|
161
|
+
|
|
162
|
+
attempt = 0
|
|
163
|
+
begin
|
|
164
|
+
response = http.request(req)
|
|
165
|
+
handle_response(response)
|
|
166
|
+
rescue Net::OpenTimeout, Net::ReadTimeout
|
|
167
|
+
raise TimeoutError, "Request timed out after #{timeout} seconds"
|
|
168
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET, SocketError => e
|
|
169
|
+
raise NetworkError, "Connection failed: #{e.message}"
|
|
170
|
+
rescue RateLimitError => e
|
|
171
|
+
attempt += 1
|
|
172
|
+
if attempt <= max_retries && e.retry_after
|
|
173
|
+
sleep(e.retry_after)
|
|
174
|
+
retry
|
|
175
|
+
end
|
|
176
|
+
raise
|
|
177
|
+
rescue ServerError => e
|
|
178
|
+
attempt += 1
|
|
179
|
+
if attempt <= max_retries
|
|
180
|
+
sleep(2 ** attempt)
|
|
181
|
+
retry
|
|
182
|
+
end
|
|
183
|
+
raise
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
125
187
|
private
|
|
126
188
|
|
|
127
189
|
def validate_api_key!
|
data/lib/sendly/media.rb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sendly
|
|
4
|
+
class Media
|
|
5
|
+
attr_reader :client
|
|
6
|
+
|
|
7
|
+
def initialize(client)
|
|
8
|
+
@client = client
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def upload(file, content_type: "image/jpeg", filename: "upload.jpg")
|
|
12
|
+
response = client.post_multipart("/media", file, content_type: content_type, filename: filename)
|
|
13
|
+
MediaFile.new(response)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/sendly/messages.rb
CHANGED
|
@@ -36,13 +36,14 @@ module Sendly
|
|
|
36
36
|
# text: "Your verification code is 123456",
|
|
37
37
|
# message_type: "transactional"
|
|
38
38
|
# )
|
|
39
|
-
def send(to:, text:, message_type: nil, metadata: nil)
|
|
39
|
+
def send(to:, text:, message_type: nil, metadata: nil, media_urls: nil)
|
|
40
40
|
validate_phone!(to)
|
|
41
41
|
validate_text!(text)
|
|
42
42
|
|
|
43
43
|
body = { to: to, text: text }
|
|
44
44
|
body[:messageType] = message_type if message_type
|
|
45
45
|
body[:metadata] = metadata if metadata
|
|
46
|
+
body[:mediaUrls] = media_urls if media_urls
|
|
46
47
|
|
|
47
48
|
response = client.post("/messages", body)
|
|
48
49
|
# API returns message directly at top level
|
data/lib/sendly/types.rb
CHANGED
|
@@ -51,11 +51,17 @@ module Sendly
|
|
|
51
51
|
# @return [Time, nil] Delivery timestamp
|
|
52
52
|
attr_reader :delivered_at
|
|
53
53
|
|
|
54
|
+
# @return [String, nil] Error code if delivery failed
|
|
55
|
+
attr_reader :error_code
|
|
56
|
+
|
|
57
|
+
# @return [Integer] Number of delivery retry attempts
|
|
58
|
+
attr_reader :retry_count
|
|
59
|
+
|
|
54
60
|
# @return [Hash, nil] Custom metadata attached to the message
|
|
55
61
|
attr_reader :metadata
|
|
56
62
|
|
|
57
63
|
# Message status constants (sending removed - doesn't exist in database)
|
|
58
|
-
STATUSES = %w[queued sent delivered failed bounced].freeze
|
|
64
|
+
STATUSES = %w[queued sent delivered failed bounced retrying].freeze
|
|
59
65
|
|
|
60
66
|
# Sender type constants
|
|
61
67
|
SENDER_TYPES = %w[number_pool alphanumeric sandbox].freeze
|
|
@@ -77,6 +83,8 @@ module Sendly
|
|
|
77
83
|
@sender_note = data["senderNote"]
|
|
78
84
|
@created_at = parse_time(data["createdAt"])
|
|
79
85
|
@delivered_at = parse_time(data["deliveredAt"])
|
|
86
|
+
@error_code = data["errorCode"]
|
|
87
|
+
@retry_count = data["retryCount"] || 0
|
|
80
88
|
@metadata = data["metadata"]
|
|
81
89
|
end
|
|
82
90
|
|
|
@@ -118,6 +126,8 @@ module Sendly
|
|
|
118
126
|
sender_note: sender_note,
|
|
119
127
|
created_at: created_at&.iso8601,
|
|
120
128
|
delivered_at: delivered_at&.iso8601,
|
|
129
|
+
error_code: error_code,
|
|
130
|
+
retry_count: retry_count,
|
|
121
131
|
metadata: metadata
|
|
122
132
|
}.compact
|
|
123
133
|
end
|
|
@@ -193,6 +203,30 @@ module Sendly
|
|
|
193
203
|
end
|
|
194
204
|
end
|
|
195
205
|
|
|
206
|
+
# ============================================================================
|
|
207
|
+
# Media
|
|
208
|
+
# ============================================================================
|
|
209
|
+
|
|
210
|
+
class MediaFile
|
|
211
|
+
attr_reader :id, :url, :content_type, :size_bytes
|
|
212
|
+
|
|
213
|
+
def initialize(data)
|
|
214
|
+
@id = data["id"]
|
|
215
|
+
@url = data["url"]
|
|
216
|
+
@content_type = data["contentType"]
|
|
217
|
+
@size_bytes = data["sizeBytes"]
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def to_h
|
|
221
|
+
{
|
|
222
|
+
id: id,
|
|
223
|
+
url: url,
|
|
224
|
+
content_type: content_type,
|
|
225
|
+
size_bytes: size_bytes
|
|
226
|
+
}.compact
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
196
230
|
# ============================================================================
|
|
197
231
|
# Webhooks
|
|
198
232
|
# ============================================================================
|
data/lib/sendly/version.rb
CHANGED
data/lib/sendly.rb
CHANGED
|
@@ -8,6 +8,7 @@ require_relative "sendly/errors"
|
|
|
8
8
|
require_relative "sendly/types"
|
|
9
9
|
require_relative "sendly/client"
|
|
10
10
|
require_relative "sendly/messages"
|
|
11
|
+
require_relative "sendly/media"
|
|
11
12
|
require_relative "sendly/webhooks"
|
|
12
13
|
require_relative "sendly/webhooks_resource"
|
|
13
14
|
require_relative "sendly/account_resource"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sendly
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.18.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sendly
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-02-
|
|
11
|
+
date: 2026-02-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|
|
@@ -128,6 +128,7 @@ files:
|
|
|
128
128
|
- lib/sendly/client.rb
|
|
129
129
|
- lib/sendly/contacts_resource.rb
|
|
130
130
|
- lib/sendly/errors.rb
|
|
131
|
+
- lib/sendly/media.rb
|
|
131
132
|
- lib/sendly/messages.rb
|
|
132
133
|
- lib/sendly/templates_resource.rb
|
|
133
134
|
- lib/sendly/types.rb
|