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
@@ -18,6 +18,21 @@ module Sentdm
18
18
  sig { params(id: String).void }
19
19
  attr_writer :id
20
20
 
21
+ # Which consent keyword this template answers, when it is one of Sent's
22
+ # auto-replies: OPT_IN, OPT_OUT, HELP, or OTHER for a customer-defined keyword.
23
+ # Null for an ordinary template, and omitted from the response, so its presence is
24
+ # the answer to "is this an auto-reply".
25
+ #
26
+ # Deliberately not required, unlike CustomerId, even though the same "no single
27
+ # mapper" argument applies: NJsonSchema publishes a C# required member in the
28
+ # schema's required array, so the contract would have advertised a field this
29
+ # response omits for every ordinary template, and a generated client could refuse
30
+ # the common case. A compile-time guard is not worth a wrong published contract.
31
+ # Every mapping site sets it explicitly, and TemplateResponseSchemaTests pins the
32
+ # field as optional so it cannot be reintroduced.
33
+ sig { returns(T.nilable(String)) }
34
+ attr_accessor :auto_reply_action
35
+
21
36
  # Template category: MARKETING, UTILITY, AUTHENTICATION
22
37
  sig { returns(T.nilable(String)) }
23
38
  attr_reader :category
@@ -25,7 +40,18 @@ module Sentdm
25
40
  sig { params(category: String).void }
26
41
  attr_writer :category
27
42
 
28
- # Supported channels: sms, whatsapp
43
+ # The channels this template's definition can render on, in canonical order: sms,
44
+ # whatsapp, rcs.
45
+ #
46
+ # Derived from the definition's body, mirroring each channel's send-time fallback
47
+ # chain, so a channel is listed only when a real body would be produced for it:
48
+ # SMS reads sms ?? multiChannel, WhatsApp reads whatsapp ?? multiChannel, and RCS
49
+ # reads rcs ?? multiChannel ?? sms. A multiChannel body therefore reports all
50
+ # three, and the extra SMS fallback on RCS is why an sms/whatsapp pair reports RCS
51
+ # too.
52
+ #
53
+ # This says what the content can render on, not what may be sent: sending also
54
+ # needs the template approved for that channel.
29
55
  sig { returns(T.nilable(T::Array[String])) }
30
56
  attr_accessor :channels
31
57
 
@@ -57,7 +83,8 @@ module Sentdm
57
83
  sig { params(name: String).void }
58
84
  attr_writer :name
59
85
 
60
- # Template status: APPROVED, PENDING, REJECTED
86
+ # Template status: DRAFT, PENDING, APPROVED, REJECTED. A template created with
87
+ # submit_for_review: false starts as DRAFT and stays there until it is submitted.
61
88
  sig { returns(T.nilable(String)) }
62
89
  attr_reader :status
63
90
 
@@ -77,6 +104,7 @@ module Sentdm
77
104
  params(
78
105
  customer_id: String,
79
106
  id: String,
107
+ auto_reply_action: T.nilable(String),
80
108
  category: String,
81
109
  channels: T.nilable(T::Array[String]),
82
110
  created_at: Time,
@@ -94,9 +122,33 @@ module Sentdm
94
122
  customer_id:,
95
123
  # Unique template identifier
96
124
  id: nil,
125
+ # Which consent keyword this template answers, when it is one of Sent's
126
+ # auto-replies: OPT_IN, OPT_OUT, HELP, or OTHER for a customer-defined keyword.
127
+ # Null for an ordinary template, and omitted from the response, so its presence is
128
+ # the answer to "is this an auto-reply".
129
+ #
130
+ # Deliberately not required, unlike CustomerId, even though the same "no single
131
+ # mapper" argument applies: NJsonSchema publishes a C# required member in the
132
+ # schema's required array, so the contract would have advertised a field this
133
+ # response omits for every ordinary template, and a generated client could refuse
134
+ # the common case. A compile-time guard is not worth a wrong published contract.
135
+ # Every mapping site sets it explicitly, and TemplateResponseSchemaTests pins the
136
+ # field as optional so it cannot be reintroduced.
137
+ auto_reply_action: nil,
97
138
  # Template category: MARKETING, UTILITY, AUTHENTICATION
98
139
  category: nil,
99
- # Supported channels: sms, whatsapp
140
+ # The channels this template's definition can render on, in canonical order: sms,
141
+ # whatsapp, rcs.
142
+ #
143
+ # Derived from the definition's body, mirroring each channel's send-time fallback
144
+ # chain, so a channel is listed only when a real body would be produced for it:
145
+ # SMS reads sms ?? multiChannel, WhatsApp reads whatsapp ?? multiChannel, and RCS
146
+ # reads rcs ?? multiChannel ?? sms. A multiChannel body therefore reports all
147
+ # three, and the extra SMS fallback on RCS is why an sms/whatsapp pair reports RCS
148
+ # too.
149
+ #
150
+ # This says what the content can render on, not what may be sent: sending also
151
+ # needs the template approved for that channel.
100
152
  channels: nil,
101
153
  # When the template was created
102
154
  created_at: nil,
@@ -106,7 +158,8 @@ module Sentdm
106
158
  language: nil,
107
159
  # Template display name
108
160
  name: nil,
109
- # Template status: APPROVED, PENDING, REJECTED
161
+ # Template status: DRAFT, PENDING, APPROVED, REJECTED. A template created with
162
+ # submit_for_review: false starts as DRAFT and stays there until it is submitted.
110
163
  status: nil,
111
164
  # When the template was last updated
112
165
  updated_at: nil,
@@ -120,6 +173,7 @@ module Sentdm
120
173
  {
121
174
  customer_id: String,
122
175
  id: String,
176
+ auto_reply_action: T.nilable(String),
123
177
  category: String,
124
178
  channels: T.nilable(T::Array[String]),
125
179
  created_at: Time,
@@ -6,8 +6,20 @@ module Sentdm
6
6
  OrHash =
7
7
  T.type_alias { T.any(Sentdm::TemplateBody, Sentdm::Internal::AnyHash) }
8
8
 
9
- # Content that will be used for all channels (SMS and WhatsApp) unless
10
- # channel-specific content is provided
9
+ # MMS-specific content — subject, text and attachments.
10
+ #
11
+ # Like Rcs, an override that cannot stand on its own: a template still needs a
12
+ # MultiChannel body or the Sms + Whatsapp pair to be deliverable at all. Unlike
13
+ # Rcs, it has no fallback at send time — MMS with no media is a more expensive
14
+ # SMS, so a template without this slot is deliberately not MMS-capable and never
15
+ # produces an MMS route candidate.
16
+ sig { returns(T.nilable(Sentdm::TemplateBody::Mms)) }
17
+ attr_reader :mms
18
+
19
+ sig { params(mms: T.nilable(Sentdm::TemplateBody::Mms::OrHash)).void }
20
+ attr_writer :mms
21
+
22
+ # The shared body, used for every channel. One half of the choice described above.
11
23
  sig { returns(T.nilable(Sentdm::TemplateBodyContent)) }
12
24
  attr_reader :multi_channel
13
25
 
@@ -18,22 +30,23 @@ module Sentdm
18
30
  end
19
31
  attr_writer :multi_channel
20
32
 
21
- # RCS-specific content that overrides multi-channel content for RCS messages
33
+ # RCS-specific copy that overrides the chosen strategy for RCS only. The one true
34
+ # override: optional on top of either strategy, but it cannot be the only body
35
+ # present. Its length cap is the higher one described on Template.
22
36
  sig { returns(T.nilable(Sentdm::TemplateBodyContent)) }
23
37
  attr_reader :rcs
24
38
 
25
39
  sig { params(rcs: T.nilable(Sentdm::TemplateBodyContent::OrHash)).void }
26
40
  attr_writer :rcs
27
41
 
28
- # SMS-specific content that overrides multi-channel content for SMS messages
42
+ # The SMS body. It does not override multiChannel, it replaces it.
29
43
  sig { returns(T.nilable(Sentdm::TemplateBodyContent)) }
30
44
  attr_reader :sms
31
45
 
32
46
  sig { params(sms: T.nilable(Sentdm::TemplateBodyContent::OrHash)).void }
33
47
  attr_writer :sms
34
48
 
35
- # WhatsApp-specific content that overrides multi-channel content for WhatsApp
36
- # messages
49
+ # The WhatsApp body. It does not override multiChannel, it replaces it.
37
50
  sig { returns(T.nilable(Sentdm::TemplateBodyContent)) }
38
51
  attr_reader :whatsapp
39
52
 
@@ -42,9 +55,19 @@ module Sentdm
42
55
  end
43
56
  attr_writer :whatsapp
44
57
 
45
- # Body section of a message template with channel-specific content
58
+ # Body section of a message template.
59
+ #
60
+ # A body picks one of two authoring strategies, and mixing them is refused
61
+ # (TemplateDefinitionValidator.HaveValidChannelConfiguration): a shared
62
+ # multiChannel body on its own, or an explicit sms + whatsapp pair, both present.
63
+ #
64
+ # multiChannel together with sms or whatsapp is rejected, and so is sms or
65
+ # whatsapp on its own — every template is expected to be deliverable on every
66
+ # channel. rcs is the one true override: it may accompany either strategy to vary
67
+ # the copy, but cannot stand alone.
46
68
  sig do
47
69
  params(
70
+ mms: T.nilable(Sentdm::TemplateBody::Mms::OrHash),
48
71
  multi_channel: T.nilable(Sentdm::TemplateBodyContent::OrHash),
49
72
  rcs: T.nilable(Sentdm::TemplateBodyContent::OrHash),
50
73
  sms: T.nilable(Sentdm::TemplateBodyContent::OrHash),
@@ -52,15 +75,23 @@ module Sentdm
52
75
  ).returns(T.attached_class)
53
76
  end
54
77
  def self.new(
55
- # Content that will be used for all channels (SMS and WhatsApp) unless
56
- # channel-specific content is provided
78
+ # MMS-specific content — subject, text and attachments.
79
+ #
80
+ # Like Rcs, an override that cannot stand on its own: a template still needs a
81
+ # MultiChannel body or the Sms + Whatsapp pair to be deliverable at all. Unlike
82
+ # Rcs, it has no fallback at send time — MMS with no media is a more expensive
83
+ # SMS, so a template without this slot is deliberately not MMS-capable and never
84
+ # produces an MMS route candidate.
85
+ mms: nil,
86
+ # The shared body, used for every channel. One half of the choice described above.
57
87
  multi_channel: nil,
58
- # RCS-specific content that overrides multi-channel content for RCS messages
88
+ # RCS-specific copy that overrides the chosen strategy for RCS only. The one true
89
+ # override: optional on top of either strategy, but it cannot be the only body
90
+ # present. Its length cap is the higher one described on Template.
59
91
  rcs: nil,
60
- # SMS-specific content that overrides multi-channel content for SMS messages
92
+ # The SMS body. It does not override multiChannel, it replaces it.
61
93
  sms: nil,
62
- # WhatsApp-specific content that overrides multi-channel content for WhatsApp
63
- # messages
94
+ # The WhatsApp body. It does not override multiChannel, it replaces it.
64
95
  whatsapp: nil
65
96
  )
66
97
  end
@@ -68,6 +99,7 @@ module Sentdm
68
99
  sig do
69
100
  override.returns(
70
101
  {
102
+ mms: T.nilable(Sentdm::TemplateBody::Mms),
71
103
  multi_channel: T.nilable(Sentdm::TemplateBodyContent),
72
104
  rcs: T.nilable(Sentdm::TemplateBodyContent),
73
105
  sms: T.nilable(Sentdm::TemplateBodyContent),
@@ -77,6 +109,116 @@ module Sentdm
77
109
  end
78
110
  def to_hash
79
111
  end
112
+
113
+ class Mms < Sentdm::Models::TemplateBodyContent
114
+ OrHash =
115
+ T.type_alias do
116
+ T.any(Sentdm::TemplateBody::Mms, Sentdm::Internal::AnyHash)
117
+ end
118
+
119
+ # Attachments carried by every send on this template, in order. A per-send
120
+ # media_urls on the request replaces this list rather than adding to it, so a
121
+ # template can hold a default creative and a caller can still send something
122
+ # recipient-specific.
123
+ sig { returns(T.nilable(T::Array[Sentdm::TemplateBody::Mms::Media])) }
124
+ attr_accessor :media
125
+
126
+ # MMS subject line. Optional — most handsets render it above the body, some ignore
127
+ # it entirely. Deliberately its own field rather than riding TemplateHeader: the
128
+ # header is authored once and shared across every channel, and carries Meta's
129
+ # 60-character cap plus its no-newline, no-emoji text rules, none of which
130
+ # describe an MMS subject.
131
+ sig { returns(T.nilable(String)) }
132
+ attr_accessor :subject
133
+
134
+ # MMS-specific content — subject, text and attachments.
135
+ #
136
+ # Like Rcs, an override that cannot stand on its own: a template still needs a
137
+ # MultiChannel body or the Sms + Whatsapp pair to be deliverable at all. Unlike
138
+ # Rcs, it has no fallback at send time — MMS with no media is a more expensive
139
+ # SMS, so a template without this slot is deliberately not MMS-capable and never
140
+ # produces an MMS route candidate.
141
+ sig do
142
+ params(
143
+ media:
144
+ T.nilable(T::Array[Sentdm::TemplateBody::Mms::Media::OrHash]),
145
+ subject: T.nilable(String)
146
+ ).returns(T.attached_class)
147
+ end
148
+ def self.new(
149
+ # Attachments carried by every send on this template, in order. A per-send
150
+ # media_urls on the request replaces this list rather than adding to it, so a
151
+ # template can hold a default creative and a caller can still send something
152
+ # recipient-specific.
153
+ media: nil,
154
+ # MMS subject line. Optional — most handsets render it above the body, some ignore
155
+ # it entirely. Deliberately its own field rather than riding TemplateHeader: the
156
+ # header is authored once and shared across every channel, and carries Meta's
157
+ # 60-character cap plus its no-newline, no-emoji text rules, none of which
158
+ # describe an MMS subject.
159
+ subject: nil
160
+ )
161
+ end
162
+
163
+ sig do
164
+ override.returns(
165
+ {
166
+ media: T.nilable(T::Array[Sentdm::TemplateBody::Mms::Media]),
167
+ subject: T.nilable(String)
168
+ }
169
+ )
170
+ end
171
+ def to_hash
172
+ end
173
+
174
+ class Media < Sentdm::Internal::Type::BaseModel
175
+ OrHash =
176
+ T.type_alias do
177
+ T.any(Sentdm::TemplateBody::Mms::Media, Sentdm::Internal::AnyHash)
178
+ end
179
+
180
+ # One of MmsMediaTypes. Advisory: the carrier reads the Content-Type off the
181
+ # fetched object, not this field. It exists so an authoring UI can render the
182
+ # right preview and so a reviewer can see what was intended.
183
+ sig { returns(T.nilable(String)) }
184
+ attr_accessor :media_type
185
+
186
+ # Publicly fetchable https URL. The carrier's MMSC fetches this at send time, so
187
+ # it has to stay reachable and unauthenticated for the life of the send —
188
+ # including retries and a DLQ replay — which is why a presigned URL is not a valid
189
+ # value here.
190
+ sig { returns(T.nilable(String)) }
191
+ attr_reader :url
192
+
193
+ sig { params(url: String).void }
194
+ attr_writer :url
195
+
196
+ # One attachment on an MMS template body.
197
+ sig do
198
+ params(media_type: T.nilable(String), url: String).returns(
199
+ T.attached_class
200
+ )
201
+ end
202
+ def self.new(
203
+ # One of MmsMediaTypes. Advisory: the carrier reads the Content-Type off the
204
+ # fetched object, not this field. It exists so an authoring UI can render the
205
+ # right preview and so a reviewer can see what was intended.
206
+ media_type: nil,
207
+ # Publicly fetchable https URL. The carrier's MMSC fetches this at send time, so
208
+ # it has to stay reachable and unauthenticated for the life of the send —
209
+ # including retries and a DLQ replay — which is why a presigned URL is not a valid
210
+ # value here.
211
+ url: nil
212
+ )
213
+ end
214
+
215
+ sig do
216
+ override.returns({ media_type: T.nilable(String), url: String })
217
+ end
218
+ def to_hash
219
+ end
220
+ end
221
+ end
80
222
  end
81
223
  end
82
224
  end
@@ -8,12 +8,39 @@ module Sentdm
8
8
  T.any(Sentdm::TemplateBodyContent, Sentdm::Internal::AnyHash)
9
9
  end
10
10
 
11
+ # The body copy, with variables written as {{index:variable}}.
12
+ #
13
+ # Length cap depends on which channel this body belongs to:
14
+ # TemplateContentLimits.MaxBodyLength (1024) for multiChannel, sms and whatsapp —
15
+ # Meta's BODY limit, which a multiChannel body may be delivered under — and
16
+ # TemplateContentLimits.MaxRcsBodyLength (3072) for an rcs body, which never
17
+ # reaches Meta. The maxLength advertised on this schema is the 1024 one, because
18
+ # all four channel bodies share this single schema — an rcs body between the two
19
+ # is accepted.
20
+ #
21
+ # Meta requires every variable to carry surrounding context, so a body is refused
22
+ # unless it also satisfies all of the following (enforced by
23
+ # TemplateDefinitionValidator): At least one letter before the first variable and
24
+ # after the last — trailing punctuation such as "... {{1:variable}}." does not
25
+ # count. At least (2 × variable count) + 1 words once the placeholders are
26
+ # removed. No two variables adjacent with only whitespace between them. No leading
27
+ # or trailing newline, no more than two consecutive line breaks, and no more than
28
+ # four consecutive spaces.
29
+ #
30
+ # Example: "Hello {{0:variable}}! Welcome to {{1:variable}}. We are glad to have
31
+ # you on board." — two variables, so at least five words are required, and the
32
+ # copy after the final variable contains letters.
11
33
  sig { returns(String) }
12
34
  attr_accessor :template
13
35
 
36
+ # The type of body content — send "text". It is dropped from the stored definition
37
+ # when null, so a body posted without it is saved with no type key at all and the
38
+ # template editor has nothing to render the block from.
14
39
  sig { returns(T.nilable(String)) }
15
40
  attr_accessor :type
16
41
 
42
+ # The variables referenced by the body copy, one entry per {{index:variable}}
43
+ # placeholder.
17
44
  sig { returns(T.nilable(T::Array[Sentdm::TemplateVariable])) }
18
45
  attr_accessor :variables
19
46
 
@@ -24,7 +51,38 @@ module Sentdm
24
51
  variables: T.nilable(T::Array[Sentdm::TemplateVariable::OrHash])
25
52
  ).returns(T.attached_class)
26
53
  end
27
- def self.new(template:, type: nil, variables: nil)
54
+ def self.new(
55
+ # The body copy, with variables written as {{index:variable}}.
56
+ #
57
+ # Length cap depends on which channel this body belongs to:
58
+ # TemplateContentLimits.MaxBodyLength (1024) for multiChannel, sms and whatsapp —
59
+ # Meta's BODY limit, which a multiChannel body may be delivered under — and
60
+ # TemplateContentLimits.MaxRcsBodyLength (3072) for an rcs body, which never
61
+ # reaches Meta. The maxLength advertised on this schema is the 1024 one, because
62
+ # all four channel bodies share this single schema — an rcs body between the two
63
+ # is accepted.
64
+ #
65
+ # Meta requires every variable to carry surrounding context, so a body is refused
66
+ # unless it also satisfies all of the following (enforced by
67
+ # TemplateDefinitionValidator): At least one letter before the first variable and
68
+ # after the last — trailing punctuation such as "... {{1:variable}}." does not
69
+ # count. At least (2 × variable count) + 1 words once the placeholders are
70
+ # removed. No two variables adjacent with only whitespace between them. No leading
71
+ # or trailing newline, no more than two consecutive line breaks, and no more than
72
+ # four consecutive spaces.
73
+ #
74
+ # Example: "Hello {{0:variable}}! Welcome to {{1:variable}}. We are glad to have
75
+ # you on board." — two variables, so at least five words are required, and the
76
+ # copy after the final variable contains letters.
77
+ template:,
78
+ # The type of body content — send "text". It is dropped from the stored definition
79
+ # when null, so a body posted without it is saved with no type key at all and the
80
+ # template editor has nothing to render the block from.
81
+ type: nil,
82
+ # The variables referenced by the body copy, one entry per {{index:variable}}
83
+ # placeholder.
84
+ variables: nil
85
+ )
28
86
  end
29
87
 
30
88
  sig do
@@ -19,7 +19,13 @@ module Sentdm
19
19
  sig { returns(String) }
20
20
  attr_accessor :type
21
21
 
22
- # The unique identifier of the button (1-based index)
22
+ # The button's identifier (1-based index), unique within the template.
23
+ #
24
+ # Omitting it is only safe for a template holding a single button. The field is a
25
+ # non-nullable int, so every button that leaves it out defaults to 0, and two such
26
+ # buttons are refused by the unique-id rule ("Button IDs must be unique"). Number
27
+ # them from 1 in the order they should appear — order matters on RCS, where only
28
+ # the first four buttons render.
23
29
  sig { returns(T.nilable(Integer)) }
24
30
  attr_reader :id
25
31
 
@@ -39,7 +45,13 @@ module Sentdm
39
45
  props:,
40
46
  # The type of button (e.g., QUICK_REPLY, URL, PHONE_NUMBER, VOICE_CALL, COPY_CODE)
41
47
  type:,
42
- # The unique identifier of the button (1-based index)
48
+ # The button's identifier (1-based index), unique within the template.
49
+ #
50
+ # Omitting it is only safe for a template holding a single button. The field is a
51
+ # non-nullable int, so every button that leaves it out defaults to 0, and two such
52
+ # buttons are refused by the unique-id rule ("Button IDs must be unique"). Number
53
+ # them from 1 in the order they should appear — order matters on RCS, where only
54
+ # the first four buttons render.
43
55
  id: nil
44
56
  )
45
57
  end
@@ -23,6 +23,17 @@ module Sentdm
23
23
  sig { returns(String) }
24
24
  attr_accessor :quick_reply_type
25
25
 
26
+ # The button's label. Required for every button type, and capped at
27
+ # TemplateContentLimits.MaxButtonTextLength (25) characters.
28
+ #
29
+ # Meta accepts only static text here, so a label is refused when it contains a
30
+ # {{...}} variable placeholder, a newline, an emoji, or WhatsApp formatting markup
31
+ # (\*, \_, ~) — enforced by ApplyButtonLabelContentRules in
32
+ # TemplateButtonValidator. Meta reports all four as one error: "Buttons can't have
33
+ # any variables, newlines, emojis, or formatting characters."
34
+ #
35
+ # AUTHENTICATION OTP buttons are the exception: Meta auto-localizes their label
36
+ # from the template language, and the converter drops whatever text was sent.
26
37
  sig { returns(String) }
27
38
  attr_accessor :text
28
39
 
@@ -73,6 +84,17 @@ module Sentdm
73
84
  offer_code:,
74
85
  phone_number:,
75
86
  quick_reply_type:,
87
+ # The button's label. Required for every button type, and capped at
88
+ # TemplateContentLimits.MaxButtonTextLength (25) characters.
89
+ #
90
+ # Meta accepts only static text here, so a label is refused when it contains a
91
+ # {{...}} variable placeholder, a newline, an emoji, or WhatsApp formatting markup
92
+ # (\*, \_, ~) — enforced by ApplyButtonLabelContentRules in
93
+ # TemplateButtonValidator. Meta reports all four as one error: "Buttons can't have
94
+ # any variables, newlines, emojis, or formatting characters."
95
+ #
96
+ # AUTHENTICATION OTP buttons are the exception: Meta auto-localizes their label
97
+ # from the template language, and the converter drops whatever text was sent.
76
98
  text:,
77
99
  url:,
78
100
  url_type:,
@@ -8,7 +8,16 @@ module Sentdm
8
8
  T.any(Sentdm::TemplateDefinition, Sentdm::Internal::AnyHash)
9
9
  end
10
10
 
11
- # Body section of a message template with channel-specific content
11
+ # Body section of a message template.
12
+ #
13
+ # A body picks one of two authoring strategies, and mixing them is refused
14
+ # (TemplateDefinitionValidator.HaveValidChannelConfiguration): a shared
15
+ # multiChannel body on its own, or an explicit sms + whatsapp pair, both present.
16
+ #
17
+ # multiChannel together with sms or whatsapp is rejected, and so is sms or
18
+ # whatsapp on its own — every template is expected to be deliverable on every
19
+ # channel. rcs is the one true override: it may accompany either strategy to vary
20
+ # the copy, but cannot stand alone.
12
21
  sig { returns(Sentdm::TemplateBody) }
13
22
  attr_reader :body
14
23
 
@@ -62,7 +71,16 @@ module Sentdm
62
71
  ).returns(T.attached_class)
63
72
  end
64
73
  def self.new(
65
- # Body section of a message template with channel-specific content
74
+ # Body section of a message template.
75
+ #
76
+ # A body picks one of two authoring strategies, and mixing them is refused
77
+ # (TemplateDefinitionValidator.HaveValidChannelConfiguration): a shared
78
+ # multiChannel body on its own, or an explicit sms + whatsapp pair, both present.
79
+ #
80
+ # multiChannel together with sms or whatsapp is rejected, and so is sms or
81
+ # whatsapp on its own — every template is expected to be deliverable on every
82
+ # channel. rcs is the one true override: it may accompany either strategy to vary
83
+ # the copy, but cannot stand alone.
66
84
  body:,
67
85
  # Configuration for AUTHENTICATION category templates
68
86
  authentication_config: nil,
@@ -6,14 +6,14 @@ module Sentdm
6
6
  OrHash =
7
7
  T.type_alias { T.any(Sentdm::TemplateEvent, Sentdm::Internal::AnyHash) }
8
8
 
9
- # The specific event within the family, for example message.delivered or
10
- # message.received. Absent on events that have no subtype, so treat it as
11
- # optional.
9
+ # The specific event within the family, for example message.delivered,
10
+ # message.received or contact.opt_out. Absent on events that have no subtype, so
11
+ # treat it as optional.
12
12
  sig { returns(T.nilable(String)) }
13
13
  attr_accessor :event
14
14
 
15
- # The event family, for example message or templates. Route on this first, then on
16
- # event for the specific change.
15
+ # The event family, for example message, templates or contact. Route on this
16
+ # first, then on event for the specific change.
17
17
  sig { returns(T.nilable(String)) }
18
18
  attr_reader :field
19
19
 
@@ -30,6 +30,10 @@ module Sentdm
30
30
  end
31
31
  attr_writer :payload
32
32
 
33
+ # The event-specific body.
34
+ sig { returns(T.nilable(String)) }
35
+ attr_accessor :request_id
36
+
33
37
  # When Sent emitted the event, in UTC (yyyy-MM-ddTHH:mm:ssZ). This is the emission
34
38
  # time, not the time the underlying change happened. Use the timestamp inside the
35
39
  # payload for the latter.
@@ -46,20 +50,23 @@ module Sentdm
46
50
  event: T.nilable(String),
47
51
  field: String,
48
52
  payload: T.nilable(Sentdm::TemplateEventPayload::OrHash),
53
+ request_id: T.nilable(String),
49
54
  timestamp: String
50
55
  ).returns(T.attached_class)
51
56
  end
52
57
  def self.new(
53
- # The specific event within the family, for example message.delivered or
54
- # message.received. Absent on events that have no subtype, so treat it as
55
- # optional.
58
+ # The specific event within the family, for example message.delivered,
59
+ # message.received or contact.opt_out. Absent on events that have no subtype, so
60
+ # treat it as optional.
56
61
  event: nil,
57
- # The event family, for example message or templates. Route on this first, then on
58
- # event for the specific change.
62
+ # The event family, for example message, templates or contact. Route on this
63
+ # first, then on event for the specific change.
59
64
  field: nil,
60
65
  # Body of a template status event. Delivered when a template's review outcome
61
66
  # changes, so you can react without polling.
62
67
  payload: nil,
68
+ # The event-specific body.
69
+ request_id: nil,
63
70
  # When Sent emitted the event, in UTC (yyyy-MM-ddTHH:mm:ssZ). This is the emission
64
71
  # time, not the time the underlying change happened. Use the timestamp inside the
65
72
  # payload for the latter.
@@ -73,6 +80,7 @@ module Sentdm
73
80
  event: T.nilable(String),
74
81
  field: String,
75
82
  payload: T.nilable(Sentdm::TemplateEventPayload),
83
+ request_id: T.nilable(String),
76
84
  timestamp: String
77
85
  }
78
86
  )