sendly 3.35.0 → 3.37.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/README.md +413 -0
- data/lib/sendly/account_resource.rb +31 -0
- data/lib/sendly/client.rb +57 -4
- data/lib/sendly/links_resource.rb +240 -0
- data/lib/sendly/messages.rb +83 -0
- data/lib/sendly/numbers_resource.rb +107 -1
- data/lib/sendly/tendlc_resource.rb +397 -0
- data/lib/sendly/types.rb +79 -0
- data/lib/sendly/version.rb +1 -1
- data/lib/sendly.rb +2 -0
- metadata +4 -2
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sendly
|
|
4
|
+
# A newly minted branded short link, as returned by {LinksResource#create}.
|
|
5
|
+
class ShortLink
|
|
6
|
+
# @return [String] Short code (the segment after the domain, e.g. "Ab3xY7")
|
|
7
|
+
attr_reader :code
|
|
8
|
+
|
|
9
|
+
# @return [String] Full branded short URL to share (e.g. "https://sendly.live/l/Ab3xY7")
|
|
10
|
+
attr_reader :short_url
|
|
11
|
+
|
|
12
|
+
# @return [String] The destination the short link redirects to
|
|
13
|
+
attr_reader :destination_url
|
|
14
|
+
|
|
15
|
+
# @return [Hash] The raw parsed response
|
|
16
|
+
attr_reader :raw
|
|
17
|
+
|
|
18
|
+
def initialize(data)
|
|
19
|
+
@raw = data
|
|
20
|
+
@code = data["code"]
|
|
21
|
+
@short_url = data["shortUrl"] || data["short_url"]
|
|
22
|
+
@destination_url = data["destinationUrl"] || data["destination_url"]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def to_h
|
|
26
|
+
{
|
|
27
|
+
code: code, short_url: short_url, destination_url: destination_url
|
|
28
|
+
}.compact
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# A short link with click analytics, as returned by {LinksResource#list}.
|
|
33
|
+
class ShortLinkListItem
|
|
34
|
+
attr_reader :code, :short_url, :destination_url, :brand_slug, :click_count,
|
|
35
|
+
:disabled, :last_country, :last_clicked_at, :created_at, :spark
|
|
36
|
+
|
|
37
|
+
def initialize(data)
|
|
38
|
+
@code = data["code"]
|
|
39
|
+
@short_url = data["shortUrl"] || data["short_url"]
|
|
40
|
+
@destination_url = data["destinationUrl"] || data["destination_url"]
|
|
41
|
+
@brand_slug = data["brandSlug"] || data["brand_slug"]
|
|
42
|
+
@click_count = data["clickCount"] || data["click_count"] || 0
|
|
43
|
+
@disabled = data["disabled"] || false
|
|
44
|
+
@last_country = data["lastCountry"] || data["last_country"]
|
|
45
|
+
@last_clicked_at = parse_time(data["lastClickedAt"] || data["last_clicked_at"])
|
|
46
|
+
@created_at = parse_time(data["createdAt"] || data["created_at"])
|
|
47
|
+
# 14-day daily click histogram, oldest first (today last)
|
|
48
|
+
@spark = data["spark"] || []
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @return [Boolean] Whether the link is disabled (its redirect returns 404)
|
|
52
|
+
def disabled?
|
|
53
|
+
disabled
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def to_h
|
|
57
|
+
{
|
|
58
|
+
code: code, short_url: short_url, destination_url: destination_url,
|
|
59
|
+
brand_slug: brand_slug, click_count: click_count, disabled: disabled,
|
|
60
|
+
last_country: last_country, last_clicked_at: last_clicked_at&.iso8601,
|
|
61
|
+
created_at: created_at&.iso8601, spark: spark
|
|
62
|
+
}.compact
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def parse_time(value)
|
|
68
|
+
return nil if value.nil?
|
|
69
|
+
Time.parse(value)
|
|
70
|
+
rescue ArgumentError
|
|
71
|
+
nil
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# A page of short links with their click analytics.
|
|
76
|
+
class ShortLinkList
|
|
77
|
+
include Enumerable
|
|
78
|
+
|
|
79
|
+
# @return [Array<ShortLinkListItem>] The workspace's short links, newest first
|
|
80
|
+
attr_reader :links
|
|
81
|
+
|
|
82
|
+
# @return [Integer] Total number of short links in the workspace
|
|
83
|
+
attr_reader :total
|
|
84
|
+
|
|
85
|
+
def initialize(response)
|
|
86
|
+
@links = (response["links"] || []).map { |l| ShortLinkListItem.new(l) }
|
|
87
|
+
@total = response["total"] || @links.length
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def each(&block)
|
|
91
|
+
links.each(&block)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def count
|
|
95
|
+
links.length
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
alias size count
|
|
99
|
+
alias length count
|
|
100
|
+
|
|
101
|
+
def empty?
|
|
102
|
+
links.empty?
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def first
|
|
106
|
+
links.first
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def last
|
|
110
|
+
links.last
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# The code and new disabled state returned when a link's kill switch is toggled.
|
|
115
|
+
class ShortLinkStatus
|
|
116
|
+
# @return [String] Short code that was updated
|
|
117
|
+
attr_reader :code
|
|
118
|
+
|
|
119
|
+
# @return [Boolean] New disabled state
|
|
120
|
+
attr_reader :disabled
|
|
121
|
+
|
|
122
|
+
def initialize(data)
|
|
123
|
+
@code = data["code"]
|
|
124
|
+
@disabled = data["disabled"] || false
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# @return [Boolean] Whether the link is now disabled
|
|
128
|
+
def disabled?
|
|
129
|
+
disabled
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def to_h
|
|
133
|
+
{ code: code, disabled: disabled }
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Links resource — branded URL shortening.
|
|
138
|
+
#
|
|
139
|
+
# Mint branded short links for a destination URL, list the links your
|
|
140
|
+
# workspace has created (with click analytics), and enable or disable an
|
|
141
|
+
# individual link (a per-link kill switch).
|
|
142
|
+
#
|
|
143
|
+
# Branded, owned-domain short links improve deliverability — carriers filter
|
|
144
|
+
# public shorteners — and give you click data.
|
|
145
|
+
#
|
|
146
|
+
# NOTE: URL shortening is not yet generally available. It is gated behind the
|
|
147
|
+
# +url_shortener+ rollout flag (currently founder-only); for API-key clients a
|
|
148
|
+
# flag-off account reads as a 404 {Sendly::NotFoundError} (the feature is
|
|
149
|
+
# absent) until the flag is on for your account.
|
|
150
|
+
#
|
|
151
|
+
# @example Shorten a URL
|
|
152
|
+
# link = client.links.create(url: "https://example.com/spring-sale?utm_source=sms")
|
|
153
|
+
# puts link.short_url # => "https://sendly.live/l/Ab3xY7"
|
|
154
|
+
#
|
|
155
|
+
# @example List your links with click counts
|
|
156
|
+
# result = client.links.list(limit: 20)
|
|
157
|
+
# result.each { |l| puts "#{l.short_url} -> #{l.destination_url} (#{l.click_count} clicks)" }
|
|
158
|
+
#
|
|
159
|
+
# @example Kill a link
|
|
160
|
+
# client.links.disable(link.code)
|
|
161
|
+
class LinksResource
|
|
162
|
+
def initialize(client)
|
|
163
|
+
@client = client
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Mint a branded short link for a destination URL. Uses your workspace's
|
|
167
|
+
# brand slug when one is configured.
|
|
168
|
+
#
|
|
169
|
+
# @param url [String] The destination URL to shorten (must be http/https)
|
|
170
|
+
# @return [Sendly::ShortLink] The short link (code, short_url, destination_url)
|
|
171
|
+
#
|
|
172
|
+
# @raise [Sendly::ValidationError] If url is missing or not an http/https URL
|
|
173
|
+
def create(url:)
|
|
174
|
+
validate_url!(url)
|
|
175
|
+
|
|
176
|
+
response = @client.post("/links", { url: url })
|
|
177
|
+
ShortLink.new(response)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# List the short links your workspace has created, newest first, with click
|
|
181
|
+
# counts and a 14-day daily click histogram.
|
|
182
|
+
#
|
|
183
|
+
# @param limit [Integer, nil] Maximum links to return (default 50, max 200)
|
|
184
|
+
# @param offset [Integer, nil] Number of links to skip (default 0)
|
|
185
|
+
# @return [Sendly::ShortLinkList] The links and a total count
|
|
186
|
+
def list(limit: nil, offset: nil)
|
|
187
|
+
params = {}
|
|
188
|
+
params[:limit] = limit if limit
|
|
189
|
+
params[:offset] = offset if offset
|
|
190
|
+
|
|
191
|
+
response = @client.get("/links", params)
|
|
192
|
+
ShortLinkList.new(response)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Enable or disable a short link. A disabled link's redirect returns 404
|
|
196
|
+
# until it is re-enabled.
|
|
197
|
+
#
|
|
198
|
+
# @param code [String] The short link code
|
|
199
|
+
# @param disabled [Boolean] true to disable, false to re-enable
|
|
200
|
+
# @return [Sendly::ShortLinkStatus] The link's code and new disabled state
|
|
201
|
+
#
|
|
202
|
+
# @raise [Sendly::NotFoundError] If no such link exists in your workspace
|
|
203
|
+
def update(code, disabled:)
|
|
204
|
+
raise ValidationError, "Link code is required" if code.nil? || code.to_s.empty?
|
|
205
|
+
|
|
206
|
+
encoded_code = URI.encode_www_form_component(code)
|
|
207
|
+
response = @client.patch("/links/#{encoded_code}", { disabled: disabled })
|
|
208
|
+
ShortLinkStatus.new(response)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Disable a short link (its redirect returns 404 until re-enabled).
|
|
212
|
+
# Convenience wrapper over {#update}.
|
|
213
|
+
#
|
|
214
|
+
# @param code [String] The short link code
|
|
215
|
+
# @return [Sendly::ShortLinkStatus]
|
|
216
|
+
def disable(code)
|
|
217
|
+
update(code, disabled: true)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Re-enable a previously disabled short link. Convenience wrapper over
|
|
221
|
+
# {#update}.
|
|
222
|
+
#
|
|
223
|
+
# @param code [String] The short link code
|
|
224
|
+
# @return [Sendly::ShortLinkStatus]
|
|
225
|
+
def enable(code)
|
|
226
|
+
update(code, disabled: false)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
private
|
|
230
|
+
|
|
231
|
+
# Client-side guard mirroring the server's http/https-only check.
|
|
232
|
+
def validate_url!(url)
|
|
233
|
+
raise ValidationError, "url is required" if url.nil? || url.to_s.empty?
|
|
234
|
+
|
|
235
|
+
return if url.is_a?(String) && (url.start_with?("http://") || url.start_with?("https://"))
|
|
236
|
+
|
|
237
|
+
raise ValidationError, "url must be an http:// or https:// URL"
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
data/lib/sendly/messages.rb
CHANGED
|
@@ -52,6 +52,89 @@ module Sendly
|
|
|
52
52
|
Message.new(response)
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
+
# Send a group MMS to 2-8 recipients (US/Canada only)
|
|
56
|
+
#
|
|
57
|
+
# Creates a multi-party MMS conversation: every recipient sees the others,
|
|
58
|
+
# and replies fan out to all participants. Group messaging is an A2P 10DLC
|
|
59
|
+
# capability — the sending number must be an MMS-enabled, 10DLC-registered
|
|
60
|
+
# number you own. Omit +from+ to use your workspace's default sender.
|
|
61
|
+
# Requires the +group_mms+ feature (and +enable_mms+ when sending media).
|
|
62
|
+
#
|
|
63
|
+
# @param to [Array<String>] 2-8 recipient phone numbers in E.164 format (US/CA only)
|
|
64
|
+
# @param text [String] Message content (required unless media_urls is provided)
|
|
65
|
+
# @param from [String] Sender ID or phone number (optional)
|
|
66
|
+
# @param media_urls [Array<String>] Media URLs to attach (required unless text is provided)
|
|
67
|
+
# @param message_type [String] Message type: "transactional" (default) or "marketing"
|
|
68
|
+
# @return [Sendly::GroupMessage] The sent group message, including a group_message_id
|
|
69
|
+
#
|
|
70
|
+
# @raise [Sendly::ValidationError] If fewer than 2 / more than 8 recipients, or no body
|
|
71
|
+
# @raise [Sendly::InsufficientCreditsError] If credit balance is too low (billed per recipient)
|
|
72
|
+
#
|
|
73
|
+
# @example
|
|
74
|
+
# group = client.messages.send_group(
|
|
75
|
+
# to: ["+14155551234", "+14155555678"],
|
|
76
|
+
# text: "Hey team - quick sync at noon?"
|
|
77
|
+
# )
|
|
78
|
+
# puts group.id
|
|
79
|
+
# puts group.group_message_id
|
|
80
|
+
def send_group(to:, text: nil, from: nil, media_urls: nil, message_type: nil)
|
|
81
|
+
unless to.is_a?(Array) && to.length >= 2
|
|
82
|
+
raise ValidationError, "Group messaging requires at least 2 recipients in 'to'"
|
|
83
|
+
end
|
|
84
|
+
raise ValidationError, "Group messaging supports at most 8 recipients" if to.length > 8
|
|
85
|
+
|
|
86
|
+
to.each { |recipient| validate_phone!(recipient) }
|
|
87
|
+
|
|
88
|
+
has_media = media_urls.is_a?(Array) && !media_urls.empty?
|
|
89
|
+
raise ValidationError, "Provide 'text' or 'media_urls'" if (text.nil? || text.empty?) && !has_media
|
|
90
|
+
|
|
91
|
+
body = { to: to }
|
|
92
|
+
body[:text] = text if text && !text.empty?
|
|
93
|
+
body[:from] = from if from
|
|
94
|
+
body[:mediaUrls] = media_urls if has_media
|
|
95
|
+
body[:messageType] = message_type if message_type
|
|
96
|
+
|
|
97
|
+
response = client.post("/messages/group", body)
|
|
98
|
+
GroupMessage.new(response)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# AI-enhance a draft message for clarity, compliance, and send-readiness
|
|
102
|
+
#
|
|
103
|
+
# Rewrites the supplied text into a single, polished SMS segment (<=160
|
|
104
|
+
# chars) and returns a short explanation of what changed. Pass +message_type+
|
|
105
|
+
# to steer the rewrite (e.g. "marketing" vs "transactional"); with no +text+
|
|
106
|
+
# it generates a suitable message for that type instead. At least one of
|
|
107
|
+
# +text+ or +message_type+ is required. Requires the +ai_classification+
|
|
108
|
+
# feature. When AI enhancement is unavailable, the response falls back to the
|
|
109
|
+
# original text with an empty explanation.
|
|
110
|
+
#
|
|
111
|
+
# @param text [String] Draft message text to rewrite (optional if message_type given)
|
|
112
|
+
# @param message_type [String] Message-type hint, e.g. "marketing" or "transactional"
|
|
113
|
+
# @return [Sendly::EnhancedMessage] The enhanced text, an explanation, and the model used
|
|
114
|
+
#
|
|
115
|
+
# @raise [Sendly::ValidationError] If neither text nor message_type is provided
|
|
116
|
+
# @raise [Sendly::NotFoundError] If AI enhancement is not enabled for the account
|
|
117
|
+
#
|
|
118
|
+
# @example
|
|
119
|
+
# result = client.messages.enhance(
|
|
120
|
+
# text: "hey come check out our sale this weekend",
|
|
121
|
+
# message_type: "marketing"
|
|
122
|
+
# )
|
|
123
|
+
# puts result.enhanced # polished, <=160-char rewrite
|
|
124
|
+
# puts result.explanation # what changed and why
|
|
125
|
+
def enhance(text: nil, message_type: nil)
|
|
126
|
+
if (text.nil? || text.to_s.empty?) && (message_type.nil? || message_type.to_s.empty?)
|
|
127
|
+
raise ValidationError, "Provide 'text' or 'message_type'"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
body = {}
|
|
131
|
+
body[:text] = text unless text.nil?
|
|
132
|
+
body[:messageType] = message_type if message_type
|
|
133
|
+
|
|
134
|
+
response = client.post("/ai/enhance", body)
|
|
135
|
+
EnhancedMessage.new(response)
|
|
136
|
+
end
|
|
137
|
+
|
|
55
138
|
# List messages
|
|
56
139
|
#
|
|
57
140
|
# @param limit [Integer] Maximum messages to return (default: 20, max: 100)
|
|
@@ -43,6 +43,10 @@ module Sendly
|
|
|
43
43
|
class PhoneNumber
|
|
44
44
|
attr_reader :id, :phone_number, :status, :source, :country_code,
|
|
45
45
|
:phone_number_type, :monthly_cost_cents,
|
|
46
|
+
# true when this is the workspace's default sender. Only present
|
|
47
|
+
# on single-number responses ({NumbersResource#get} /
|
|
48
|
+
# {NumbersResource#update}); nil in the {NumbersResource#list} projection.
|
|
49
|
+
:is_default,
|
|
46
50
|
# ISO-8601 timestamp string, or nil when the number still needs
|
|
47
51
|
# regulatory documents (a value means docs are under carrier review).
|
|
48
52
|
:requirements_submitted_at,
|
|
@@ -59,6 +63,7 @@ module Sendly
|
|
|
59
63
|
@country_code = data["countryCode"] || data["country_code"]
|
|
60
64
|
@phone_number_type = data["phoneNumberType"] || data["phone_number_type"]
|
|
61
65
|
@monthly_cost_cents = data["monthlyCostCents"] || data["monthly_cost_cents"]
|
|
66
|
+
@is_default = data.key?("isDefault") ? data["isDefault"] : data["is_default"]
|
|
62
67
|
@requirements_submitted_at = data["requirementsSubmittedAt"] || data["requirements_submitted_at"]
|
|
63
68
|
@pending_cancellation = data.key?("pendingCancellation") ? data["pendingCancellation"] : data["pending_cancellation"]
|
|
64
69
|
@scheduled_release_at = data["scheduledReleaseAt"] || data["scheduled_release_at"]
|
|
@@ -68,7 +73,7 @@ module Sendly
|
|
|
68
73
|
{
|
|
69
74
|
id: id, phone_number: phone_number, status: status, source: source,
|
|
70
75
|
country_code: country_code, phone_number_type: phone_number_type,
|
|
71
|
-
monthly_cost_cents: monthly_cost_cents,
|
|
76
|
+
monthly_cost_cents: monthly_cost_cents, is_default: is_default,
|
|
72
77
|
requirements_submitted_at: requirements_submitted_at,
|
|
73
78
|
pending_cancellation: pending_cancellation,
|
|
74
79
|
scheduled_release_at: scheduled_release_at
|
|
@@ -160,6 +165,45 @@ module Sendly
|
|
|
160
165
|
end
|
|
161
166
|
end
|
|
162
167
|
|
|
168
|
+
# The result of releasing an owned number via {NumbersResource#release}.
|
|
169
|
+
#
|
|
170
|
+
# A live paid purchase is cancelled at the end of the paid period — the result
|
|
171
|
+
# then reports +scheduled?+ true with a +scheduled_release_at+ timestamp.
|
|
172
|
+
# Everything else is released immediately (+scheduled?+ false, no timestamp).
|
|
173
|
+
# The raw parsed response is preserved on +#raw+.
|
|
174
|
+
class NumberRelease
|
|
175
|
+
# @return [Boolean] true on success
|
|
176
|
+
attr_reader :success
|
|
177
|
+
|
|
178
|
+
# @return [Boolean] true when the release was scheduled for the period end
|
|
179
|
+
attr_reader :scheduled
|
|
180
|
+
|
|
181
|
+
# @return [String, nil] ISO-8601 timestamp the scheduled release takes effect
|
|
182
|
+
attr_reader :scheduled_release_at
|
|
183
|
+
|
|
184
|
+
# @return [Hash] The raw parsed response
|
|
185
|
+
attr_reader :raw
|
|
186
|
+
|
|
187
|
+
def initialize(data)
|
|
188
|
+
@raw = data
|
|
189
|
+
@success = data.key?("success") ? data["success"] : true
|
|
190
|
+
@scheduled = data["scheduled"] || false
|
|
191
|
+
@scheduled_release_at = data["scheduledReleaseAt"] || data["scheduled_release_at"]
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# @return [Boolean] Whether the release was scheduled (vs immediate)
|
|
195
|
+
def scheduled?
|
|
196
|
+
scheduled
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def to_h
|
|
200
|
+
{
|
|
201
|
+
success: success, scheduled: scheduled,
|
|
202
|
+
scheduled_release_at: scheduled_release_at
|
|
203
|
+
}.compact
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
163
207
|
# Numbers resource — search, list, and purchase phone numbers.
|
|
164
208
|
#
|
|
165
209
|
# @example List supported countries
|
|
@@ -257,5 +301,67 @@ module Sendly
|
|
|
257
301
|
response = @client.post("/numbers/buy", body)
|
|
258
302
|
NumberPurchase.new(response)
|
|
259
303
|
end
|
|
304
|
+
|
|
305
|
+
# Get a single number owned by the account.
|
|
306
|
+
#
|
|
307
|
+
# Unlike the entries {#list} returns, this includes +is_default+ — whether
|
|
308
|
+
# the number is the workspace's default sender.
|
|
309
|
+
#
|
|
310
|
+
# @param id [String] The number's id
|
|
311
|
+
# @return [PhoneNumber]
|
|
312
|
+
# @raise [Sendly::NotFoundError] If no such number exists in your workspace
|
|
313
|
+
def get(id)
|
|
314
|
+
raise ValidationError, "id is required" if id.nil? || id.to_s.empty?
|
|
315
|
+
|
|
316
|
+
encoded_id = URI.encode_www_form_component(id)
|
|
317
|
+
response = @client.get("/numbers/#{encoded_id}")
|
|
318
|
+
PhoneNumber.new(response)
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# Update a number you own. Supply at least one supported mutation — these are
|
|
322
|
+
# the only two the API supports:
|
|
323
|
+
#
|
|
324
|
+
# - +is_default: true+ — make this the workspace's default sending number
|
|
325
|
+
# (the number must be +active+).
|
|
326
|
+
# - +pending_cancellation: false+ — cancel a previously scheduled release and
|
|
327
|
+
# keep the number.
|
|
328
|
+
#
|
|
329
|
+
# @param id [String] The number's id
|
|
330
|
+
# @param is_default [Boolean, nil] Pass +true+ to make this the default sender
|
|
331
|
+
# @param pending_cancellation [Boolean, nil] Pass +false+ to cancel a scheduled release
|
|
332
|
+
# @return [PhoneNumber] The updated number (including +is_default+)
|
|
333
|
+
# @raise [Sendly::ValidationError] If no supported field is given, or +is_default+
|
|
334
|
+
# is requested for a non-active number
|
|
335
|
+
# @raise [Sendly::NotFoundError] If no such number exists in your workspace
|
|
336
|
+
def update(id, is_default: nil, pending_cancellation: nil)
|
|
337
|
+
raise ValidationError, "id is required" if id.nil? || id.to_s.empty?
|
|
338
|
+
if is_default.nil? && pending_cancellation.nil?
|
|
339
|
+
raise ValidationError, "Provide is_default: true and/or pending_cancellation: false"
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
body = {}
|
|
343
|
+
body[:isDefault] = is_default unless is_default.nil?
|
|
344
|
+
body[:pendingCancellation] = pending_cancellation unless pending_cancellation.nil?
|
|
345
|
+
|
|
346
|
+
encoded_id = URI.encode_www_form_component(id)
|
|
347
|
+
response = @client.patch("/numbers/#{encoded_id}", body)
|
|
348
|
+
PhoneNumber.new(response)
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# Release a number you own. A live paid purchase is cancelled at the end of
|
|
352
|
+
# the paid period (the result then reports +scheduled?+ true with a
|
|
353
|
+
# +scheduled_release_at+); everything else is released immediately.
|
|
354
|
+
#
|
|
355
|
+
# @param id [String] The number's id
|
|
356
|
+
# @return [NumberRelease]
|
|
357
|
+
# @raise [Sendly::NotFoundError] If no such number exists in your workspace
|
|
358
|
+
# @raise [Sendly::ValidationError] If the carrier release failed
|
|
359
|
+
def release(id)
|
|
360
|
+
raise ValidationError, "id is required" if id.nil? || id.to_s.empty?
|
|
361
|
+
|
|
362
|
+
encoded_id = URI.encode_www_form_component(id)
|
|
363
|
+
response = @client.delete("/numbers/#{encoded_id}")
|
|
364
|
+
NumberRelease.new(response)
|
|
365
|
+
end
|
|
260
366
|
end
|
|
261
367
|
end
|