sendly 3.12.3 → 3.13.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/campaigns_resource.rb +176 -0
- data/lib/sendly/client.rb +14 -0
- data/lib/sendly/contacts_resource.rb +189 -0
- data/lib/sendly/templates_resource.rb +7 -0
- data/lib/sendly/version.rb +1 -1
- data/lib/sendly.rb +2 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a1468fb0c87212a7d6f930ad557fc6b9546eca11a292ac8c7873c9db1d209ac1
|
|
4
|
+
data.tar.gz: bf62a3866fa8c02557d5925d175fe06c0de554c00d0bb371b1178d2eff84f5a0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: acc02947f862c3de0dd2122a44438fee340398d091a9066eab33e8929e153f545b385f906c3831185760d90afefe6e0c2f6464f5357c8ba185e617833f1ff6d8
|
|
7
|
+
data.tar.gz: 97843f126f57e6ce43e75c3d7f9a6658acd2b3b74b805fee08561f0e57ad55664bcb1f5e95cbad2f920dd927ec0d04e71b20ebde5e628ec6ba28ff729010ba01
|
data/Gemfile.lock
CHANGED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sendly
|
|
4
|
+
class Campaign
|
|
5
|
+
attr_reader :id, :name, :text, :template_id, :contact_list_ids, :status,
|
|
6
|
+
:recipient_count, :sent_count, :delivered_count, :failed_count,
|
|
7
|
+
:estimated_credits, :credits_used, :scheduled_at, :timezone,
|
|
8
|
+
:started_at, :completed_at, :created_at, :updated_at
|
|
9
|
+
|
|
10
|
+
STATUSES = %w[draft scheduled sending sent paused cancelled failed].freeze
|
|
11
|
+
|
|
12
|
+
def initialize(data)
|
|
13
|
+
@id = data["id"]
|
|
14
|
+
@name = data["name"]
|
|
15
|
+
@text = data["text"]
|
|
16
|
+
@template_id = data["template_id"] || data["templateId"]
|
|
17
|
+
@contact_list_ids = data["contact_list_ids"] || data["contactListIds"] || []
|
|
18
|
+
@status = data["status"]
|
|
19
|
+
@recipient_count = data["recipient_count"] || data["recipientCount"] || 0
|
|
20
|
+
@sent_count = data["sent_count"] || data["sentCount"] || 0
|
|
21
|
+
@delivered_count = data["delivered_count"] || data["deliveredCount"] || 0
|
|
22
|
+
@failed_count = data["failed_count"] || data["failedCount"] || 0
|
|
23
|
+
@estimated_credits = data["estimated_credits"] || data["estimatedCredits"] || 0
|
|
24
|
+
@credits_used = data["credits_used"] || data["creditsUsed"] || 0
|
|
25
|
+
@scheduled_at = parse_time(data["scheduled_at"] || data["scheduledAt"])
|
|
26
|
+
@timezone = data["timezone"]
|
|
27
|
+
@started_at = parse_time(data["started_at"] || data["startedAt"])
|
|
28
|
+
@completed_at = parse_time(data["completed_at"] || data["completedAt"])
|
|
29
|
+
@created_at = parse_time(data["created_at"] || data["createdAt"])
|
|
30
|
+
@updated_at = parse_time(data["updated_at"] || data["updatedAt"])
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def draft?
|
|
34
|
+
status == "draft"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def scheduled?
|
|
38
|
+
status == "scheduled"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def sending?
|
|
42
|
+
status == "sending"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def sent?
|
|
46
|
+
status == "sent"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def cancelled?
|
|
50
|
+
status == "cancelled"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def to_h
|
|
54
|
+
{
|
|
55
|
+
id: id, name: name, text: text, template_id: template_id,
|
|
56
|
+
contact_list_ids: contact_list_ids, status: status,
|
|
57
|
+
recipient_count: recipient_count, sent_count: sent_count,
|
|
58
|
+
delivered_count: delivered_count, failed_count: failed_count,
|
|
59
|
+
estimated_credits: estimated_credits, credits_used: credits_used,
|
|
60
|
+
scheduled_at: scheduled_at&.iso8601, timezone: timezone,
|
|
61
|
+
started_at: started_at&.iso8601, completed_at: completed_at&.iso8601,
|
|
62
|
+
created_at: created_at&.iso8601, updated_at: updated_at&.iso8601
|
|
63
|
+
}.compact
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def parse_time(value)
|
|
69
|
+
return nil if value.nil?
|
|
70
|
+
Time.parse(value)
|
|
71
|
+
rescue ArgumentError
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
class CampaignPreview
|
|
77
|
+
attr_reader :id, :recipient_count, :estimated_segments, :estimated_credits,
|
|
78
|
+
:current_balance, :has_enough_credits, :breakdown
|
|
79
|
+
|
|
80
|
+
def initialize(data)
|
|
81
|
+
@id = data["id"]
|
|
82
|
+
@recipient_count = data["recipient_count"] || data["recipientCount"] || 0
|
|
83
|
+
@estimated_segments = data["estimated_segments"] || data["estimatedSegments"] || 0
|
|
84
|
+
@estimated_credits = data["estimated_credits"] || data["estimatedCredits"] || 0
|
|
85
|
+
@current_balance = data["current_balance"] || data["currentBalance"] || 0
|
|
86
|
+
@has_enough_credits = data["has_enough_credits"] || data["hasEnoughCredits"] || false
|
|
87
|
+
@breakdown = data["breakdown"]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def enough_credits?
|
|
91
|
+
has_enough_credits
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
class CampaignsResource
|
|
96
|
+
def initialize(client)
|
|
97
|
+
@client = client
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def create(name:, text:, contact_list_ids:, template_id: nil)
|
|
101
|
+
body = {
|
|
102
|
+
name: name,
|
|
103
|
+
text: text,
|
|
104
|
+
contactListIds: contact_list_ids
|
|
105
|
+
}
|
|
106
|
+
body[:templateId] = template_id if template_id
|
|
107
|
+
|
|
108
|
+
response = @client.post("/campaigns", body)
|
|
109
|
+
Campaign.new(response)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def list(limit: nil, offset: nil, status: nil)
|
|
113
|
+
params = {}
|
|
114
|
+
params[:limit] = limit if limit
|
|
115
|
+
params[:offset] = offset if offset
|
|
116
|
+
params[:status] = status if status
|
|
117
|
+
|
|
118
|
+
response = @client.get("/campaigns", params)
|
|
119
|
+
campaigns = (response["campaigns"] || []).map { |c| Campaign.new(c) }
|
|
120
|
+
{
|
|
121
|
+
campaigns: campaigns,
|
|
122
|
+
total: response["total"],
|
|
123
|
+
limit: response["limit"],
|
|
124
|
+
offset: response["offset"]
|
|
125
|
+
}
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def get(id)
|
|
129
|
+
response = @client.get("/campaigns/#{id}")
|
|
130
|
+
Campaign.new(response)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def update(id, name: nil, text: nil, template_id: nil, contact_list_ids: nil)
|
|
134
|
+
body = {}
|
|
135
|
+
body[:name] = name if name
|
|
136
|
+
body[:text] = text if text
|
|
137
|
+
body[:templateId] = template_id unless template_id.nil?
|
|
138
|
+
body[:contactListIds] = contact_list_ids if contact_list_ids
|
|
139
|
+
|
|
140
|
+
response = @client.patch("/campaigns/#{id}", body)
|
|
141
|
+
Campaign.new(response)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def delete(id)
|
|
145
|
+
@client.delete("/campaigns/#{id}")
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def preview(id)
|
|
149
|
+
response = @client.get("/campaigns/#{id}/preview")
|
|
150
|
+
CampaignPreview.new(response)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def send_campaign(id)
|
|
154
|
+
response = @client.post("/campaigns/#{id}/send")
|
|
155
|
+
Campaign.new(response)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def schedule(id, scheduled_at:, timezone: nil)
|
|
159
|
+
body = { scheduledAt: scheduled_at }
|
|
160
|
+
body[:timezone] = timezone if timezone
|
|
161
|
+
|
|
162
|
+
response = @client.post("/campaigns/#{id}/schedule", body)
|
|
163
|
+
Campaign.new(response)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def cancel(id)
|
|
167
|
+
response = @client.post("/campaigns/#{id}/cancel")
|
|
168
|
+
Campaign.new(response)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def clone(id)
|
|
172
|
+
response = @client.post("/campaigns/#{id}/clone")
|
|
173
|
+
Campaign.new(response)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
data/lib/sendly/client.rb
CHANGED
|
@@ -73,6 +73,20 @@ module Sendly
|
|
|
73
73
|
@templates ||= TemplatesResource.new(self)
|
|
74
74
|
end
|
|
75
75
|
|
|
76
|
+
# Access the Campaigns resource
|
|
77
|
+
#
|
|
78
|
+
# @return [Sendly::CampaignsResource]
|
|
79
|
+
def campaigns
|
|
80
|
+
@campaigns ||= CampaignsResource.new(self)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Access the Contacts resource
|
|
84
|
+
#
|
|
85
|
+
# @return [Sendly::ContactsResource]
|
|
86
|
+
def contacts
|
|
87
|
+
@contacts ||= ContactsResource.new(self)
|
|
88
|
+
end
|
|
89
|
+
|
|
76
90
|
# Make a GET request
|
|
77
91
|
#
|
|
78
92
|
# @param path [String] API path
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sendly
|
|
4
|
+
class Contact
|
|
5
|
+
attr_reader :id, :phone_number, :name, :email, :metadata, :opted_out,
|
|
6
|
+
:created_at, :updated_at, :lists
|
|
7
|
+
|
|
8
|
+
def initialize(data)
|
|
9
|
+
@id = data["id"]
|
|
10
|
+
@phone_number = data["phone_number"] || data["phoneNumber"]
|
|
11
|
+
@name = data["name"]
|
|
12
|
+
@email = data["email"]
|
|
13
|
+
@metadata = data["metadata"] || {}
|
|
14
|
+
@opted_out = data["opted_out"] || data["optedOut"] || false
|
|
15
|
+
@created_at = parse_time(data["created_at"] || data["createdAt"])
|
|
16
|
+
@updated_at = parse_time(data["updated_at"] || data["updatedAt"])
|
|
17
|
+
@lists = data["lists"]&.map { |l| { id: l["id"], name: l["name"] } }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def opted_out?
|
|
21
|
+
opted_out
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def to_h
|
|
25
|
+
{
|
|
26
|
+
id: id, phone_number: phone_number, name: name, email: email,
|
|
27
|
+
metadata: metadata, opted_out: opted_out,
|
|
28
|
+
created_at: created_at&.iso8601, updated_at: updated_at&.iso8601,
|
|
29
|
+
lists: lists
|
|
30
|
+
}.compact
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def parse_time(value)
|
|
36
|
+
return nil if value.nil?
|
|
37
|
+
Time.parse(value)
|
|
38
|
+
rescue ArgumentError
|
|
39
|
+
nil
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
class ContactList
|
|
44
|
+
attr_reader :id, :name, :description, :contact_count, :created_at,
|
|
45
|
+
:updated_at, :contacts, :contacts_total
|
|
46
|
+
|
|
47
|
+
def initialize(data)
|
|
48
|
+
@id = data["id"]
|
|
49
|
+
@name = data["name"]
|
|
50
|
+
@description = data["description"]
|
|
51
|
+
@contact_count = data["contact_count"] || data["contactCount"] || 0
|
|
52
|
+
@created_at = parse_time(data["created_at"] || data["createdAt"])
|
|
53
|
+
@updated_at = parse_time(data["updated_at"] || data["updatedAt"])
|
|
54
|
+
@contacts = data["contacts"]&.map do |c|
|
|
55
|
+
{
|
|
56
|
+
id: c["id"],
|
|
57
|
+
phone_number: c["phone_number"] || c["phoneNumber"],
|
|
58
|
+
name: c["name"],
|
|
59
|
+
email: c["email"]
|
|
60
|
+
}
|
|
61
|
+
end
|
|
62
|
+
@contacts_total = data["contacts_total"] || data["contactsTotal"]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def to_h
|
|
66
|
+
{
|
|
67
|
+
id: id, name: name, description: description,
|
|
68
|
+
contact_count: contact_count, created_at: created_at&.iso8601,
|
|
69
|
+
updated_at: updated_at&.iso8601, contacts: contacts,
|
|
70
|
+
contacts_total: contacts_total
|
|
71
|
+
}.compact
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def parse_time(value)
|
|
77
|
+
return nil if value.nil?
|
|
78
|
+
Time.parse(value)
|
|
79
|
+
rescue ArgumentError
|
|
80
|
+
nil
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
class ContactListsResource
|
|
85
|
+
def initialize(client)
|
|
86
|
+
@client = client
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def list
|
|
90
|
+
response = @client.get("/contact-lists")
|
|
91
|
+
lists = (response["lists"] || []).map { |l| ContactList.new(l) }
|
|
92
|
+
{ lists: lists }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def get(id, limit: nil, offset: nil)
|
|
96
|
+
params = {}
|
|
97
|
+
params[:limit] = limit if limit
|
|
98
|
+
params[:offset] = offset if offset
|
|
99
|
+
|
|
100
|
+
response = @client.get("/contact-lists/#{id}", params)
|
|
101
|
+
ContactList.new(response)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def create(name:, description: nil)
|
|
105
|
+
body = { name: name }
|
|
106
|
+
body[:description] = description if description
|
|
107
|
+
|
|
108
|
+
response = @client.post("/contact-lists", body)
|
|
109
|
+
ContactList.new(response)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def update(id, name: nil, description: nil)
|
|
113
|
+
body = {}
|
|
114
|
+
body[:name] = name if name
|
|
115
|
+
body[:description] = description unless description.nil?
|
|
116
|
+
|
|
117
|
+
response = @client.patch("/contact-lists/#{id}", body)
|
|
118
|
+
ContactList.new(response)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def delete(id)
|
|
122
|
+
@client.delete("/contact-lists/#{id}")
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def add_contacts(list_id, contact_ids)
|
|
126
|
+
response = @client.post("/contact-lists/#{list_id}/contacts", { contact_ids: contact_ids })
|
|
127
|
+
{ added_count: response["added_count"] || response["addedCount"] }
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def remove_contact(list_id, contact_id)
|
|
131
|
+
@client.delete("/contact-lists/#{list_id}/contacts/#{contact_id}")
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
class ContactsResource
|
|
136
|
+
attr_reader :lists
|
|
137
|
+
|
|
138
|
+
def initialize(client)
|
|
139
|
+
@client = client
|
|
140
|
+
@lists = ContactListsResource.new(client)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def list(limit: nil, offset: nil, search: nil, list_id: nil)
|
|
144
|
+
params = {}
|
|
145
|
+
params[:limit] = limit if limit
|
|
146
|
+
params[:offset] = offset if offset
|
|
147
|
+
params[:search] = search if search
|
|
148
|
+
params[:list_id] = list_id if list_id
|
|
149
|
+
|
|
150
|
+
response = @client.get("/contacts", params)
|
|
151
|
+
contacts = (response["contacts"] || []).map { |c| Contact.new(c) }
|
|
152
|
+
{
|
|
153
|
+
contacts: contacts,
|
|
154
|
+
total: response["total"],
|
|
155
|
+
limit: response["limit"],
|
|
156
|
+
offset: response["offset"]
|
|
157
|
+
}
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def get(id)
|
|
161
|
+
response = @client.get("/contacts/#{id}")
|
|
162
|
+
Contact.new(response)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def create(phone_number:, name: nil, email: nil, metadata: nil)
|
|
166
|
+
body = { phone_number: phone_number }
|
|
167
|
+
body[:name] = name if name
|
|
168
|
+
body[:email] = email if email
|
|
169
|
+
body[:metadata] = metadata if metadata
|
|
170
|
+
|
|
171
|
+
response = @client.post("/contacts", body)
|
|
172
|
+
Contact.new(response)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def update(id, name: nil, email: nil, metadata: nil)
|
|
176
|
+
body = {}
|
|
177
|
+
body[:name] = name unless name.nil?
|
|
178
|
+
body[:email] = email unless email.nil?
|
|
179
|
+
body[:metadata] = metadata unless metadata.nil?
|
|
180
|
+
|
|
181
|
+
response = @client.patch("/contacts/#{id}", body)
|
|
182
|
+
Contact.new(response)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def delete(id)
|
|
186
|
+
@client.delete("/contacts/#{id}")
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
@@ -104,5 +104,12 @@ module Sendly
|
|
|
104
104
|
response = @client.post("/verify/templates/#{id}/unpublish")
|
|
105
105
|
Template.new(response)
|
|
106
106
|
end
|
|
107
|
+
|
|
108
|
+
def clone(id, name: nil)
|
|
109
|
+
body = {}
|
|
110
|
+
body[:name] = name if name
|
|
111
|
+
response = @client.post("/templates/#{id}/clone", body)
|
|
112
|
+
Template.new(response)
|
|
113
|
+
end
|
|
107
114
|
end
|
|
108
115
|
end
|
data/lib/sendly/version.rb
CHANGED
data/lib/sendly.rb
CHANGED
|
@@ -13,6 +13,8 @@ require_relative "sendly/webhooks_resource"
|
|
|
13
13
|
require_relative "sendly/account_resource"
|
|
14
14
|
require_relative "sendly/verify"
|
|
15
15
|
require_relative "sendly/templates_resource"
|
|
16
|
+
require_relative "sendly/campaigns_resource"
|
|
17
|
+
require_relative "sendly/contacts_resource"
|
|
16
18
|
|
|
17
19
|
# Sendly Ruby SDK
|
|
18
20
|
#
|
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.13.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-01-
|
|
11
|
+
date: 2026-01-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|
|
@@ -124,7 +124,9 @@ files:
|
|
|
124
124
|
- examples/send_sms.rb
|
|
125
125
|
- lib/sendly.rb
|
|
126
126
|
- lib/sendly/account_resource.rb
|
|
127
|
+
- lib/sendly/campaigns_resource.rb
|
|
127
128
|
- lib/sendly/client.rb
|
|
129
|
+
- lib/sendly/contacts_resource.rb
|
|
128
130
|
- lib/sendly/errors.rb
|
|
129
131
|
- lib/sendly/messages.rb
|
|
130
132
|
- lib/sendly/templates_resource.rb
|