sendly 3.26.0 → 3.29.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/CHANGELOG.md +17 -0
- data/Gemfile.lock +2 -2
- data/lib/sendly/client.rb +7 -0
- data/lib/sendly/contacts_resource.rb +56 -0
- data/lib/sendly/conversations_resource.rb +11 -0
- data/lib/sendly/rules_resource.rb +41 -0
- data/lib/sendly/templates_resource.rb +7 -0
- data/lib/sendly/types.rb +92 -0
- data/lib/sendly/verify.rb +36 -37
- data/lib/sendly/version.rb +1 -1
- data/lib/sendly/webhooks.rb +38 -2
- 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: c41be641303c996be30a1408b25aa49607676c0b6a8511ced4a5275e439602d9
|
|
4
|
+
data.tar.gz: 6228171a120073a65d7fe6b52500906785022d4c2d185e2b678b28a2ffc7e9b3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7c9239c487bf91a790e2b639a724d2fea57b15032524ab4738de185604ca242100416ec43096f7308f820b42ae0eb8134121f597a12052b4fb232e6b5e325b6d
|
|
7
|
+
data.tar.gz: 80ef1fc95098ab7a4ca6bff2afe4e08d82007bae9979a49ce59e4f0fb222ab6353958fd2a7e561cbd4d37323cbafc2781cfab72064633e12da4f967f3f65d163
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# sendly (Ruby)
|
|
2
2
|
|
|
3
|
+
## 3.29.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- `contacts.bulk_mark_valid(ids: ..., list_id: ...)`: clear the invalid flag on many contacts at once (up to 10,000 per call). Escape hatch for when auto-mark misclassifies at scale.
|
|
8
|
+
- Four new list-health webhook event constants in `Sendly::Webhooks`: `EVENT_CONTACT_AUTO_FLAGGED`, `EVENT_CONTACT_MARKED_VALID`, `EVENT_CONTACTS_LOOKUP_COMPLETED`, `EVENT_CONTACTS_BULK_MARKED_VALID`.
|
|
9
|
+
- New `Sendly::Webhooks::ListHealthEventSource` module with frozen constants (`SEND_FAILURE | CARRIER_LOOKUP | USER_ACTION | BULK_MARK_VALID`) for the `source` field on auto-flag and mark-valid webhooks.
|
|
10
|
+
- `Contact` gains `user_marked_valid_at` — when a user manually cleared an auto-flag. Carrier re-checks respect this timestamp and leave the contact clean.
|
|
11
|
+
|
|
12
|
+
## 3.28.0
|
|
13
|
+
|
|
14
|
+
### Minor Changes
|
|
15
|
+
|
|
16
|
+
- `contacts.mark_valid(id)`: clear the auto-exclusion flag on a contact.
|
|
17
|
+
- `contacts.check_numbers(list_id: nil, force: false)`: trigger a background carrier lookup.
|
|
18
|
+
- `Contact` gains `line_type`, `carrier_name`, `line_type_checked_at`, `invalid_reason`, `invalidated_at` plus `invalid?` helper.
|
|
19
|
+
|
|
3
20
|
## 3.18.1
|
|
4
21
|
|
|
5
22
|
### Patch Changes
|
data/Gemfile.lock
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
sendly (3.
|
|
4
|
+
sendly (3.29.0)
|
|
5
5
|
faraday (~> 2.0)
|
|
6
6
|
faraday-retry (~> 2.0)
|
|
7
7
|
|
|
8
8
|
GEM
|
|
9
9
|
remote: https://rubygems.org/
|
|
10
10
|
specs:
|
|
11
|
-
addressable (2.
|
|
11
|
+
addressable (2.9.0)
|
|
12
12
|
public_suffix (>= 2.0.2, < 8.0)
|
|
13
13
|
ast (2.4.3)
|
|
14
14
|
bigdecimal (3.3.1)
|
data/lib/sendly/client.rb
CHANGED
|
@@ -121,6 +121,13 @@ module Sendly
|
|
|
121
121
|
@drafts ||= DraftsResource.new(self)
|
|
122
122
|
end
|
|
123
123
|
|
|
124
|
+
# Access the Rules resource
|
|
125
|
+
#
|
|
126
|
+
# @return [Sendly::RulesResource]
|
|
127
|
+
def rules
|
|
128
|
+
@rules ||= RulesResource.new(self)
|
|
129
|
+
end
|
|
130
|
+
|
|
124
131
|
# Access the Enterprise resource
|
|
125
132
|
#
|
|
126
133
|
# @return [Sendly::EnterpriseResource]
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
module Sendly
|
|
4
4
|
class Contact
|
|
5
5
|
attr_reader :id, :phone_number, :name, :email, :metadata, :opted_out,
|
|
6
|
+
:line_type, :carrier_name, :line_type_checked_at,
|
|
7
|
+
:invalid_reason, :invalidated_at, :user_marked_valid_at,
|
|
6
8
|
:created_at, :updated_at, :lists
|
|
7
9
|
|
|
8
10
|
def initialize(data)
|
|
@@ -12,6 +14,12 @@ module Sendly
|
|
|
12
14
|
@email = data["email"]
|
|
13
15
|
@metadata = data["metadata"] || {}
|
|
14
16
|
@opted_out = data["opted_out"] || data["optedOut"] || false
|
|
17
|
+
@line_type = data["line_type"] || data["lineType"]
|
|
18
|
+
@carrier_name = data["carrier_name"] || data["carrierName"]
|
|
19
|
+
@line_type_checked_at = parse_time(data["line_type_checked_at"] || data["lineTypeCheckedAt"])
|
|
20
|
+
@invalid_reason = data["invalid_reason"] || data["invalidReason"]
|
|
21
|
+
@invalidated_at = parse_time(data["invalidated_at"] || data["invalidatedAt"])
|
|
22
|
+
@user_marked_valid_at = parse_time(data["user_marked_valid_at"] || data["userMarkedValidAt"])
|
|
15
23
|
@created_at = parse_time(data["created_at"] || data["createdAt"])
|
|
16
24
|
@updated_at = parse_time(data["updated_at"] || data["updatedAt"])
|
|
17
25
|
@lists = data["lists"]&.map { |l| { id: l["id"], name: l["name"] } }
|
|
@@ -21,10 +29,19 @@ module Sendly
|
|
|
21
29
|
opted_out
|
|
22
30
|
end
|
|
23
31
|
|
|
32
|
+
def invalid?
|
|
33
|
+
!invalid_reason.nil?
|
|
34
|
+
end
|
|
35
|
+
|
|
24
36
|
def to_h
|
|
25
37
|
{
|
|
26
38
|
id: id, phone_number: phone_number, name: name, email: email,
|
|
27
39
|
metadata: metadata, opted_out: opted_out,
|
|
40
|
+
line_type: line_type, carrier_name: carrier_name,
|
|
41
|
+
line_type_checked_at: line_type_checked_at&.iso8601,
|
|
42
|
+
invalid_reason: invalid_reason,
|
|
43
|
+
invalidated_at: invalidated_at&.iso8601,
|
|
44
|
+
user_marked_valid_at: user_marked_valid_at&.iso8601,
|
|
28
45
|
created_at: created_at&.iso8601, updated_at: updated_at&.iso8601,
|
|
29
46
|
lists: lists
|
|
30
47
|
}.compact
|
|
@@ -186,6 +203,45 @@ module Sendly
|
|
|
186
203
|
@client.delete("/contacts/#{id}")
|
|
187
204
|
end
|
|
188
205
|
|
|
206
|
+
# Clear the invalid flag on a contact so future campaigns include it again.
|
|
207
|
+
# Contacts get auto-flagged when a send fails with a terminal bad-number
|
|
208
|
+
# error (landline, invalid number) or when a carrier lookup reports they
|
|
209
|
+
# can't receive SMS.
|
|
210
|
+
def mark_valid(id)
|
|
211
|
+
response = @client.post("/contacts/#{id}/mark-valid", {})
|
|
212
|
+
Contact.new(response)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Clear the invalid flag on many contacts at once — the escape hatch for
|
|
216
|
+
# when auto-flag misclassifies at scale. Pass either an explicit id array
|
|
217
|
+
# (up to 10,000 per call) OR a list_id, not both. Foreign ids silently
|
|
218
|
+
# no-op via the per-organization filter.
|
|
219
|
+
#
|
|
220
|
+
# @param ids [Array<String>, nil] Explicit contact ids to clear
|
|
221
|
+
# @param list_id [String, nil] Clear every flagged member of this list
|
|
222
|
+
# @return [Hash] `{ cleared: Integer }` — number of contacts whose flag was
|
|
223
|
+
# actually cleared. Already-clean contacts and foreign ids don't count.
|
|
224
|
+
def bulk_mark_valid(ids: nil, list_id: nil)
|
|
225
|
+
if ids.nil? && list_id.nil?
|
|
226
|
+
raise ArgumentError, "bulk_mark_valid requires either :ids or :list_id"
|
|
227
|
+
end
|
|
228
|
+
if ids && list_id
|
|
229
|
+
raise ArgumentError, "bulk_mark_valid accepts :ids OR :list_id, not both"
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
body = ids ? { ids: ids } : { listId: list_id }
|
|
233
|
+
response = @client.post("/contacts/bulk-mark-valid", body)
|
|
234
|
+
{ cleared: response["cleared"] || 0 }
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Trigger a background carrier lookup across your contacts. Landlines
|
|
238
|
+
# and other non-SMS-capable numbers are auto-excluded from future
|
|
239
|
+
# campaigns. The lookup runs asynchronously (1-5 minutes).
|
|
240
|
+
# Options: list_id (scope to a single list), force (re-check already-looked-up)
|
|
241
|
+
def check_numbers(list_id: nil, force: false)
|
|
242
|
+
@client.post("/contacts/lookup", { listId: list_id, force: force })
|
|
243
|
+
end
|
|
244
|
+
|
|
189
245
|
def import_contacts(contacts, list_id: nil, opted_in_at: nil)
|
|
190
246
|
body = {
|
|
191
247
|
contacts: contacts.map { |c|
|
|
@@ -96,6 +96,17 @@ module Sendly
|
|
|
96
96
|
@client.delete("/conversations/#{encoded_id}/labels/#{encoded_label_id}")
|
|
97
97
|
end
|
|
98
98
|
|
|
99
|
+
def get_context(id, max_messages: nil)
|
|
100
|
+
raise ValidationError, "Conversation ID is required" if id.nil? || id.empty?
|
|
101
|
+
|
|
102
|
+
params = {}
|
|
103
|
+
params[:max_messages] = max_messages if max_messages
|
|
104
|
+
|
|
105
|
+
encoded_id = URI.encode_www_form_component(id)
|
|
106
|
+
response = @client.get("/conversations/#{encoded_id}/context", params.compact)
|
|
107
|
+
ConversationContext.new(response)
|
|
108
|
+
end
|
|
109
|
+
|
|
99
110
|
def each(status: nil, batch_size: 100, &block)
|
|
100
111
|
return enum_for(:each, status: status, batch_size: batch_size) unless block_given?
|
|
101
112
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sendly
|
|
4
|
+
class RulesResource
|
|
5
|
+
def initialize(client)
|
|
6
|
+
@client = client
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def list
|
|
10
|
+
response = @client.get("/rules")
|
|
11
|
+
(response["data"] || []).map { |r| Rule.new(r) }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def create(name:, conditions:, actions:, priority: nil)
|
|
15
|
+
body = { name: name, conditions: conditions, actions: actions }
|
|
16
|
+
body[:priority] = priority if priority
|
|
17
|
+
|
|
18
|
+
response = @client.post("/rules", body)
|
|
19
|
+
Rule.new(response)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def update(id, name: nil, conditions: nil, actions: nil, priority: nil)
|
|
23
|
+
raise ValidationError, "Rule ID is required" if id.nil? || id.empty?
|
|
24
|
+
|
|
25
|
+
body = {}
|
|
26
|
+
body[:name] = name if name
|
|
27
|
+
body[:conditions] = conditions if conditions
|
|
28
|
+
body[:actions] = actions if actions
|
|
29
|
+
body[:priority] = priority if priority
|
|
30
|
+
|
|
31
|
+
response = @client.patch("/rules/#{URI.encode_www_form_component(id)}", body)
|
|
32
|
+
Rule.new(response)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def delete(id)
|
|
36
|
+
raise ValidationError, "Rule ID is required" if id.nil? || id.empty?
|
|
37
|
+
|
|
38
|
+
@client.delete("/rules/#{URI.encode_www_form_component(id)}")
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -111,5 +111,12 @@ module Sendly
|
|
|
111
111
|
response = @client.post("/templates/#{id}/clone", body)
|
|
112
112
|
Template.new(response)
|
|
113
113
|
end
|
|
114
|
+
|
|
115
|
+
def generate(description:, category: nil)
|
|
116
|
+
body = { description: description }
|
|
117
|
+
body[:category] = category if category
|
|
118
|
+
response = @client.post("/templates/generate", body)
|
|
119
|
+
GeneratedTemplate.new(response)
|
|
120
|
+
end
|
|
114
121
|
end
|
|
115
122
|
end
|
data/lib/sendly/types.rb
CHANGED
|
@@ -746,4 +746,96 @@ module Sendly
|
|
|
746
746
|
end
|
|
747
747
|
end
|
|
748
748
|
end
|
|
749
|
+
|
|
750
|
+
# ============================================================================
|
|
751
|
+
# Conversation Context
|
|
752
|
+
# ============================================================================
|
|
753
|
+
|
|
754
|
+
class ConversationContext
|
|
755
|
+
attr_reader :context, :conversation, :token_estimate, :business
|
|
756
|
+
|
|
757
|
+
def initialize(data)
|
|
758
|
+
@context = data["context"]
|
|
759
|
+
@conversation = {
|
|
760
|
+
id: data.dig("conversation", "id"),
|
|
761
|
+
phone_number: data.dig("conversation", "phoneNumber") || data.dig("conversation", "phone_number"),
|
|
762
|
+
status: data.dig("conversation", "status"),
|
|
763
|
+
message_count: data.dig("conversation", "messageCount") || data.dig("conversation", "message_count") || 0,
|
|
764
|
+
unread_count: data.dig("conversation", "unreadCount") || data.dig("conversation", "unread_count") || 0
|
|
765
|
+
}
|
|
766
|
+
@token_estimate = data["tokenEstimate"] || data["token_estimate"] || 0
|
|
767
|
+
if data["business"]
|
|
768
|
+
@business = {
|
|
769
|
+
name: data.dig("business", "name"),
|
|
770
|
+
use_case: data.dig("business", "useCase") || data.dig("business", "use_case")
|
|
771
|
+
}
|
|
772
|
+
end
|
|
773
|
+
end
|
|
774
|
+
|
|
775
|
+
def to_h
|
|
776
|
+
result = {
|
|
777
|
+
context: context,
|
|
778
|
+
conversation: conversation,
|
|
779
|
+
token_estimate: token_estimate
|
|
780
|
+
}
|
|
781
|
+
result[:business] = business if business
|
|
782
|
+
result
|
|
783
|
+
end
|
|
784
|
+
end
|
|
785
|
+
|
|
786
|
+
# ============================================================================
|
|
787
|
+
# Rules
|
|
788
|
+
# ============================================================================
|
|
789
|
+
|
|
790
|
+
class Rule
|
|
791
|
+
attr_reader :id, :name, :conditions, :actions, :priority, :created_at, :updated_at
|
|
792
|
+
|
|
793
|
+
def initialize(data)
|
|
794
|
+
@id = data["id"]
|
|
795
|
+
@name = data["name"]
|
|
796
|
+
@conditions = data["conditions"] || {}
|
|
797
|
+
@actions = data["actions"] || {}
|
|
798
|
+
@priority = data["priority"]
|
|
799
|
+
@created_at = parse_time(data["createdAt"] || data["created_at"])
|
|
800
|
+
@updated_at = parse_time(data["updatedAt"] || data["updated_at"])
|
|
801
|
+
end
|
|
802
|
+
|
|
803
|
+
def to_h
|
|
804
|
+
{
|
|
805
|
+
id: id, name: name, conditions: conditions, actions: actions,
|
|
806
|
+
priority: priority, created_at: created_at&.iso8601,
|
|
807
|
+
updated_at: updated_at&.iso8601
|
|
808
|
+
}.compact
|
|
809
|
+
end
|
|
810
|
+
|
|
811
|
+
private
|
|
812
|
+
|
|
813
|
+
def parse_time(value)
|
|
814
|
+
return nil if value.nil?
|
|
815
|
+
Time.parse(value)
|
|
816
|
+
rescue ArgumentError
|
|
817
|
+
nil
|
|
818
|
+
end
|
|
819
|
+
end
|
|
820
|
+
|
|
821
|
+
# ============================================================================
|
|
822
|
+
# Generated Template
|
|
823
|
+
# ============================================================================
|
|
824
|
+
|
|
825
|
+
class GeneratedTemplate
|
|
826
|
+
attr_reader :name, :text, :variables, :category
|
|
827
|
+
|
|
828
|
+
def initialize(data)
|
|
829
|
+
@name = data["name"]
|
|
830
|
+
@text = data["text"]
|
|
831
|
+
@variables = data["variables"] || []
|
|
832
|
+
@category = data["category"]
|
|
833
|
+
end
|
|
834
|
+
|
|
835
|
+
def to_h
|
|
836
|
+
{
|
|
837
|
+
name: name, text: text, variables: variables, category: category
|
|
838
|
+
}.compact
|
|
839
|
+
end
|
|
840
|
+
end
|
|
749
841
|
end
|
data/lib/sendly/verify.rb
CHANGED
|
@@ -3,28 +3,25 @@
|
|
|
3
3
|
module Sendly
|
|
4
4
|
class Verification
|
|
5
5
|
attr_reader :id, :status, :phone, :delivery_status, :attempts, :max_attempts,
|
|
6
|
-
:
|
|
7
|
-
:app_name, :template_id, :profile_id
|
|
6
|
+
:expires_at, :verified_at, :created_at, :sandbox,
|
|
7
|
+
:app_name, :template_id, :profile_id
|
|
8
8
|
|
|
9
9
|
STATUSES = %w[pending verified expired failed].freeze
|
|
10
|
-
CHANNELS = %w[sms whatsapp email].freeze
|
|
11
10
|
|
|
12
11
|
def initialize(data)
|
|
13
12
|
@id = data["id"]
|
|
14
13
|
@status = data["status"]
|
|
15
14
|
@phone = data["phone"]
|
|
16
|
-
@delivery_status = data["
|
|
15
|
+
@delivery_status = data["delivery_status"]
|
|
17
16
|
@attempts = data["attempts"] || 0
|
|
18
|
-
@max_attempts = data["
|
|
19
|
-
@
|
|
20
|
-
@
|
|
21
|
-
@
|
|
22
|
-
@created_at = parse_time(data["createdAt"] || data["created_at"])
|
|
17
|
+
@max_attempts = data["max_attempts"] || 3
|
|
18
|
+
@expires_at = parse_time(data["expires_at"])
|
|
19
|
+
@verified_at = parse_time(data["verified_at"])
|
|
20
|
+
@created_at = parse_time(data["created_at"])
|
|
23
21
|
@sandbox = data["sandbox"] || false
|
|
24
|
-
@app_name = data["
|
|
25
|
-
@template_id = data["
|
|
26
|
-
@profile_id = data["
|
|
27
|
-
@metadata = data["metadata"] || {}
|
|
22
|
+
@app_name = data["app_name"]
|
|
23
|
+
@template_id = data["template_id"]
|
|
24
|
+
@profile_id = data["profile_id"]
|
|
28
25
|
end
|
|
29
26
|
|
|
30
27
|
def pending?
|
|
@@ -46,10 +43,10 @@ module Sendly
|
|
|
46
43
|
def to_h
|
|
47
44
|
{
|
|
48
45
|
id: id, status: status, phone: phone, delivery_status: delivery_status,
|
|
49
|
-
attempts: attempts, max_attempts: max_attempts,
|
|
46
|
+
attempts: attempts, max_attempts: max_attempts,
|
|
50
47
|
expires_at: expires_at&.iso8601, verified_at: verified_at&.iso8601,
|
|
51
48
|
created_at: created_at&.iso8601, sandbox: sandbox, app_name: app_name,
|
|
52
|
-
template_id: template_id, profile_id: profile_id
|
|
49
|
+
template_id: template_id, profile_id: profile_id
|
|
53
50
|
}.compact
|
|
54
51
|
end
|
|
55
52
|
|
|
@@ -64,25 +61,32 @@ module Sendly
|
|
|
64
61
|
end
|
|
65
62
|
|
|
66
63
|
class SendVerificationResponse
|
|
67
|
-
attr_reader :
|
|
64
|
+
attr_reader :id, :status, :phone, :expires_at, :sandbox, :sandbox_code, :message
|
|
68
65
|
|
|
69
66
|
def initialize(data)
|
|
70
|
-
@
|
|
71
|
-
@
|
|
67
|
+
@id = data["id"]
|
|
68
|
+
@status = data["status"]
|
|
69
|
+
@phone = data["phone"]
|
|
70
|
+
@expires_at = data["expires_at"]
|
|
71
|
+
@sandbox = data["sandbox"] || false
|
|
72
|
+
@sandbox_code = data["sandbox_code"]
|
|
73
|
+
@message = data["message"]
|
|
72
74
|
end
|
|
73
75
|
end
|
|
74
76
|
|
|
75
77
|
class CheckVerificationResponse
|
|
76
|
-
attr_reader :
|
|
78
|
+
attr_reader :id, :status, :phone, :verified_at, :remaining_attempts
|
|
77
79
|
|
|
78
80
|
def initialize(data)
|
|
79
|
-
@
|
|
81
|
+
@id = data["id"]
|
|
80
82
|
@status = data["status"]
|
|
81
|
-
@
|
|
83
|
+
@phone = data["phone"]
|
|
84
|
+
@verified_at = data["verified_at"]
|
|
85
|
+
@remaining_attempts = data["remaining_attempts"]
|
|
82
86
|
end
|
|
83
87
|
|
|
84
|
-
def
|
|
85
|
-
|
|
88
|
+
def verified?
|
|
89
|
+
status == "verified"
|
|
86
90
|
end
|
|
87
91
|
end
|
|
88
92
|
|
|
@@ -163,18 +167,14 @@ module Sendly
|
|
|
163
167
|
@sessions = SessionsResource.new(client)
|
|
164
168
|
end
|
|
165
169
|
|
|
166
|
-
def send(
|
|
167
|
-
|
|
168
|
-
body = { to:
|
|
169
|
-
body[:
|
|
170
|
-
body[:
|
|
171
|
-
body[:
|
|
172
|
-
body[:
|
|
173
|
-
body[:
|
|
174
|
-
body[:profileId] = profile_id if profile_id
|
|
175
|
-
body[:appName] = app_name if app_name
|
|
176
|
-
body[:locale] = locale if locale
|
|
177
|
-
body[:metadata] = metadata if metadata
|
|
170
|
+
def send(to:, template_id: nil, profile_id: nil, app_name: nil,
|
|
171
|
+
timeout_secs: nil, code_length: nil)
|
|
172
|
+
body = { to: to }
|
|
173
|
+
body[:template_id] = template_id if template_id
|
|
174
|
+
body[:profile_id] = profile_id if profile_id
|
|
175
|
+
body[:app_name] = app_name if app_name
|
|
176
|
+
body[:timeout_secs] = timeout_secs if timeout_secs
|
|
177
|
+
body[:code_length] = code_length if code_length
|
|
178
178
|
|
|
179
179
|
response = @client.post("/verify", body)
|
|
180
180
|
SendVerificationResponse.new(response)
|
|
@@ -195,11 +195,10 @@ module Sendly
|
|
|
195
195
|
Verification.new(response)
|
|
196
196
|
end
|
|
197
197
|
|
|
198
|
-
def list(limit: nil, status: nil
|
|
198
|
+
def list(limit: nil, status: nil)
|
|
199
199
|
params = {}
|
|
200
200
|
params[:limit] = limit if limit
|
|
201
201
|
params[:status] = status if status
|
|
202
|
-
params[:phone] = phone if phone
|
|
203
202
|
|
|
204
203
|
response = @client.get("/verify", params)
|
|
205
204
|
verifications = (response["verifications"] || []).map { |v| Verification.new(v) }
|
data/lib/sendly/version.rb
CHANGED
data/lib/sendly/webhooks.rb
CHANGED
|
@@ -34,6 +34,40 @@ module Sendly
|
|
|
34
34
|
module Webhooks
|
|
35
35
|
SIGNATURE_TOLERANCE_SECONDS = 300
|
|
36
36
|
|
|
37
|
+
# Webhook event type string constants. Use these when subscribing
|
|
38
|
+
# instead of string literals so you catch typos at load time.
|
|
39
|
+
EVENT_MESSAGE_QUEUED = "message.queued"
|
|
40
|
+
EVENT_MESSAGE_SENT = "message.sent"
|
|
41
|
+
EVENT_MESSAGE_DELIVERED = "message.delivered"
|
|
42
|
+
EVENT_MESSAGE_FAILED = "message.failed"
|
|
43
|
+
EVENT_MESSAGE_BOUNCED = "message.bounced"
|
|
44
|
+
EVENT_MESSAGE_RETRYING = "message.retrying"
|
|
45
|
+
EVENT_MESSAGE_RECEIVED = "message.received"
|
|
46
|
+
EVENT_MESSAGE_OPT_OUT = "message.opt_out"
|
|
47
|
+
EVENT_MESSAGE_OPT_IN = "message.opt_in"
|
|
48
|
+
EVENT_VERIFICATION_CREATED = "verification.created"
|
|
49
|
+
EVENT_VERIFICATION_DELIVERED = "verification.delivered"
|
|
50
|
+
EVENT_VERIFICATION_VERIFIED = "verification.verified"
|
|
51
|
+
EVENT_VERIFICATION_EXPIRED = "verification.expired"
|
|
52
|
+
EVENT_VERIFICATION_FAILED = "verification.failed"
|
|
53
|
+
EVENT_VERIFICATION_RESENT = "verification.resent"
|
|
54
|
+
EVENT_VERIFICATION_DELIVERY_FAILED = "verification.delivery_failed"
|
|
55
|
+
EVENT_CONTACT_AUTO_FLAGGED = "contact.auto_flagged"
|
|
56
|
+
EVENT_CONTACT_MARKED_VALID = "contact.marked_valid"
|
|
57
|
+
EVENT_CONTACTS_LOOKUP_COMPLETED = "contacts.lookup_completed"
|
|
58
|
+
EVENT_CONTACTS_BULK_MARKED_VALID = "contacts.bulk_marked_valid"
|
|
59
|
+
|
|
60
|
+
# Source of a list-health event. Frozen enum — new values will be
|
|
61
|
+
# added in minor SDK versions, never removed.
|
|
62
|
+
module ListHealthEventSource
|
|
63
|
+
SEND_FAILURE = "send_failure"
|
|
64
|
+
CARRIER_LOOKUP = "carrier_lookup"
|
|
65
|
+
USER_ACTION = "user_action"
|
|
66
|
+
BULK_MARK_VALID = "bulk_mark_valid"
|
|
67
|
+
|
|
68
|
+
ALL = [SEND_FAILURE, CARRIER_LOOKUP, USER_ACTION, BULK_MARK_VALID].freeze
|
|
69
|
+
end
|
|
70
|
+
|
|
37
71
|
class << self
|
|
38
72
|
# Verify webhook signature from Sendly.
|
|
39
73
|
#
|
|
@@ -146,7 +180,7 @@ module Sendly
|
|
|
146
180
|
attr_reader :id, :status, :to, :from, :direction, :organization_id,
|
|
147
181
|
:text, :error, :error_code, :delivered_at, :failed_at,
|
|
148
182
|
:created_at, :segments, :credits_used, :message_format, :media_urls,
|
|
149
|
-
:retry_count, :metadata
|
|
183
|
+
:retry_count, :metadata, :batch_id
|
|
150
184
|
|
|
151
185
|
def initialize(data)
|
|
152
186
|
@id = data[:id] || data[:message_id] || ''
|
|
@@ -167,6 +201,7 @@ module Sendly
|
|
|
167
201
|
@media_urls = data[:media_urls]
|
|
168
202
|
@retry_count = data[:retry_count]
|
|
169
203
|
@metadata = data[:metadata]
|
|
204
|
+
@batch_id = data[:batch_id]
|
|
170
205
|
end
|
|
171
206
|
|
|
172
207
|
def message_id
|
|
@@ -185,7 +220,8 @@ module Sendly
|
|
|
185
220
|
delivered_at: @delivered_at,
|
|
186
221
|
failed_at: @failed_at,
|
|
187
222
|
segments: @segments,
|
|
188
|
-
credits_used: @credits_used
|
|
223
|
+
credits_used: @credits_used,
|
|
224
|
+
batch_id: @batch_id
|
|
189
225
|
}.compact
|
|
190
226
|
end
|
|
191
227
|
end
|
data/lib/sendly.rb
CHANGED
|
@@ -19,6 +19,7 @@ require_relative "sendly/contacts_resource"
|
|
|
19
19
|
require_relative "sendly/conversations_resource"
|
|
20
20
|
require_relative "sendly/labels_resource"
|
|
21
21
|
require_relative "sendly/drafts_resource"
|
|
22
|
+
require_relative "sendly/rules_resource"
|
|
22
23
|
require_relative "sendly/enterprise"
|
|
23
24
|
|
|
24
25
|
# Sendly Ruby SDK
|
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.29.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-
|
|
11
|
+
date: 2026-04-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|
|
@@ -135,6 +135,7 @@ files:
|
|
|
135
135
|
- lib/sendly/labels_resource.rb
|
|
136
136
|
- lib/sendly/media.rb
|
|
137
137
|
- lib/sendly/messages.rb
|
|
138
|
+
- lib/sendly/rules_resource.rb
|
|
138
139
|
- lib/sendly/templates_resource.rb
|
|
139
140
|
- lib/sendly/types.rb
|
|
140
141
|
- lib/sendly/verify.rb
|