sentdm 0.31.0 → 0.33.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 (79) 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.rb +78 -0
  6. data/lib/sentdm/models/channel_event_payload.rb +265 -0
  7. data/lib/sentdm/models/contact_event.rb +77 -0
  8. data/lib/sentdm/models/contact_event_payload.rb +172 -0
  9. data/lib/sentdm/models/conversation_messages_list.rb +125 -4
  10. data/lib/sentdm/models/inbound_message_event.rb +16 -8
  11. data/lib/sentdm/models/message_event.rb +16 -8
  12. data/lib/sentdm/models/message_event_payload.rb +30 -1
  13. data/lib/sentdm/models/message_retrieve_activities_response.rb +20 -5
  14. data/lib/sentdm/models/message_retrieve_status_response.rb +133 -6
  15. data/lib/sentdm/models/message_send_params.rb +67 -3
  16. data/lib/sentdm/models/message_send_response.rb +13 -4
  17. data/lib/sentdm/models/template.rb +36 -5
  18. data/lib/sentdm/models/template_body.rb +102 -12
  19. data/lib/sentdm/models/template_body_content.rb +35 -3
  20. data/lib/sentdm/models/template_button.rb +8 -2
  21. data/lib/sentdm/models/template_button_props.rb +12 -1
  22. data/lib/sentdm/models/template_definition.rb +14 -2
  23. data/lib/sentdm/models/template_event.rb +16 -8
  24. data/lib/sentdm/models/template_event_payload.rb +31 -4
  25. data/lib/sentdm/models/template_header.rb +91 -3
  26. data/lib/sentdm/models/template_variable.rb +35 -4
  27. data/lib/sentdm/models/webhook_list_events_response.rb +332 -9
  28. data/lib/sentdm/models.rb +8 -0
  29. data/lib/sentdm/resources/messages.rb +33 -5
  30. data/lib/sentdm/resources/templates.rb +28 -2
  31. data/lib/sentdm/version.rb +1 -1
  32. data/lib/sentdm.rb +5 -1
  33. data/rbi/sentdm/client.rbi +6 -0
  34. data/rbi/sentdm/models/channel_event.rbi +128 -0
  35. data/rbi/sentdm/models/channel_event_payload.rbi +440 -0
  36. data/rbi/sentdm/models/contact_event.rbi +126 -0
  37. data/rbi/sentdm/models/contact_event_payload.rbi +254 -0
  38. data/rbi/sentdm/models/conversation_messages_list.rbi +202 -7
  39. data/rbi/sentdm/models/inbound_message_event.rbi +18 -10
  40. data/rbi/sentdm/models/message_event.rbi +18 -10
  41. data/rbi/sentdm/models/message_event_payload.rbi +34 -0
  42. data/rbi/sentdm/models/message_retrieve_activities_response.rbi +23 -5
  43. data/rbi/sentdm/models/message_retrieve_status_response.rbi +214 -9
  44. data/rbi/sentdm/models/message_send_params.rbi +100 -2
  45. data/rbi/sentdm/models/message_send_response.rbi +15 -5
  46. data/rbi/sentdm/models/template.rbi +58 -4
  47. data/rbi/sentdm/models/template_body.rbi +155 -13
  48. data/rbi/sentdm/models/template_body_content.rbi +59 -1
  49. data/rbi/sentdm/models/template_button.rbi +14 -2
  50. data/rbi/sentdm/models/template_button_props.rbi +22 -0
  51. data/rbi/sentdm/models/template_definition.rbi +20 -2
  52. data/rbi/sentdm/models/template_event.rbi +18 -10
  53. data/rbi/sentdm/models/template_event_payload.rbi +51 -8
  54. data/rbi/sentdm/models/template_header.rbi +143 -2
  55. data/rbi/sentdm/models/template_variable.rbi +38 -1
  56. data/rbi/sentdm/models/webhook_list_events_response.rbi +484 -12
  57. data/rbi/sentdm/models.rbi +8 -0
  58. data/rbi/sentdm/resources/messages.rbi +58 -3
  59. data/rbi/sentdm/resources/templates.rbi +28 -2
  60. data/sig/sentdm/models/channel_event.rbs +44 -0
  61. data/sig/sentdm/models/channel_event_payload.rbs +120 -0
  62. data/sig/sentdm/models/contact_event.rbs +44 -0
  63. data/sig/sentdm/models/contact_event_payload.rbs +78 -0
  64. data/sig/sentdm/models/conversation_messages_list.rbs +48 -3
  65. data/sig/sentdm/models/inbound_message_event.rbs +5 -0
  66. data/sig/sentdm/models/message_event.rbs +5 -0
  67. data/sig/sentdm/models/message_event_payload.rbs +15 -0
  68. data/sig/sentdm/models/message_retrieve_activities_response.rbs +5 -0
  69. data/sig/sentdm/models/message_retrieve_status_response.rbs +48 -3
  70. data/sig/sentdm/models/message_send_params.rbs +15 -0
  71. data/sig/sentdm/models/template.rbs +5 -0
  72. data/sig/sentdm/models/template_body.rbs +44 -0
  73. data/sig/sentdm/models/template_event.rbs +5 -0
  74. data/sig/sentdm/models/template_event_payload.rbs +9 -6
  75. data/sig/sentdm/models/template_header.rbs +44 -0
  76. data/sig/sentdm/models/webhook_list_events_response.rbs +147 -0
  77. data/sig/sentdm/models.rbs +8 -0
  78. data/sig/sentdm/resources/messages.rbs +3 -0
  79. metadata +14 -2
@@ -25,6 +25,12 @@ module Sentdm
25
25
  sig { returns(T.nilable(String)) }
26
26
  attr_accessor :agent_id
27
27
 
28
+ # The rendered message body, as plain text. Sent as null when we aren't asserting
29
+ # a body for this event. The field is always present, so read it and check for
30
+ # null rather than checking whether the key exists. Truncated to 3072 characters.
31
+ sig { returns(T.nilable(String)) }
32
+ attr_accessor :body
33
+
28
34
  # The channel the message went out on, for example sms or whatsapp. A message that
29
35
  # falls back to another channel reports the channel actually used.
30
36
  sig { returns(T.nilable(String)) }
@@ -48,6 +54,17 @@ module Sentdm
48
54
  sig { params(outbound_number: String).void }
49
55
  attr_writer :outbound_number
50
56
 
57
+ # message.scheduled only: why the message is held, either because you scheduled it
58
+ # or because the recipient is inside a protected quiet-hours window. Omitted on
59
+ # every other event.
60
+ sig { returns(T.nilable(String)) }
61
+ attr_accessor :schedule_reason
62
+
63
+ # message.scheduled only: when the held message will be released for delivery, in
64
+ # UTC (yyyy-MM-ddTHH:mm:ssZ). Omitted on every other event.
65
+ sig { returns(T.nilable(String)) }
66
+ attr_accessor :scheduled_at
67
+
51
68
  # The template the message was sent from, when it was sent from one.
52
69
  sig { returns(T.nilable(String)) }
53
70
  attr_accessor :template_id
@@ -72,9 +89,12 @@ module Sentdm
72
89
  message_status: String,
73
90
  account_id: String,
74
91
  agent_id: T.nilable(String),
92
+ body: T.nilable(String),
75
93
  channel: String,
76
94
  message_id: String,
77
95
  outbound_number: String,
96
+ schedule_reason: T.nilable(String),
97
+ scheduled_at: T.nilable(String),
78
98
  template_id: T.nilable(String),
79
99
  template_name: T.nilable(String),
80
100
  updated_at: String
@@ -89,6 +109,10 @@ module Sentdm
89
109
  account_id: nil,
90
110
  # The agent attributed to the send, when the send was attributed to one.
91
111
  agent_id: nil,
112
+ # The rendered message body, as plain text. Sent as null when we aren't asserting
113
+ # a body for this event. The field is always present, so read it and check for
114
+ # null rather than checking whether the key exists. Truncated to 3072 characters.
115
+ body: nil,
92
116
  # The channel the message went out on, for example sms or whatsapp. A message that
93
117
  # falls back to another channel reports the channel actually used.
94
118
  channel: nil,
@@ -97,6 +121,13 @@ module Sentdm
97
121
  message_id: nil,
98
122
  # The recipient's number in E.164 format.
99
123
  outbound_number: nil,
124
+ # message.scheduled only: why the message is held, either because you scheduled it
125
+ # or because the recipient is inside a protected quiet-hours window. Omitted on
126
+ # every other event.
127
+ schedule_reason: nil,
128
+ # message.scheduled only: when the held message will be released for delivery, in
129
+ # UTC (yyyy-MM-ddTHH:mm:ssZ). Omitted on every other event.
130
+ scheduled_at: nil,
100
131
  # The template the message was sent from, when it was sent from one.
101
132
  template_id: nil,
102
133
  # Name of the template the message was sent from. Omitted when the message wasn't
@@ -113,9 +144,12 @@ module Sentdm
113
144
  message_status: String,
114
145
  account_id: String,
115
146
  agent_id: T.nilable(String),
147
+ body: T.nilable(String),
116
148
  channel: String,
117
149
  message_id: String,
118
150
  outbound_number: String,
151
+ schedule_reason: T.nilable(String),
152
+ scheduled_at: T.nilable(String),
119
153
  template_id: T.nilable(String),
120
154
  template_name: T.nilable(String),
121
155
  updated_at: String
@@ -203,8 +203,15 @@ module Sentdm
203
203
  sig { returns(T.nilable(String)) }
204
204
  attr_accessor :price
205
205
 
206
- # Activity status. Outbound: QUEUED, PROCESSED, ROUTED, SENT, DELIVERED, READ,
207
- # FAILED. Inbound (from contact): RECEIVED (terminal).
206
+ # SCHEDULED activities only: when the held message will be released for delivery,
207
+ # in UTC. Same wire name as on the send response, the message and the webhook.
208
+ # Omitted on every other activity. A message that quiet hours moved at release has
209
+ # two SCHEDULED entries, each carrying the instant as it stood at that moment.
210
+ sig { returns(T.nilable(Time)) }
211
+ attr_accessor :scheduled_at
212
+
213
+ # Activity status. Outbound: QUEUED, PROCESSED, ROUTED, SCHEDULED, SENT,
214
+ # DELIVERED, READ, FAILED. Inbound (from contact): RECEIVED (terminal).
208
215
  sig { returns(T.nilable(String)) }
209
216
  attr_reader :status
210
217
 
@@ -218,13 +225,18 @@ module Sentdm
218
225
  sig { params(timestamp: Time).void }
219
226
  attr_writer :timestamp
220
227
 
221
- # A single message activity event for v3 API
228
+ # A single message activity event for v3 API.
229
+ #
230
+ # The activity list mixes statuses, so unlike a message it is one shape rather
231
+ # than two: a SCHEDULED entry carries scheduled_at, and every other entry has no
232
+ # such key.
222
233
  sig do
223
234
  params(
224
235
  active_contact_price: T.nilable(String),
225
236
  description: String,
226
237
  from: T.nilable(String),
227
238
  price: T.nilable(String),
239
+ scheduled_at: T.nilable(Time),
228
240
  status: String,
229
241
  timestamp: Time
230
242
  ).returns(T.attached_class)
@@ -242,8 +254,13 @@ module Sentdm
242
254
  # Channel cost for this activity (e.g., SMS/WhatsApp provider cost), formatted to
243
255
  # 4 decimal places.
244
256
  price: nil,
245
- # Activity status. Outbound: QUEUED, PROCESSED, ROUTED, SENT, DELIVERED, READ,
246
- # FAILED. Inbound (from contact): RECEIVED (terminal).
257
+ # SCHEDULED activities only: when the held message will be released for delivery,
258
+ # in UTC. Same wire name as on the send response, the message and the webhook.
259
+ # Omitted on every other activity. A message that quiet hours moved at release has
260
+ # two SCHEDULED entries, each carrying the instant as it stood at that moment.
261
+ scheduled_at: nil,
262
+ # Activity status. Outbound: QUEUED, PROCESSED, ROUTED, SCHEDULED, SENT,
263
+ # DELIVERED, READ, FAILED. Inbound (from contact): RECEIVED (terminal).
247
264
  status: nil,
248
265
  # When this activity occurred
249
266
  timestamp: nil
@@ -257,6 +274,7 @@ module Sentdm
257
274
  description: String,
258
275
  from: T.nilable(String),
259
276
  price: T.nilable(String),
277
+ scheduled_at: T.nilable(Time),
260
278
  status: String,
261
279
  timestamp: Time
262
280
  }
@@ -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(
@@ -202,7 +219,12 @@ module Sentdm
202
219
  sig { returns(T.nilable(String)) }
203
220
  attr_accessor :template_name
204
221
 
205
- # Message response for v3 API — same shape as v2 with snake_case JSON conventions
222
+ # Message response for v3 API — same shape as v2 with snake_case JSON conventions.
223
+ #
224
+ # The shape of a message that was sent immediately: it never has a scheduled_at
225
+ # key. A message that is or was held for a later instant is a
226
+ # ScheduledMessageResponse, and the endpoint decides which of the two to answer
227
+ # with. From always returns this type.
206
228
  sig do
207
229
  params(
208
230
  id: String,
@@ -242,7 +264,14 @@ module Sentdm
242
264
  direction: nil,
243
265
  events: nil,
244
266
  # Structured message body format for database storage. Preserves channel-specific
245
- # components (header, body, footer, buttons).
267
+ # components (header, header media, body, footer, buttons, MMS subject and media).
268
+ #
269
+ # Persisted as the messageBody jsonb column on Messages. Every write path goes
270
+ # through MessageUtils.MessageBodyJsonOptions, which writes nulls, so the envelope
271
+ # shape is stable regardless of channel or status. Anything that rebuilds this
272
+ # object field by field — the four IMessageBodyStrategy implementations and
273
+ # MessageUtils.BuildSegmentBody — has to carry every member, or that member is
274
+ # silently dropped on whichever path forgot it.
246
275
  message_body: nil,
247
276
  phone: nil,
248
277
  phone_international: nil,
@@ -363,8 +392,57 @@ module Sentdm
363
392
  sig { returns(T.nilable(String)) }
364
393
  attr_accessor :header
365
394
 
395
+ # The media asset that rode a message's header, recorded as sent.
396
+ sig do
397
+ returns(
398
+ T.nilable(
399
+ Sentdm::Models::MessageRetrieveStatusResponse::Data::MessageBody::HeaderMedia
400
+ )
401
+ )
402
+ end
403
+ attr_reader :header_media
404
+
405
+ sig do
406
+ params(
407
+ header_media:
408
+ T.nilable(
409
+ Sentdm::Models::MessageRetrieveStatusResponse::Data::MessageBody::HeaderMedia::OrHash
410
+ )
411
+ ).void
412
+ end
413
+ attr_writer :header_media
414
+
415
+ # MMS attachments, as the publicly fetchable URLs handed to the carrier. Null on
416
+ # every other channel.
417
+ #
418
+ # Persisted rather than derived because a resend and a curfew release rebuild the
419
+ # send from the stored row — MessageReplayCommandBuilder reads templateId and
420
+ # templateVariables and nothing else — so media that lives only on the original
421
+ # request would silently turn a replayed MMS into a text message.
422
+ sig do
423
+ returns(
424
+ T.nilable(
425
+ T::Array[
426
+ Sentdm::Models::MessageRetrieveStatusResponse::Data::MessageBody::Media
427
+ ]
428
+ )
429
+ )
430
+ end
431
+ attr_accessor :media
432
+
433
+ # MMS subject line. Null on every other channel.
434
+ sig { returns(T.nilable(String)) }
435
+ attr_accessor :subject
436
+
366
437
  # Structured message body format for database storage. Preserves channel-specific
367
- # components (header, body, footer, buttons).
438
+ # components (header, header media, body, footer, buttons, MMS subject and media).
439
+ #
440
+ # Persisted as the messageBody jsonb column on Messages. Every write path goes
441
+ # through MessageUtils.MessageBodyJsonOptions, which writes nulls, so the envelope
442
+ # shape is stable regardless of channel or status. Anything that rebuilds this
443
+ # object field by field — the four IMessageBodyStrategy implementations and
444
+ # MessageUtils.BuildSegmentBody — has to carry every member, or that member is
445
+ # silently dropped on whichever path forgot it.
368
446
  sig do
369
447
  params(
370
448
  buttons:
@@ -375,10 +453,38 @@ module Sentdm
375
453
  ),
376
454
  content: String,
377
455
  footer: T.nilable(String),
378
- header: T.nilable(String)
456
+ header: T.nilable(String),
457
+ header_media:
458
+ T.nilable(
459
+ Sentdm::Models::MessageRetrieveStatusResponse::Data::MessageBody::HeaderMedia::OrHash
460
+ ),
461
+ media:
462
+ T.nilable(
463
+ T::Array[
464
+ Sentdm::Models::MessageRetrieveStatusResponse::Data::MessageBody::Media::OrHash
465
+ ]
466
+ ),
467
+ subject: T.nilable(String)
379
468
  ).returns(T.attached_class)
380
469
  end
381
- def self.new(buttons: nil, content: nil, footer: nil, header: nil)
470
+ def self.new(
471
+ buttons: nil,
472
+ content: nil,
473
+ footer: nil,
474
+ header: nil,
475
+ # The media asset that rode a message's header, recorded as sent.
476
+ header_media: nil,
477
+ # MMS attachments, as the publicly fetchable URLs handed to the carrier. Null on
478
+ # every other channel.
479
+ #
480
+ # Persisted rather than derived because a resend and a curfew release rebuild the
481
+ # send from the stored row — MessageReplayCommandBuilder reads templateId and
482
+ # templateVariables and nothing else — so media that lives only on the original
483
+ # request would silently turn a replayed MMS into a text message.
484
+ media: nil,
485
+ # MMS subject line. Null on every other channel.
486
+ subject: nil
487
+ )
382
488
  end
383
489
 
384
490
  sig do
@@ -392,7 +498,18 @@ module Sentdm
392
498
  ),
393
499
  content: String,
394
500
  footer: T.nilable(String),
395
- header: T.nilable(String)
501
+ header: T.nilable(String),
502
+ header_media:
503
+ T.nilable(
504
+ Sentdm::Models::MessageRetrieveStatusResponse::Data::MessageBody::HeaderMedia
505
+ ),
506
+ media:
507
+ T.nilable(
508
+ T::Array[
509
+ Sentdm::Models::MessageRetrieveStatusResponse::Data::MessageBody::Media
510
+ ]
511
+ ),
512
+ subject: T.nilable(String)
396
513
  }
397
514
  )
398
515
  end
@@ -450,6 +567,94 @@ module Sentdm
450
567
  def to_hash
451
568
  end
452
569
  end
570
+
571
+ class HeaderMedia < Sentdm::Internal::Type::BaseModel
572
+ OrHash =
573
+ T.type_alias do
574
+ T.any(
575
+ Sentdm::Models::MessageRetrieveStatusResponse::Data::MessageBody::HeaderMedia,
576
+ Sentdm::Internal::AnyHash
577
+ )
578
+ end
579
+
580
+ # "image", "video" or "document" — taken from the header's media variable.
581
+ sig { returns(T.nilable(String)) }
582
+ attr_reader :type
583
+
584
+ sig { params(type: String).void }
585
+ attr_writer :type
586
+
587
+ # The https URL the caller supplied for this send. Never the template's stored
588
+ # props.sample, which is Meta's expiring header_handle rather than what was
589
+ # delivered.
590
+ sig { returns(T.nilable(String)) }
591
+ attr_reader :url
592
+
593
+ sig { params(url: String).void }
594
+ attr_writer :url
595
+
596
+ # The media asset that rode a message's header, recorded as sent.
597
+ sig { params(type: String, url: String).returns(T.attached_class) }
598
+ def self.new(
599
+ # "image", "video" or "document" — taken from the header's media variable.
600
+ type: nil,
601
+ # The https URL the caller supplied for this send. Never the template's stored
602
+ # props.sample, which is Meta's expiring header_handle rather than what was
603
+ # delivered.
604
+ url: nil
605
+ )
606
+ end
607
+
608
+ sig { override.returns({ type: String, url: String }) }
609
+ def to_hash
610
+ end
611
+ end
612
+
613
+ class Media < Sentdm::Internal::Type::BaseModel
614
+ OrHash =
615
+ T.type_alias do
616
+ T.any(
617
+ Sentdm::Models::MessageRetrieveStatusResponse::Data::MessageBody::Media,
618
+ Sentdm::Internal::AnyHash
619
+ )
620
+ end
621
+
622
+ # One of Constants.MmsMediaTypes when known. Advisory — the carrier reads the
623
+ # fetched object's Content-Type, not this.
624
+ sig { returns(T.nilable(String)) }
625
+ attr_accessor :media_type
626
+
627
+ sig { returns(T.nilable(String)) }
628
+ attr_reader :url
629
+
630
+ sig { params(url: String).void }
631
+ attr_writer :url
632
+
633
+ # One attachment on a message: a customer-supplied public URL handed to the
634
+ # carrier as-is.
635
+ #
636
+ # A URL and nothing else. sent.dm never takes custody of MMS media — the customer hosts it and we
637
+ # pass the link through at send time — so there is no storage key, size or expiry to record. If we ever
638
+ # do host attachments, that belongs with the change that introduces the hosting, not here.
639
+ sig do
640
+ params(media_type: T.nilable(String), url: String).returns(
641
+ T.attached_class
642
+ )
643
+ end
644
+ def self.new(
645
+ # One of Constants.MmsMediaTypes when known. Advisory — the carrier reads the
646
+ # fetched object's Content-Type, not this.
647
+ media_type: nil,
648
+ url: nil
649
+ )
650
+ end
651
+
652
+ sig do
653
+ override.returns({ media_type: T.nilable(String), url: String })
654
+ end
655
+ def to_hash
656
+ end
657
+ end
453
658
  end
454
659
  end
455
660
  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