sentdm 0.32.0 → 0.34.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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/README.md +1 -1
  4. data/lib/sentdm/client.rb +6 -0
  5. data/lib/sentdm/models/channel_event_payload.rb +154 -7
  6. data/lib/sentdm/models/contact_event.rb +15 -7
  7. data/lib/sentdm/models/contact_event_payload.rb +77 -16
  8. data/lib/sentdm/models/conversation_messages_list.rb +214 -6
  9. data/lib/sentdm/models/inbound_message_event_payload.rb +70 -3
  10. data/lib/sentdm/models/me_retrieve_response.rb +21 -1
  11. data/lib/sentdm/models/message_event_payload.rb +42 -1
  12. data/lib/sentdm/models/message_retrieve_activities_response.rb +42 -5
  13. data/lib/sentdm/models/message_retrieve_status_response.rb +222 -8
  14. data/lib/sentdm/models/message_send_params.rb +67 -3
  15. data/lib/sentdm/models/message_send_response.rb +13 -4
  16. data/lib/sentdm/models/template_body.rb +82 -1
  17. data/lib/sentdm/models/template_create_params.rb +11 -1
  18. data/lib/sentdm/models/template_header.rb +91 -3
  19. data/lib/sentdm/models/template_variable.rb +15 -1
  20. data/lib/sentdm/models/webhook_create_params.rb +31 -1
  21. data/lib/sentdm/models/webhook_list_events_response.rb +497 -9
  22. data/lib/sentdm/models/webhook_update_params.rb +31 -1
  23. data/lib/sentdm/resources/me.rb +5 -0
  24. data/lib/sentdm/resources/messages.rb +33 -5
  25. data/lib/sentdm/resources/templates.rb +3 -1
  26. data/lib/sentdm/resources/webhooks.rb +9 -3
  27. data/lib/sentdm/version.rb +1 -1
  28. data/lib/sentdm.rb +1 -1
  29. data/rbi/sentdm/client.rbi +6 -0
  30. data/rbi/sentdm/models/channel_event_payload.rbi +273 -10
  31. data/rbi/sentdm/models/contact_event.rbi +28 -12
  32. data/rbi/sentdm/models/contact_event_payload.rbi +118 -28
  33. data/rbi/sentdm/models/conversation_messages_list.rbi +308 -10
  34. data/rbi/sentdm/models/inbound_message_event_payload.rbi +110 -2
  35. data/rbi/sentdm/models/me_retrieve_response.rbi +35 -0
  36. data/rbi/sentdm/models/message_event_payload.rbi +50 -0
  37. data/rbi/sentdm/models/message_retrieve_activities_response.rbi +49 -5
  38. data/rbi/sentdm/models/message_retrieve_status_response.rbi +320 -12
  39. data/rbi/sentdm/models/message_send_params.rbi +100 -2
  40. data/rbi/sentdm/models/message_send_response.rbi +15 -5
  41. data/rbi/sentdm/models/template_body.rbi +133 -0
  42. data/rbi/sentdm/models/template_create_params.rbi +15 -0
  43. data/rbi/sentdm/models/template_header.rbi +143 -2
  44. data/rbi/sentdm/models/template_variable.rbi +10 -0
  45. data/rbi/sentdm/models/webhook_create_params.rbi +61 -0
  46. data/rbi/sentdm/models/webhook_list_events_response.rbi +747 -12
  47. data/rbi/sentdm/models/webhook_update_params.rbi +61 -0
  48. data/rbi/sentdm/resources/me.rbi +5 -0
  49. data/rbi/sentdm/resources/messages.rbi +58 -3
  50. data/rbi/sentdm/resources/templates.rbi +6 -0
  51. data/rbi/sentdm/resources/webhooks.rbi +15 -1
  52. data/sig/sentdm/models/channel_event_payload.rbs +62 -0
  53. data/sig/sentdm/models/contact_event_payload.rbs +24 -9
  54. data/sig/sentdm/models/conversation_messages_list.rbs +99 -6
  55. data/sig/sentdm/models/inbound_message_event_payload.rbs +37 -0
  56. data/sig/sentdm/models/me_retrieve_response.rbs +7 -0
  57. data/sig/sentdm/models/message_event_payload.rbs +20 -0
  58. data/sig/sentdm/models/message_retrieve_activities_response.rbs +15 -0
  59. data/sig/sentdm/models/message_retrieve_status_response.rbs +99 -6
  60. data/sig/sentdm/models/message_send_params.rbs +15 -0
  61. data/sig/sentdm/models/template_body.rbs +44 -0
  62. data/sig/sentdm/models/template_create_params.rbs +7 -0
  63. data/sig/sentdm/models/template_header.rbs +44 -0
  64. data/sig/sentdm/models/webhook_create_params.rbs +29 -0
  65. data/sig/sentdm/models/webhook_list_events_response.rbs +252 -0
  66. data/sig/sentdm/models/webhook_update_params.rbs +29 -0
  67. data/sig/sentdm/resources/messages.rbs +3 -0
  68. data/sig/sentdm/resources/templates.rbs +1 -0
  69. data/sig/sentdm/resources/webhooks.rbs +2 -0
  70. metadata +2 -2
@@ -11,7 +11,12 @@ module Sentdm
11
11
  )
12
12
  end
13
13
 
14
- # Message response for v3 API — same shape as v2 with snake_case JSON conventions
14
+ # Message response for v3 API — same shape as v2 with snake_case JSON conventions.
15
+ #
16
+ # The shape of a message that was sent immediately: it never has a scheduled_at
17
+ # key. A message that is or was held for a later instant is a
18
+ # ScheduledMessageResponse, and the endpoint decides which of the two to answer
19
+ # with. From always returns this type.
15
20
  sig do
16
21
  returns(T.nilable(Sentdm::Models::MessageRetrieveStatusResponse::Data))
17
22
  end
@@ -61,7 +66,12 @@ module Sentdm
61
66
  ).returns(T.attached_class)
62
67
  end
63
68
  def self.new(
64
- # Message response for v3 API — same shape as v2 with snake_case JSON conventions
69
+ # Message response for v3 API — same shape as v2 with snake_case JSON conventions.
70
+ #
71
+ # The shape of a message that was sent immediately: it never has a scheduled_at
72
+ # key. A message that is or was held for a later instant is a
73
+ # ScheduledMessageResponse, and the endpoint decides which of the two to answer
74
+ # with. From always returns this type.
65
75
  data: nil,
66
76
  # Error information
67
77
  error: nil,
@@ -146,7 +156,14 @@ module Sentdm
146
156
  attr_accessor :events
147
157
 
148
158
  # Structured message body format for database storage. Preserves channel-specific
149
- # components (header, body, footer, buttons).
159
+ # components (header, header media, body, footer, buttons, MMS subject and media).
160
+ #
161
+ # Persisted as the messageBody jsonb column on Messages. Every write path goes
162
+ # through MessageUtils.MessageBodyJsonOptions, which writes nulls, so the envelope
163
+ # shape is stable regardless of channel or status. Anything that rebuilds this
164
+ # object field by field — the four IMessageBodyStrategy implementations and
165
+ # MessageUtils.BuildSegmentBody — has to carry every member, or that member is
166
+ # silently dropped on whichever path forgot it.
150
167
  sig do
151
168
  returns(
152
169
  T.nilable(
@@ -181,6 +198,20 @@ module Sentdm
181
198
  sig { returns(T.nilable(Float)) }
182
199
  attr_accessor :price
183
200
 
201
+ # A human-readable sentence for reason_code, for example "Insufficient balance".
202
+ # Omitted whenever reason_code is.
203
+ sig { returns(T.nilable(String)) }
204
+ attr_accessor :reason
205
+
206
+ # Why the message is at its current status, as a stable platform code such as
207
+ # DELIVERY_007, BUSINESS_003 or DELIVERY_003. Present when the current status is
208
+ # FAILED, FILTERED or BLOCKED and the lifecycle was loaded; omitted otherwise.
209
+ # Switch on this rather than on reason: the code is stable, the wording may be
210
+ # improved. It is the platform's classification of the outcome, never a carrier or
211
+ # vendor code.
212
+ sig { returns(T.nilable(String)) }
213
+ attr_accessor :reason_code
214
+
184
215
  sig { returns(T.nilable(String)) }
185
216
  attr_reader :region_code
186
217
 
@@ -202,7 +233,12 @@ module Sentdm
202
233
  sig { returns(T.nilable(String)) }
203
234
  attr_accessor :template_name
204
235
 
205
- # Message response for v3 API — same shape as v2 with snake_case JSON conventions
236
+ # Message response for v3 API — same shape as v2 with snake_case JSON conventions.
237
+ #
238
+ # The shape of a message that was sent immediately: it never has a scheduled_at
239
+ # key. A message that is or was held for a later instant is a
240
+ # ScheduledMessageResponse, and the endpoint decides which of the two to answer
241
+ # with. From always returns this type.
206
242
  sig do
207
243
  params(
208
244
  id: String,
@@ -225,6 +261,8 @@ module Sentdm
225
261
  phone: String,
226
262
  phone_international: String,
227
263
  price: T.nilable(Float),
264
+ reason: T.nilable(String),
265
+ reason_code: T.nilable(String),
228
266
  region_code: String,
229
267
  status: String,
230
268
  template_category: T.nilable(String),
@@ -242,11 +280,28 @@ module Sentdm
242
280
  direction: nil,
243
281
  events: nil,
244
282
  # Structured message body format for database storage. Preserves channel-specific
245
- # components (header, body, footer, buttons).
283
+ # components (header, header media, body, footer, buttons, MMS subject and media).
284
+ #
285
+ # Persisted as the messageBody jsonb column on Messages. Every write path goes
286
+ # through MessageUtils.MessageBodyJsonOptions, which writes nulls, so the envelope
287
+ # shape is stable regardless of channel or status. Anything that rebuilds this
288
+ # object field by field — the four IMessageBodyStrategy implementations and
289
+ # MessageUtils.BuildSegmentBody — has to carry every member, or that member is
290
+ # silently dropped on whichever path forgot it.
246
291
  message_body: nil,
247
292
  phone: nil,
248
293
  phone_international: nil,
249
294
  price: nil,
295
+ # A human-readable sentence for reason_code, for example "Insufficient balance".
296
+ # Omitted whenever reason_code is.
297
+ reason: nil,
298
+ # Why the message is at its current status, as a stable platform code such as
299
+ # DELIVERY_007, BUSINESS_003 or DELIVERY_003. Present when the current status is
300
+ # FAILED, FILTERED or BLOCKED and the lifecycle was loaded; omitted otherwise.
301
+ # Switch on this rather than on reason: the code is stable, the wording may be
302
+ # improved. It is the platform's classification of the outcome, never a carrier or
303
+ # vendor code.
304
+ reason_code: nil,
250
305
  region_code: nil,
251
306
  status: nil,
252
307
  template_category: nil,
@@ -278,6 +333,8 @@ module Sentdm
278
333
  phone: String,
279
334
  phone_international: String,
280
335
  price: T.nilable(Float),
336
+ reason: T.nilable(String),
337
+ reason_code: T.nilable(String),
281
338
  region_code: String,
282
339
  status: String,
283
340
  template_category: T.nilable(String),
@@ -307,15 +364,39 @@ module Sentdm
307
364
  sig { returns(T.nilable(String)) }
308
365
  attr_accessor :description
309
366
 
367
+ # A human-readable sentence for reason_code. Omitted whenever reason_code is.
368
+ sig { returns(T.nilable(String)) }
369
+ attr_accessor :reason
370
+
371
+ # Why the message reached this status, as a stable platform code such as
372
+ # DELIVERY_007. Present on FAILED, FILTERED and BLOCKED events; omitted on every
373
+ # status that needs no explanation. Same wire name and vocabulary as on the
374
+ # activities list and the webhook.
375
+ sig { returns(T.nilable(String)) }
376
+ attr_accessor :reason_code
377
+
310
378
  # Represents a status change event in a message's lifecycle (v3)
311
379
  sig do
312
380
  params(
313
381
  status: String,
314
382
  timestamp: Time,
315
- description: T.nilable(String)
383
+ description: T.nilable(String),
384
+ reason: T.nilable(String),
385
+ reason_code: T.nilable(String)
316
386
  ).returns(T.attached_class)
317
387
  end
318
- def self.new(status:, timestamp:, description: nil)
388
+ def self.new(
389
+ status:,
390
+ timestamp:,
391
+ description: nil,
392
+ # A human-readable sentence for reason_code. Omitted whenever reason_code is.
393
+ reason: nil,
394
+ # Why the message reached this status, as a stable platform code such as
395
+ # DELIVERY_007. Present on FAILED, FILTERED and BLOCKED events; omitted on every
396
+ # status that needs no explanation. Same wire name and vocabulary as on the
397
+ # activities list and the webhook.
398
+ reason_code: nil
399
+ )
319
400
  end
320
401
 
321
402
  sig do
@@ -323,7 +404,9 @@ module Sentdm
323
404
  {
324
405
  status: String,
325
406
  timestamp: Time,
326
- description: T.nilable(String)
407
+ description: T.nilable(String),
408
+ reason: T.nilable(String),
409
+ reason_code: T.nilable(String)
327
410
  }
328
411
  )
329
412
  end
@@ -363,8 +446,57 @@ module Sentdm
363
446
  sig { returns(T.nilable(String)) }
364
447
  attr_accessor :header
365
448
 
449
+ # The media asset that rode a message's header, recorded as sent.
450
+ sig do
451
+ returns(
452
+ T.nilable(
453
+ Sentdm::Models::MessageRetrieveStatusResponse::Data::MessageBody::HeaderMedia
454
+ )
455
+ )
456
+ end
457
+ attr_reader :header_media
458
+
459
+ sig do
460
+ params(
461
+ header_media:
462
+ T.nilable(
463
+ Sentdm::Models::MessageRetrieveStatusResponse::Data::MessageBody::HeaderMedia::OrHash
464
+ )
465
+ ).void
466
+ end
467
+ attr_writer :header_media
468
+
469
+ # MMS attachments, as the publicly fetchable URLs handed to the carrier. Null on
470
+ # every other channel.
471
+ #
472
+ # Persisted rather than derived because a resend and a curfew release rebuild the
473
+ # send from the stored row — MessageReplayCommandBuilder reads templateId and
474
+ # templateVariables and nothing else — so media that lives only on the original
475
+ # request would silently turn a replayed MMS into a text message.
476
+ sig do
477
+ returns(
478
+ T.nilable(
479
+ T::Array[
480
+ Sentdm::Models::MessageRetrieveStatusResponse::Data::MessageBody::Media
481
+ ]
482
+ )
483
+ )
484
+ end
485
+ attr_accessor :media
486
+
487
+ # MMS subject line. Null on every other channel.
488
+ sig { returns(T.nilable(String)) }
489
+ attr_accessor :subject
490
+
366
491
  # Structured message body format for database storage. Preserves channel-specific
367
- # components (header, body, footer, buttons).
492
+ # components (header, header media, body, footer, buttons, MMS subject and media).
493
+ #
494
+ # Persisted as the messageBody jsonb column on Messages. Every write path goes
495
+ # through MessageUtils.MessageBodyJsonOptions, which writes nulls, so the envelope
496
+ # shape is stable regardless of channel or status. Anything that rebuilds this
497
+ # object field by field — the four IMessageBodyStrategy implementations and
498
+ # MessageUtils.BuildSegmentBody — has to carry every member, or that member is
499
+ # silently dropped on whichever path forgot it.
368
500
  sig do
369
501
  params(
370
502
  buttons:
@@ -375,10 +507,38 @@ module Sentdm
375
507
  ),
376
508
  content: String,
377
509
  footer: T.nilable(String),
378
- header: T.nilable(String)
510
+ header: T.nilable(String),
511
+ header_media:
512
+ T.nilable(
513
+ Sentdm::Models::MessageRetrieveStatusResponse::Data::MessageBody::HeaderMedia::OrHash
514
+ ),
515
+ media:
516
+ T.nilable(
517
+ T::Array[
518
+ Sentdm::Models::MessageRetrieveStatusResponse::Data::MessageBody::Media::OrHash
519
+ ]
520
+ ),
521
+ subject: T.nilable(String)
379
522
  ).returns(T.attached_class)
380
523
  end
381
- def self.new(buttons: nil, content: nil, footer: nil, header: nil)
524
+ def self.new(
525
+ buttons: nil,
526
+ content: nil,
527
+ footer: nil,
528
+ header: nil,
529
+ # The media asset that rode a message's header, recorded as sent.
530
+ header_media: nil,
531
+ # MMS attachments, as the publicly fetchable URLs handed to the carrier. Null on
532
+ # every other channel.
533
+ #
534
+ # Persisted rather than derived because a resend and a curfew release rebuild the
535
+ # send from the stored row — MessageReplayCommandBuilder reads templateId and
536
+ # templateVariables and nothing else — so media that lives only on the original
537
+ # request would silently turn a replayed MMS into a text message.
538
+ media: nil,
539
+ # MMS subject line. Null on every other channel.
540
+ subject: nil
541
+ )
382
542
  end
383
543
 
384
544
  sig do
@@ -392,7 +552,18 @@ module Sentdm
392
552
  ),
393
553
  content: String,
394
554
  footer: T.nilable(String),
395
- header: T.nilable(String)
555
+ header: T.nilable(String),
556
+ header_media:
557
+ T.nilable(
558
+ Sentdm::Models::MessageRetrieveStatusResponse::Data::MessageBody::HeaderMedia
559
+ ),
560
+ media:
561
+ T.nilable(
562
+ T::Array[
563
+ Sentdm::Models::MessageRetrieveStatusResponse::Data::MessageBody::Media
564
+ ]
565
+ ),
566
+ subject: T.nilable(String)
396
567
  }
397
568
  )
398
569
  end
@@ -450,6 +621,143 @@ module Sentdm
450
621
  def to_hash
451
622
  end
452
623
  end
624
+
625
+ class HeaderMedia < Sentdm::Internal::Type::BaseModel
626
+ OrHash =
627
+ T.type_alias do
628
+ T.any(
629
+ Sentdm::Models::MessageRetrieveStatusResponse::Data::MessageBody::HeaderMedia,
630
+ Sentdm::Internal::AnyHash
631
+ )
632
+ end
633
+
634
+ # "image", "video" or "document" — taken from the header's media variable.
635
+ sig { returns(T.nilable(String)) }
636
+ attr_reader :type
637
+
638
+ sig { params(type: String).void }
639
+ attr_writer :type
640
+
641
+ # The https URL the caller supplied for this send. Never the template's stored
642
+ # props.sample, which is Meta's expiring header_handle rather than what was
643
+ # delivered.
644
+ sig { returns(T.nilable(String)) }
645
+ attr_reader :url
646
+
647
+ sig { params(url: String).void }
648
+ attr_writer :url
649
+
650
+ # The media asset that rode a message's header, recorded as sent.
651
+ sig { params(type: String, url: String).returns(T.attached_class) }
652
+ def self.new(
653
+ # "image", "video" or "document" — taken from the header's media variable.
654
+ type: nil,
655
+ # The https URL the caller supplied for this send. Never the template's stored
656
+ # props.sample, which is Meta's expiring header_handle rather than what was
657
+ # delivered.
658
+ url: nil
659
+ )
660
+ end
661
+
662
+ sig { override.returns({ type: String, url: String }) }
663
+ def to_hash
664
+ end
665
+ end
666
+
667
+ class Media < Sentdm::Internal::Type::BaseModel
668
+ OrHash =
669
+ T.type_alias do
670
+ T.any(
671
+ Sentdm::Models::MessageRetrieveStatusResponse::Data::MessageBody::Media,
672
+ Sentdm::Internal::AnyHash
673
+ )
674
+ end
675
+
676
+ # One of MmsMediaTypes when the content type is known. Advisory — a reader should
677
+ # trust the fetched object's own Content-Type.
678
+ sig { returns(T.nilable(String)) }
679
+ attr_accessor :media_type
680
+
681
+ # Content type as the provider declared it. Null when it declared none.
682
+ sig { returns(T.nilable(String)) }
683
+ attr_accessor :mime_type
684
+
685
+ # Size as the provider declared it. Never measured here — nothing downloads the
686
+ # file.
687
+ sig { returns(T.nilable(Integer)) }
688
+ attr_accessor :size_bytes
689
+
690
+ # Inbound only: the SHA-256 the provider declared alongside the attachment, when
691
+ # it declared one. Relayed to the customer so they can verify what they fetch
692
+ # matches what the carrier said it sent. It is the only integrity signal available
693
+ # on an attachment nobody here has read.
694
+ sig { returns(T.nilable(String)) }
695
+ attr_accessor :source_hash_sha256
696
+
697
+ # Where the file lives. Outbound: the URL the customer gave us and the carrier
698
+ # fetched. Inbound: the URL the carrier hosts it at, relayed unchanged.
699
+ sig { returns(T.nilable(String)) }
700
+ attr_accessor :url
701
+
702
+ # One attachment on a message, in either direction — and in both, a URL somebody
703
+ # else hosts.
704
+ #
705
+ # Outbound: the customer supplied a public URL and we handed it to the carrier.
706
+ # Inbound: the carrier hosts the file and we record where. sent.dm never holds the
707
+ # bytes, so there is no key, no expiry bookkeeping and nothing minted per read —
708
+ # what is stored is what is served.
709
+ #
710
+ # An inbound link expires on the carrier's own schedule and is unauthenticated.
711
+ # That is the customer's to manage, and it is documented where they will see it
712
+ # rather than only here — a recipient who needs an attachment to outlive that
713
+ # window copies it on receipt.
714
+ #
715
+ # Storing a presigned URL is the specific mistake this shape still avoids:
716
+ # M260826130000 and M260826140000 exist because RCS assets were stored as signed
717
+ # URLs and went stale. Nothing here is signed.
718
+ sig do
719
+ params(
720
+ media_type: T.nilable(String),
721
+ mime_type: T.nilable(String),
722
+ size_bytes: T.nilable(Integer),
723
+ source_hash_sha256: T.nilable(String),
724
+ url: T.nilable(String)
725
+ ).returns(T.attached_class)
726
+ end
727
+ def self.new(
728
+ # One of MmsMediaTypes when the content type is known. Advisory — a reader should
729
+ # trust the fetched object's own Content-Type.
730
+ media_type: nil,
731
+ # Content type as the provider declared it. Null when it declared none.
732
+ mime_type: nil,
733
+ # Size as the provider declared it. Never measured here — nothing downloads the
734
+ # file.
735
+ size_bytes: nil,
736
+ # Inbound only: the SHA-256 the provider declared alongside the attachment, when
737
+ # it declared one. Relayed to the customer so they can verify what they fetch
738
+ # matches what the carrier said it sent. It is the only integrity signal available
739
+ # on an attachment nobody here has read.
740
+ source_hash_sha256: nil,
741
+ # Where the file lives. Outbound: the URL the customer gave us and the carrier
742
+ # fetched. Inbound: the URL the carrier hosts it at, relayed unchanged.
743
+ url: nil
744
+ )
745
+ end
746
+
747
+ sig do
748
+ override.returns(
749
+ {
750
+ media_type: T.nilable(String),
751
+ mime_type: T.nilable(String),
752
+ size_bytes: T.nilable(Integer),
753
+ source_hash_sha256: T.nilable(String),
754
+ url: T.nilable(String)
755
+ }
756
+ )
757
+ end
758
+ def to_hash
759
+ end
760
+ end
453
761
  end
454
762
  end
455
763
  end
@@ -17,6 +17,23 @@ module Sentdm
17
17
  sig { returns(T.nilable(T::Array[String])) }
18
18
  attr_accessor :channel
19
19
 
20
+ # Attachments for this send, as publicly fetchable https URLs. Used by the MMS
21
+ # channel and ignored by every other one.
22
+ #
23
+ # Supplying these replaces the media on the template's mms body rather than adding
24
+ # to it, so a template can hold a default creative while a caller still sends
25
+ # something recipient-specific.
26
+ #
27
+ # Their presence is also what makes a message eligible for MMS on an auto-detect
28
+ # send: a message with nothing attached is delivered as SMS, because an MMS with
29
+ # no media is a more expensive text message.
30
+ #
31
+ # The recipient's carrier fetches each URL after the send is accepted, so it must
32
+ # stay publicly reachable — a link that expires, or one behind auth, arrives as a
33
+ # failed message.
34
+ sig { returns(T.nilable(T::Array[String])) }
35
+ attr_accessor :media_urls
36
+
20
37
  # Sandbox flag - when true, the operation is simulated without side effects Useful
21
38
  # for testing integrations without actual execution
22
39
  sig { returns(T.nilable(T::Boolean)) }
@@ -25,6 +42,25 @@ module Sentdm
25
42
  sig { params(sandbox: T::Boolean).void }
26
43
  attr_writer :sandbox
27
44
 
45
+ # Optional future send time as an ISO-8601 timestamp with an explicit UTC offset,
46
+ # e.g. 2026-10-01T09:00:00+02:00 or 2026-10-01T07:00:00Z. A value without an
47
+ # offset is rejected (400) rather than read in the server's zone. The offset only
48
+ # fixes the instant: it is stored and echoed in UTC as scheduled_at. Omit to send
49
+ # now. Must be at least one minute ahead and at most 30 days ahead. Accepted
50
+ # messages report SCHEDULED and are released for delivery at this time. Quiet
51
+ # hours, balance and template approval are evaluated at release, not at
52
+ # acceptance: a message whose time falls inside a recipient's protected
53
+ # quiet-hours window is moved to the next allowed time and a second
54
+ # message.scheduled webhook reports the new scheduled_at.
55
+ sig { returns(T.nilable(Time)) }
56
+ attr_accessor :scheduled_at
57
+
58
+ # Subject line for this send, overriding the template's. MMS only; ignored on
59
+ # every other channel. Most handsets render it above the body, some ignore it
60
+ # entirely.
61
+ sig { returns(T.nilable(String)) }
62
+ attr_accessor :subject
63
+
28
64
  # SDK-style template reference: resolve by ID or by name, with optional
29
65
  # parameters.
30
66
  sig { returns(T.nilable(Sentdm::MessageSendParams::Template)) }
@@ -63,7 +99,10 @@ module Sentdm
63
99
  sig do
64
100
  params(
65
101
  channel: T.nilable(T::Array[String]),
102
+ media_urls: T.nilable(T::Array[String]),
66
103
  sandbox: T::Boolean,
104
+ scheduled_at: T.nilable(Time),
105
+ subject: T.nilable(String),
67
106
  template: T.nilable(Sentdm::MessageSendParams::Template::OrHash),
68
107
  text: T.nilable(String),
69
108
  to: T::Array[String],
@@ -77,9 +116,39 @@ module Sentdm
77
116
  # separate message per recipient. "sent" = auto-detect. Defaults to ["sent"]
78
117
  # (auto-detect) if omitted.
79
118
  channel: nil,
119
+ # Attachments for this send, as publicly fetchable https URLs. Used by the MMS
120
+ # channel and ignored by every other one.
121
+ #
122
+ # Supplying these replaces the media on the template's mms body rather than adding
123
+ # to it, so a template can hold a default creative while a caller still sends
124
+ # something recipient-specific.
125
+ #
126
+ # Their presence is also what makes a message eligible for MMS on an auto-detect
127
+ # send: a message with nothing attached is delivered as SMS, because an MMS with
128
+ # no media is a more expensive text message.
129
+ #
130
+ # The recipient's carrier fetches each URL after the send is accepted, so it must
131
+ # stay publicly reachable — a link that expires, or one behind auth, arrives as a
132
+ # failed message.
133
+ media_urls: nil,
80
134
  # Sandbox flag - when true, the operation is simulated without side effects Useful
81
135
  # for testing integrations without actual execution
82
136
  sandbox: nil,
137
+ # Optional future send time as an ISO-8601 timestamp with an explicit UTC offset,
138
+ # e.g. 2026-10-01T09:00:00+02:00 or 2026-10-01T07:00:00Z. A value without an
139
+ # offset is rejected (400) rather than read in the server's zone. The offset only
140
+ # fixes the instant: it is stored and echoed in UTC as scheduled_at. Omit to send
141
+ # now. Must be at least one minute ahead and at most 30 days ahead. Accepted
142
+ # messages report SCHEDULED and are released for delivery at this time. Quiet
143
+ # hours, balance and template approval are evaluated at release, not at
144
+ # acceptance: a message whose time falls inside a recipient's protected
145
+ # quiet-hours window is moved to the next allowed time and a second
146
+ # message.scheduled webhook reports the new scheduled_at.
147
+ scheduled_at: nil,
148
+ # Subject line for this send, overriding the template's. MMS only; ignored on
149
+ # every other channel. Most handsets render it above the body, some ignore it
150
+ # entirely.
151
+ subject: nil,
83
152
  # SDK-style template reference: resolve by ID or by name, with optional
84
153
  # parameters.
85
154
  template: nil,
@@ -97,7 +166,10 @@ module Sentdm
97
166
  override.returns(
98
167
  {
99
168
  channel: T.nilable(T::Array[String]),
169
+ media_urls: T.nilable(T::Array[String]),
100
170
  sandbox: T::Boolean,
171
+ scheduled_at: T.nilable(Time),
172
+ subject: T.nilable(String),
101
173
  template: T.nilable(Sentdm::MessageSendParams::Template),
102
174
  text: T.nilable(String),
103
175
  to: T::Array[String],
@@ -127,7 +199,20 @@ module Sentdm
127
199
  sig { returns(T.nilable(String)) }
128
200
  attr_accessor :name
129
201
 
130
- # Template variable parameters for personalization
202
+ # Template variable parameters for personalization, keyed by variable name.
203
+ #
204
+ # Every variable the template declares is required; GET /v3/templates/{id} lists
205
+ # them. Supplying a key the template does not declare is ignored.
206
+ #
207
+ # Media headers. A template whose header is an image (designed in WhatsApp Manager
208
+ # and imported into Sent) declares a reserved header_image key. Its value is a
209
+ # publicly reachable https URL that Meta fetches at send time — Sent does not host
210
+ # the asset, and the sample approved with the template is not reused. The key is
211
+ # derived from the header's media type, so header_video and header_document follow
212
+ # the same shape when those formats ship.
213
+ #
214
+ # "parameters": { "header_image": "https://cdn.example.com/banner.jpg", "name":
215
+ # "John Doe" }
131
216
  sig { returns(T.nilable(T::Hash[Symbol, String])) }
132
217
  attr_accessor :parameters
133
218
 
@@ -145,7 +230,20 @@ module Sentdm
145
230
  id: nil,
146
231
  # Template name (mutually exclusive with id)
147
232
  name: nil,
148
- # Template variable parameters for personalization
233
+ # Template variable parameters for personalization, keyed by variable name.
234
+ #
235
+ # Every variable the template declares is required; GET /v3/templates/{id} lists
236
+ # them. Supplying a key the template does not declare is ignored.
237
+ #
238
+ # Media headers. A template whose header is an image (designed in WhatsApp Manager
239
+ # and imported into Sent) declares a reserved header_image key. Its value is a
240
+ # publicly reachable https URL that Meta fetches at send time — Sent does not host
241
+ # the asset, and the sample approved with the template is not reused. The key is
242
+ # derived from the header's media type, so header_video and header_document follow
243
+ # the same shape when those formats ship.
244
+ #
245
+ # "parameters": { "header_image": "https://cdn.example.com/banner.jpg", "name":
246
+ # "John Doe" }
149
247
  parameters: nil
150
248
  )
151
249
  end
@@ -17,7 +17,9 @@ module Sentdm
17
17
  # its result; this is what a caller sees, and the mapping between them is a
18
18
  # decision the endpoint makes.
19
19
  #
20
- # The wire is unchanged by the move: same names, same values.
20
+ # The shape of an immediate send: it never has a scheduled_at key. A send that
21
+ # carried scheduled_at is a ScheduledSendMessageResponse, and the endpoint decides
22
+ # which of the two to answer with. From always returns this type.
21
23
  sig { returns(T.nilable(Sentdm::Models::MessageSendResponse::Data)) }
22
24
  attr_reader :data
23
25
 
@@ -68,7 +70,9 @@ module Sentdm
68
70
  # its result; this is what a caller sees, and the mapping between them is a
69
71
  # decision the endpoint makes.
70
72
  #
71
- # The wire is unchanged by the move: same names, same values.
73
+ # The shape of an immediate send: it never has a scheduled_at key. A send that
74
+ # carried scheduled_at is a ScheduledSendMessageResponse, and the endpoint decides
75
+ # which of the two to answer with. From always returns this type.
72
76
  data: nil,
73
77
  # Error information
74
78
  error: nil,
@@ -120,7 +124,9 @@ module Sentdm
120
124
  end
121
125
  attr_writer :recipients
122
126
 
123
- # Overall status — QUEUED once the batch is accepted for delivery.
127
+ # QUEUED: the batch is accepted. A request that carried scheduled_at is QUEUED
128
+ # here too; each message moves to SCHEDULED once it is held, as GET
129
+ # /v3/messages/{id} and the message.scheduled webhook report.
124
130
  sig { returns(T.nilable(String)) }
125
131
  attr_reader :status
126
132
 
@@ -148,7 +154,9 @@ module Sentdm
148
154
  # its result; this is what a caller sees, and the mapping between them is a
149
155
  # decision the endpoint makes.
150
156
  #
151
- # The wire is unchanged by the move: same names, same values.
157
+ # The shape of an immediate send: it never has a scheduled_at key. A send that
158
+ # carried scheduled_at is a ScheduledSendMessageResponse, and the endpoint decides
159
+ # which of the two to answer with. From always returns this type.
152
160
  sig do
153
161
  params(
154
162
  recipients:
@@ -162,7 +170,9 @@ module Sentdm
162
170
  end
163
171
  def self.new(
164
172
  recipients: nil,
165
- # Overall status — QUEUED once the batch is accepted for delivery.
173
+ # QUEUED: the batch is accepted. A request that carried scheduled_at is QUEUED
174
+ # here too; each message moves to SCHEDULED once it is held, as GET
175
+ # /v3/messages/{id} and the message.scheduled webhook report.
166
176
  status: nil,
167
177
  template_id: nil,
168
178
  template_name: nil