sentdm 0.30.0 → 0.32.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 (111) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -0
  3. data/README.md +29 -1
  4. data/lib/sentdm/internal/contacts_page.rb +115 -0
  5. data/lib/sentdm/internal/conversations_page.rb +115 -0
  6. data/lib/sentdm/internal/templates_page.rb +115 -0
  7. data/lib/sentdm/internal/webhook_events_page.rb +115 -0
  8. data/lib/sentdm/internal/webhooks_page.rb +115 -0
  9. data/lib/sentdm/models/api_response_of_conversation_messages_list.rb +0 -1
  10. data/lib/sentdm/models/channel_event.rb +78 -0
  11. data/lib/sentdm/models/channel_event_payload.rb +128 -0
  12. data/lib/sentdm/models/contact_event.rb +69 -0
  13. data/lib/sentdm/models/contact_event_payload.rb +111 -0
  14. data/lib/sentdm/models/contact_list_params.rb +13 -13
  15. data/lib/sentdm/models/contact_response.rb +1 -0
  16. data/lib/sentdm/models/conversation_list_messages_params.rb +5 -5
  17. data/lib/sentdm/models/conversation_list_params.rb +5 -5
  18. data/lib/sentdm/models/inbound_message_event.rb +16 -8
  19. data/lib/sentdm/models/message_event.rb +16 -8
  20. data/lib/sentdm/models/message_event_payload.rb +11 -1
  21. data/lib/sentdm/models/template.rb +37 -5
  22. data/lib/sentdm/models/template_body.rb +20 -11
  23. data/lib/sentdm/models/template_body_content.rb +35 -3
  24. data/lib/sentdm/models/template_button.rb +8 -2
  25. data/lib/sentdm/models/template_button_props.rb +12 -1
  26. data/lib/sentdm/models/template_definition.rb +14 -2
  27. data/lib/sentdm/models/template_event.rb +16 -8
  28. data/lib/sentdm/models/template_event_payload.rb +31 -4
  29. data/lib/sentdm/models/template_list_params.rb +17 -17
  30. data/lib/sentdm/models/template_variable.rb +20 -3
  31. data/lib/sentdm/models/webhook_list_events_params.rb +5 -5
  32. data/lib/sentdm/models/webhook_list_events_response.rb +116 -168
  33. data/lib/sentdm/models/webhook_list_params.rb +11 -11
  34. data/lib/sentdm/models/webhook_response.rb +1 -0
  35. data/lib/sentdm/models.rb +8 -0
  36. data/lib/sentdm/resources/contacts.rb +8 -7
  37. data/lib/sentdm/resources/conversations.rb +10 -8
  38. data/lib/sentdm/resources/templates.rb +38 -11
  39. data/lib/sentdm/resources/webhooks.rb +12 -10
  40. data/lib/sentdm/version.rb +1 -1
  41. data/lib/sentdm.rb +9 -3
  42. data/rbi/sentdm/internal/contacts_page.rbi +71 -0
  43. data/rbi/sentdm/internal/conversations_page.rbi +71 -0
  44. data/rbi/sentdm/internal/templates_page.rbi +71 -0
  45. data/rbi/sentdm/internal/webhook_events_page.rbi +71 -0
  46. data/rbi/sentdm/internal/webhooks_page.rbi +71 -0
  47. data/rbi/sentdm/models/channel_event.rbi +128 -0
  48. data/rbi/sentdm/models/channel_event_payload.rbi +189 -0
  49. data/rbi/sentdm/models/contact_event.rbi +110 -0
  50. data/rbi/sentdm/models/contact_event_payload.rbi +164 -0
  51. data/rbi/sentdm/models/contact_list_params.rbi +19 -13
  52. data/rbi/sentdm/models/conversation_list_messages_params.rbi +12 -6
  53. data/rbi/sentdm/models/conversation_list_params.rbi +16 -5
  54. data/rbi/sentdm/models/inbound_message_event.rbi +18 -10
  55. data/rbi/sentdm/models/message_event.rbi +18 -10
  56. data/rbi/sentdm/models/message_event_payload.rbi +12 -0
  57. data/rbi/sentdm/models/template.rbi +58 -4
  58. data/rbi/sentdm/models/template_body.rbi +22 -13
  59. data/rbi/sentdm/models/template_body_content.rbi +59 -1
  60. data/rbi/sentdm/models/template_button.rbi +14 -2
  61. data/rbi/sentdm/models/template_button_props.rbi +22 -0
  62. data/rbi/sentdm/models/template_definition.rbi +20 -2
  63. data/rbi/sentdm/models/template_event.rbi +18 -10
  64. data/rbi/sentdm/models/template_event_payload.rbi +51 -8
  65. data/rbi/sentdm/models/template_list_params.rbi +22 -16
  66. data/rbi/sentdm/models/template_variable.rbi +28 -1
  67. data/rbi/sentdm/models/webhook_list_events_params.rbi +12 -6
  68. data/rbi/sentdm/models/webhook_list_events_response.rbi +120 -263
  69. data/rbi/sentdm/models/webhook_list_params.rbi +16 -10
  70. data/rbi/sentdm/models.rbi +8 -0
  71. data/rbi/sentdm/resources/contacts.rbi +6 -6
  72. data/rbi/sentdm/resources/conversations.rbi +14 -6
  73. data/rbi/sentdm/resources/templates.rbi +35 -9
  74. data/rbi/sentdm/resources/webhooks.rbi +12 -8
  75. data/sig/sentdm/internal/contacts_page.rbs +41 -0
  76. data/sig/sentdm/internal/conversations_page.rbs +41 -0
  77. data/sig/sentdm/internal/templates_page.rbs +44 -0
  78. data/sig/sentdm/internal/webhook_events_page.rbs +41 -0
  79. data/sig/sentdm/internal/webhooks_page.rbs +41 -0
  80. data/sig/sentdm/models/channel_event.rbs +44 -0
  81. data/sig/sentdm/models/channel_event_payload.rbs +63 -0
  82. data/sig/sentdm/models/contact_event.rbs +44 -0
  83. data/sig/sentdm/models/contact_event_payload.rbs +63 -0
  84. data/sig/sentdm/models/contact_list_params.rbs +11 -7
  85. data/sig/sentdm/models/conversation_list_messages_params.rbs +8 -4
  86. data/sig/sentdm/models/conversation_list_params.rbs +8 -4
  87. data/sig/sentdm/models/inbound_message_event.rbs +5 -0
  88. data/sig/sentdm/models/message_event.rbs +5 -0
  89. data/sig/sentdm/models/message_event_payload.rbs +5 -0
  90. data/sig/sentdm/models/template.rbs +5 -0
  91. data/sig/sentdm/models/template_event.rbs +5 -0
  92. data/sig/sentdm/models/template_event_payload.rbs +9 -6
  93. data/sig/sentdm/models/template_list_params.rbs +14 -10
  94. data/sig/sentdm/models/webhook_list_events_params.rbs +8 -4
  95. data/sig/sentdm/models/webhook_list_events_response.rbs +65 -127
  96. data/sig/sentdm/models/webhook_list_params.rbs +11 -7
  97. data/sig/sentdm/models.rbs +8 -0
  98. data/sig/sentdm/resources/contacts.rbs +3 -3
  99. data/sig/sentdm/resources/conversations.rbs +6 -6
  100. data/sig/sentdm/resources/templates.rbs +3 -3
  101. data/sig/sentdm/resources/webhooks.rbs +6 -6
  102. metadata +29 -11
  103. data/lib/sentdm/models/contact_list_response.rb +0 -65
  104. data/lib/sentdm/models/template_list_response.rb +0 -65
  105. data/lib/sentdm/models/webhook_list_response.rb +0 -65
  106. data/rbi/sentdm/models/contact_list_response.rbi +0 -128
  107. data/rbi/sentdm/models/template_list_response.rbi +0 -128
  108. data/rbi/sentdm/models/webhook_list_response.rbi +0 -128
  109. data/sig/sentdm/models/contact_list_response.rbs +0 -67
  110. data/sig/sentdm/models/template_list_response.rbs +0 -65
  111. data/sig/sentdm/models/webhook_list_response.rbs +0 -67
@@ -6,14 +6,14 @@ module Sentdm
6
6
  OrHash =
7
7
  T.type_alias { T.any(Sentdm::MessageEvent, 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
 
@@ -31,6 +31,10 @@ module Sentdm
31
31
  end
32
32
  attr_writer :payload
33
33
 
34
+ # The event-specific body.
35
+ sig { returns(T.nilable(String)) }
36
+ attr_accessor :request_id
37
+
34
38
  # When Sent emitted the event, in UTC (yyyy-MM-ddTHH:mm:ssZ). This is the emission
35
39
  # time, not the time the underlying change happened. Use the timestamp inside the
36
40
  # payload for the latter.
@@ -47,21 +51,24 @@ module Sentdm
47
51
  event: T.nilable(String),
48
52
  field: String,
49
53
  payload: T.nilable(Sentdm::MessageEventPayload::OrHash),
54
+ request_id: T.nilable(String),
50
55
  timestamp: String
51
56
  ).returns(T.attached_class)
52
57
  end
53
58
  def self.new(
54
- # The specific event within the family, for example message.delivered or
55
- # message.received. Absent on events that have no subtype, so treat it as
56
- # optional.
59
+ # The specific event within the family, for example message.delivered,
60
+ # message.received or contact.opt_out. Absent on events that have no subtype, so
61
+ # treat it as optional.
57
62
  event: nil,
58
- # The event family, for example message or templates. Route on this first, then on
59
- # event for the specific change.
63
+ # The event family, for example message, templates or contact. Route on this
64
+ # first, then on event for the specific change.
60
65
  field: nil,
61
66
  # Body of an outbound message lifecycle event. Delivered once per status change,
62
67
  # so a single message produces several of these as it moves toward a terminal
63
68
  # status.
64
69
  payload: nil,
70
+ # The event-specific body.
71
+ request_id: nil,
65
72
  # When Sent emitted the event, in UTC (yyyy-MM-ddTHH:mm:ssZ). This is the emission
66
73
  # time, not the time the underlying change happened. Use the timestamp inside the
67
74
  # payload for the latter.
@@ -75,6 +82,7 @@ module Sentdm
75
82
  event: T.nilable(String),
76
83
  field: String,
77
84
  payload: T.nilable(Sentdm::MessageEventPayload),
85
+ request_id: T.nilable(String),
78
86
  timestamp: String
79
87
  }
80
88
  )
@@ -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)) }
@@ -72,6 +78,7 @@ module Sentdm
72
78
  message_status: String,
73
79
  account_id: String,
74
80
  agent_id: T.nilable(String),
81
+ body: T.nilable(String),
75
82
  channel: String,
76
83
  message_id: String,
77
84
  outbound_number: String,
@@ -89,6 +96,10 @@ module Sentdm
89
96
  account_id: nil,
90
97
  # The agent attributed to the send, when the send was attributed to one.
91
98
  agent_id: nil,
99
+ # The rendered message body, as plain text. Sent as null when we aren't asserting
100
+ # a body for this event. The field is always present, so read it and check for
101
+ # null rather than checking whether the key exists. Truncated to 3072 characters.
102
+ body: nil,
92
103
  # The channel the message went out on, for example sms or whatsapp. A message that
93
104
  # falls back to another channel reports the channel actually used.
94
105
  channel: nil,
@@ -113,6 +124,7 @@ module Sentdm
113
124
  message_status: String,
114
125
  account_id: String,
115
126
  agent_id: T.nilable(String),
127
+ body: T.nilable(String),
116
128
  channel: String,
117
129
  message_id: String,
118
130
  outbound_number: String,
@@ -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,7 @@ 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
+ # The shared body, used for every channel. One half of the choice described above.
11
10
  sig { returns(T.nilable(Sentdm::TemplateBodyContent)) }
12
11
  attr_reader :multi_channel
13
12
 
@@ -18,22 +17,23 @@ module Sentdm
18
17
  end
19
18
  attr_writer :multi_channel
20
19
 
21
- # RCS-specific content that overrides multi-channel content for RCS messages
20
+ # RCS-specific copy that overrides the chosen strategy for RCS only. The one true
21
+ # override: optional on top of either strategy, but it cannot be the only body
22
+ # present. Its length cap is the higher one described on Template.
22
23
  sig { returns(T.nilable(Sentdm::TemplateBodyContent)) }
23
24
  attr_reader :rcs
24
25
 
25
26
  sig { params(rcs: T.nilable(Sentdm::TemplateBodyContent::OrHash)).void }
26
27
  attr_writer :rcs
27
28
 
28
- # SMS-specific content that overrides multi-channel content for SMS messages
29
+ # The SMS body. It does not override multiChannel, it replaces it.
29
30
  sig { returns(T.nilable(Sentdm::TemplateBodyContent)) }
30
31
  attr_reader :sms
31
32
 
32
33
  sig { params(sms: T.nilable(Sentdm::TemplateBodyContent::OrHash)).void }
33
34
  attr_writer :sms
34
35
 
35
- # WhatsApp-specific content that overrides multi-channel content for WhatsApp
36
- # messages
36
+ # The WhatsApp body. It does not override multiChannel, it replaces it.
37
37
  sig { returns(T.nilable(Sentdm::TemplateBodyContent)) }
38
38
  attr_reader :whatsapp
39
39
 
@@ -42,7 +42,16 @@ module Sentdm
42
42
  end
43
43
  attr_writer :whatsapp
44
44
 
45
- # Body section of a message template with channel-specific content
45
+ # Body section of a message template.
46
+ #
47
+ # A body picks one of two authoring strategies, and mixing them is refused
48
+ # (TemplateDefinitionValidator.HaveValidChannelConfiguration): a shared
49
+ # multiChannel body on its own, or an explicit sms + whatsapp pair, both present.
50
+ #
51
+ # multiChannel together with sms or whatsapp is rejected, and so is sms or
52
+ # whatsapp on its own — every template is expected to be deliverable on every
53
+ # channel. rcs is the one true override: it may accompany either strategy to vary
54
+ # the copy, but cannot stand alone.
46
55
  sig do
47
56
  params(
48
57
  multi_channel: T.nilable(Sentdm::TemplateBodyContent::OrHash),
@@ -52,15 +61,15 @@ module Sentdm
52
61
  ).returns(T.attached_class)
53
62
  end
54
63
  def self.new(
55
- # Content that will be used for all channels (SMS and WhatsApp) unless
56
- # channel-specific content is provided
64
+ # The shared body, used for every channel. One half of the choice described above.
57
65
  multi_channel: nil,
58
- # RCS-specific content that overrides multi-channel content for RCS messages
66
+ # RCS-specific copy that overrides the chosen strategy for RCS only. The one true
67
+ # override: optional on top of either strategy, but it cannot be the only body
68
+ # present. Its length cap is the higher one described on Template.
59
69
  rcs: nil,
60
- # SMS-specific content that overrides multi-channel content for SMS messages
70
+ # The SMS body. It does not override multiChannel, it replaces it.
61
71
  sms: nil,
62
- # WhatsApp-specific content that overrides multi-channel content for WhatsApp
63
- # messages
72
+ # The WhatsApp body. It does not override multiChannel, it replaces it.
64
73
  whatsapp: nil
65
74
  )
66
75
  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
  )
@@ -24,6 +24,23 @@ module Sentdm
24
24
  sig { params(account_id: String).void }
25
25
  attr_writer :account_id
26
26
 
27
+ # Which consent keyword this template answers, when it is one of Sent's
28
+ # auto-replies: OPT_IN, OPT_OUT, HELP, or OTHER for a customer-defined keyword.
29
+ #
30
+ # Omitted for an ordinary template, so its presence is the answer to "is this an
31
+ # auto-reply". Sent creates the three compliance auto-replies at signup and they
32
+ # go through review like any other template, so their events arrive mixed in with
33
+ # the customer's own with nothing else to tell them apart.
34
+ #
35
+ # Named for the reader rather than after Template.OptAction, which it is mapped
36
+ # from. The MCP tool result deliberately keeps OptAction, OptKeywords and IsOpt:
37
+ # it mirrors the internal shape on purpose and publishes the keywords too, so
38
+ # renaming one of the three there would leave a surface half in each vocabulary.
39
+ # Two names for one concept, each consistent within its own surface, chosen over a
40
+ # rename that breaks MCP clients silently.
41
+ sig { returns(T.nilable(String)) }
42
+ attr_accessor :auto_reply_action
43
+
27
44
  # The template's category, for example UTILITY, MARKETING, or AUTHENTICATION.
28
45
  sig { returns(T.nilable(String)) }
29
46
  attr_reader :category
@@ -31,12 +48,15 @@ module Sentdm
31
48
  sig { params(category: String).void }
32
49
  attr_writer :category
33
50
 
34
- # The channel the template applies to.
51
+ # The channel leg this decision is about, for example whatsapp, sms, or rcs. A
52
+ # template is reviewed per channel and the legs come back independently, so each
53
+ # one reports separately.
54
+ #
55
+ # Omitted when the decision applies to the template as a whole rather than to one
56
+ # leg. That event is the broader news: a template-wide rejection blocks every
57
+ # channel, whatever the individual legs say.
35
58
  sig { returns(T.nilable(String)) }
36
- attr_reader :channel
37
-
38
- sig { params(channel: String).void }
39
- attr_writer :channel
59
+ attr_accessor :channel
40
60
 
41
61
  # The template's language code, for example en_US.
42
62
  sig { returns(T.nilable(String)) }
@@ -71,8 +91,9 @@ module Sentdm
71
91
  status: String,
72
92
  whatsapp_template_id: String,
73
93
  account_id: String,
94
+ auto_reply_action: T.nilable(String),
74
95
  category: String,
75
- channel: String,
96
+ channel: T.nilable(String),
76
97
  language: String,
77
98
  reason: T.nilable(String),
78
99
  template_id: String,
@@ -87,9 +108,30 @@ module Sentdm
87
108
  whatsapp_template_id:,
88
109
  # The account the template belongs to.
89
110
  account_id: nil,
111
+ # Which consent keyword this template answers, when it is one of Sent's
112
+ # auto-replies: OPT_IN, OPT_OUT, HELP, or OTHER for a customer-defined keyword.
113
+ #
114
+ # Omitted for an ordinary template, so its presence is the answer to "is this an
115
+ # auto-reply". Sent creates the three compliance auto-replies at signup and they
116
+ # go through review like any other template, so their events arrive mixed in with
117
+ # the customer's own with nothing else to tell them apart.
118
+ #
119
+ # Named for the reader rather than after Template.OptAction, which it is mapped
120
+ # from. The MCP tool result deliberately keeps OptAction, OptKeywords and IsOpt:
121
+ # it mirrors the internal shape on purpose and publishes the keywords too, so
122
+ # renaming one of the three there would leave a surface half in each vocabulary.
123
+ # Two names for one concept, each consistent within its own surface, chosen over a
124
+ # rename that breaks MCP clients silently.
125
+ auto_reply_action: nil,
90
126
  # The template's category, for example UTILITY, MARKETING, or AUTHENTICATION.
91
127
  category: nil,
92
- # The channel the template applies to.
128
+ # The channel leg this decision is about, for example whatsapp, sms, or rcs. A
129
+ # template is reviewed per channel and the legs come back independently, so each
130
+ # one reports separately.
131
+ #
132
+ # Omitted when the decision applies to the template as a whole rather than to one
133
+ # leg. That event is the broader news: a template-wide rejection blocks every
134
+ # channel, whatever the individual legs say.
93
135
  channel: nil,
94
136
  # The template's language code, for example en_US.
95
137
  language: nil,
@@ -109,8 +151,9 @@ module Sentdm
109
151
  status: String,
110
152
  whatsapp_template_id: String,
111
153
  account_id: String,
154
+ auto_reply_action: T.nilable(String),
112
155
  category: String,
113
- channel: String,
156
+ channel: T.nilable(String),
114
157
  language: String,
115
158
  reason: T.nilable(String),
116
159
  template_id: String,