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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d6daab4e1812cb359de80d59229c8895c8b4542abd2b6d6fbbaafe2b495bef9a
4
- data.tar.gz: f200cbebe0bd5bc5ed70b23c691f6061fa98790aee74c4d1f84487d52540e1ba
3
+ metadata.gz: c41be641303c996be30a1408b25aa49607676c0b6a8511ced4a5275e439602d9
4
+ data.tar.gz: 6228171a120073a65d7fe6b52500906785022d4c2d185e2b678b28a2ffc7e9b3
5
5
  SHA512:
6
- metadata.gz: f2dd68745cee4033698c2f0bcf13da03fc08b47956b35c700920afa3b75dd53d133ccd0b5c46796bf117298f2848f67ab088393fd3ca82563278034e050ebdc0
7
- data.tar.gz: 385b82af9aa6d59fdca06faa84b87f77bf3822d57a177a41b1c8f18bfc00402f53e97c5c9067f9a8921cab1752bb3648a99cef43a31317ab2e9ef28c653d139a
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.26.0)
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.8.9)
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
- :channel, :expires_at, :verified_at, :created_at, :sandbox,
7
- :app_name, :template_id, :profile_id, :metadata
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["deliveryStatus"] || data["delivery_status"]
15
+ @delivery_status = data["delivery_status"]
17
16
  @attempts = data["attempts"] || 0
18
- @max_attempts = data["maxAttempts"] || data["max_attempts"] || 3
19
- @channel = data["channel"] || "sms"
20
- @expires_at = parse_time(data["expiresAt"] || data["expires_at"])
21
- @verified_at = parse_time(data["verifiedAt"] || data["verified_at"])
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["appName"] || data["app_name"]
25
- @template_id = data["templateId"] || data["template_id"]
26
- @profile_id = data["profileId"] || data["profile_id"]
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, channel: channel,
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, metadata: metadata
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 :verification, :code
64
+ attr_reader :id, :status, :phone, :expires_at, :sandbox, :sandbox_code, :message
68
65
 
69
66
  def initialize(data)
70
- @verification = Verification.new(data["verification"])
71
- @code = data["code"]
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 :valid, :status, :verification
78
+ attr_reader :id, :status, :phone, :verified_at, :remaining_attempts
77
79
 
78
80
  def initialize(data)
79
- @valid = data["valid"]
81
+ @id = data["id"]
80
82
  @status = data["status"]
81
- @verification = data["verification"] ? Verification.new(data["verification"]) : nil
83
+ @phone = data["phone"]
84
+ @verified_at = data["verified_at"]
85
+ @remaining_attempts = data["remaining_attempts"]
82
86
  end
83
87
 
84
- def valid?
85
- valid
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(phone:, channel: nil, code_length: nil, expires_in: nil, max_attempts: nil,
167
- template_id: nil, profile_id: nil, app_name: nil, locale: nil, metadata: nil)
168
- body = { to: phone }
169
- body[:channel] = channel if channel
170
- body[:codeLength] = code_length if code_length
171
- body[:expiresIn] = expires_in if expires_in
172
- body[:maxAttempts] = max_attempts if max_attempts
173
- body[:templateId] = template_id if template_id
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, phone: 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) }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sendly
4
- VERSION = "3.26.0"
4
+ VERSION = "3.29.0"
5
5
  end
@@ -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.26.0
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-03-25 00:00:00.000000000 Z
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