sentdm 0.32.0 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -0
- data/README.md +1 -1
- data/lib/sentdm/client.rb +6 -0
- data/lib/sentdm/models/channel_event_payload.rb +154 -7
- data/lib/sentdm/models/contact_event.rb +15 -7
- data/lib/sentdm/models/contact_event_payload.rb +77 -16
- data/lib/sentdm/models/conversation_messages_list.rb +214 -6
- data/lib/sentdm/models/inbound_message_event_payload.rb +70 -3
- data/lib/sentdm/models/me_retrieve_response.rb +21 -1
- data/lib/sentdm/models/message_event_payload.rb +42 -1
- data/lib/sentdm/models/message_retrieve_activities_response.rb +42 -5
- data/lib/sentdm/models/message_retrieve_status_response.rb +222 -8
- data/lib/sentdm/models/message_send_params.rb +67 -3
- data/lib/sentdm/models/message_send_response.rb +13 -4
- data/lib/sentdm/models/template_body.rb +82 -1
- data/lib/sentdm/models/template_create_params.rb +11 -1
- data/lib/sentdm/models/template_header.rb +91 -3
- data/lib/sentdm/models/template_variable.rb +15 -1
- data/lib/sentdm/models/webhook_create_params.rb +31 -1
- data/lib/sentdm/models/webhook_list_events_response.rb +497 -9
- data/lib/sentdm/models/webhook_update_params.rb +31 -1
- data/lib/sentdm/resources/me.rb +5 -0
- data/lib/sentdm/resources/messages.rb +33 -5
- data/lib/sentdm/resources/templates.rb +3 -1
- data/lib/sentdm/resources/webhooks.rb +9 -3
- data/lib/sentdm/version.rb +1 -1
- data/lib/sentdm.rb +1 -1
- data/rbi/sentdm/client.rbi +6 -0
- data/rbi/sentdm/models/channel_event_payload.rbi +273 -10
- data/rbi/sentdm/models/contact_event.rbi +28 -12
- data/rbi/sentdm/models/contact_event_payload.rbi +118 -28
- data/rbi/sentdm/models/conversation_messages_list.rbi +308 -10
- data/rbi/sentdm/models/inbound_message_event_payload.rbi +110 -2
- data/rbi/sentdm/models/me_retrieve_response.rbi +35 -0
- data/rbi/sentdm/models/message_event_payload.rbi +50 -0
- data/rbi/sentdm/models/message_retrieve_activities_response.rbi +49 -5
- data/rbi/sentdm/models/message_retrieve_status_response.rbi +320 -12
- data/rbi/sentdm/models/message_send_params.rbi +100 -2
- data/rbi/sentdm/models/message_send_response.rbi +15 -5
- data/rbi/sentdm/models/template_body.rbi +133 -0
- data/rbi/sentdm/models/template_create_params.rbi +15 -0
- data/rbi/sentdm/models/template_header.rbi +143 -2
- data/rbi/sentdm/models/template_variable.rbi +10 -0
- data/rbi/sentdm/models/webhook_create_params.rbi +61 -0
- data/rbi/sentdm/models/webhook_list_events_response.rbi +747 -12
- data/rbi/sentdm/models/webhook_update_params.rbi +61 -0
- data/rbi/sentdm/resources/me.rbi +5 -0
- data/rbi/sentdm/resources/messages.rbi +58 -3
- data/rbi/sentdm/resources/templates.rbi +6 -0
- data/rbi/sentdm/resources/webhooks.rbi +15 -1
- data/sig/sentdm/models/channel_event_payload.rbs +62 -0
- data/sig/sentdm/models/contact_event_payload.rbs +24 -9
- data/sig/sentdm/models/conversation_messages_list.rbs +99 -6
- data/sig/sentdm/models/inbound_message_event_payload.rbs +37 -0
- data/sig/sentdm/models/me_retrieve_response.rbs +7 -0
- data/sig/sentdm/models/message_event_payload.rbs +20 -0
- data/sig/sentdm/models/message_retrieve_activities_response.rbs +15 -0
- data/sig/sentdm/models/message_retrieve_status_response.rbs +99 -6
- data/sig/sentdm/models/message_send_params.rbs +15 -0
- data/sig/sentdm/models/template_body.rbs +44 -0
- data/sig/sentdm/models/template_create_params.rbs +7 -0
- data/sig/sentdm/models/template_header.rbs +44 -0
- data/sig/sentdm/models/webhook_create_params.rbs +29 -0
- data/sig/sentdm/models/webhook_list_events_response.rbs +252 -0
- data/sig/sentdm/models/webhook_update_params.rbs +29 -0
- data/sig/sentdm/resources/messages.rbs +3 -0
- data/sig/sentdm/resources/templates.rbs +1 -0
- data/sig/sentdm/resources/webhooks.rbs +2 -0
- metadata +2 -2
|
@@ -9,8 +9,9 @@ module Sentdm
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
# Whether the contact is opted out after this signal — the state to write to your
|
|
12
|
-
# own record. Same meaning as opt_out on the contact resource. On contact.help
|
|
13
|
-
# this reports the contact's existing state, which
|
|
12
|
+
# own record. Same meaning as opt_out on the contact resource. On contact.help and
|
|
13
|
+
# contact.custom_keyword this reports the contact's existing state, which neither
|
|
14
|
+
# changes.
|
|
14
15
|
#
|
|
15
16
|
# Two signals from the same contact can arrive out of order, because each one is
|
|
16
17
|
# queued on its own rather than against the contact. Compare the envelope's
|
|
@@ -35,6 +36,16 @@ module Sentdm
|
|
|
35
36
|
sig { params(account_id: String).void }
|
|
36
37
|
attr_writer :account_id
|
|
37
38
|
|
|
39
|
+
# The RCS agent the signal reached, when it reached one.
|
|
40
|
+
#
|
|
41
|
+
# Omitted entirely on channels that have no agent, rather than sent as null — an
|
|
42
|
+
# SMS or WhatsApp payload does not carry this key at all. On RCS it is the
|
|
43
|
+
# counterpart to To: a contact reaches an agent rather than a number, so exactly
|
|
44
|
+
# one of the two is populated and never both. If you run more than one agent, this
|
|
45
|
+
# is what tells you which of them the contact acted on.
|
|
46
|
+
sig { returns(T.nilable(String)) }
|
|
47
|
+
attr_accessor :agent_id
|
|
48
|
+
|
|
38
49
|
# The channel the signal arrived on, for example sms or whatsapp.
|
|
39
50
|
sig { returns(T.nilable(String)) }
|
|
40
51
|
attr_reader :channel
|
|
@@ -43,14 +54,23 @@ module Sentdm
|
|
|
43
54
|
attr_writer :channel
|
|
44
55
|
|
|
45
56
|
# The contact who raised the signal. Always populated, including for contact.help
|
|
46
|
-
# from a number you have not messaged before — the
|
|
47
|
-
# not exist yet, so this identifier is always
|
|
57
|
+
# or contact.custom_keyword from a number you have not messaged before — the
|
|
58
|
+
# contact is created if it does not exist yet, so this identifier is always
|
|
59
|
+
# resolvable against the contacts API.
|
|
48
60
|
sig { returns(T.nilable(String)) }
|
|
49
61
|
attr_reader :contact_id
|
|
50
62
|
|
|
51
63
|
sig { params(contact_id: String).void }
|
|
52
64
|
attr_writer :contact_id
|
|
53
65
|
|
|
66
|
+
# The contact's number, in E.164 format with the leading + — who raised the
|
|
67
|
+
# signal. The same party message.received publishes as inbound_number.
|
|
68
|
+
sig { returns(T.nilable(String)) }
|
|
69
|
+
attr_reader :from
|
|
70
|
+
|
|
71
|
+
sig { params(from: String).void }
|
|
72
|
+
attr_writer :from
|
|
73
|
+
|
|
54
74
|
# The inbound message that carried the signal, matching message_id on the
|
|
55
75
|
# corresponding message.received event so the two can be joined.
|
|
56
76
|
#
|
|
@@ -62,13 +82,19 @@ module Sentdm
|
|
|
62
82
|
sig { returns(T.nilable(String)) }
|
|
63
83
|
attr_accessor :message_id
|
|
64
84
|
|
|
65
|
-
# The
|
|
66
|
-
#
|
|
85
|
+
# The auto-reply template whose keyword the contact matched, joinable against the
|
|
86
|
+
# templates API.
|
|
87
|
+
#
|
|
88
|
+
# This is what identifies which signal arrived on contact.custom_keyword: every
|
|
89
|
+
# custom template reports the same event name, so the event alone cannot tell your
|
|
90
|
+
# booking keyword from your opening-hours one. One template holds as many keywords
|
|
91
|
+
# as you configured, so this is steadier to switch on than text.
|
|
92
|
+
#
|
|
93
|
+
# Populated on the compliance sub-types too, where it names the template that
|
|
94
|
+
# replied. Sent as null when no template was involved — a network-reported opt-out
|
|
95
|
+
# matches no keyword. The field is always present, so read it and check for null.
|
|
67
96
|
sig { returns(T.nilable(String)) }
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
sig { params(phone_number: String).void }
|
|
71
|
-
attr_writer :phone_number
|
|
97
|
+
attr_accessor :template_id
|
|
72
98
|
|
|
73
99
|
# The text the contact sent, for example STOP or UNSUBSCRIBE. Sent as null when
|
|
74
100
|
# the signal did not arrive as text. The field is always present, so read it and
|
|
@@ -76,33 +102,60 @@ module Sentdm
|
|
|
76
102
|
sig { returns(T.nilable(String)) }
|
|
77
103
|
attr_accessor :text
|
|
78
104
|
|
|
79
|
-
#
|
|
80
|
-
# a
|
|
105
|
+
# The number of yours that received the signal, in E.164 format with the leading
|
|
106
|
+
# +. Tells a multi-number account which of its senders the contact acted on, which
|
|
107
|
+
# nothing else on this payload answers.
|
|
108
|
+
#
|
|
109
|
+
# This is your number, not the contact's. That is the opposite of what to means on
|
|
110
|
+
# POST /v3/messages, where it is the list of recipients you are sending to. Reply
|
|
111
|
+
# to From, not to this field, or the message goes back to yourself.
|
|
112
|
+
#
|
|
113
|
+
# Sent as null when the signal did not arrive at a number of yours — an RCS signal
|
|
114
|
+
# terminates at an agent rather than a number, and a provider-reported opt-out may
|
|
115
|
+
# name no receiving number at all. The field is always present, so read it and
|
|
116
|
+
# check for null rather than checking whether the key exists.
|
|
117
|
+
sig { returns(T.nilable(String)) }
|
|
118
|
+
attr_accessor :to
|
|
119
|
+
|
|
120
|
+
# Body of a contact.opt_in, contact.opt_out, contact.help or
|
|
121
|
+
# contact.custom_keyword event. Delivered when a contact signals a consent change,
|
|
122
|
+
# asks for help, or sends one of your own auto-reply keywords.
|
|
81
123
|
#
|
|
82
124
|
# These events state the signal outright, so you do not have to recognise keywords
|
|
83
125
|
# in the text of a message.received event. They also cover cases that produce no
|
|
84
126
|
# inbound message at all, such as a network handling an opt-out on your behalf.
|
|
85
127
|
#
|
|
86
|
-
#
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
#
|
|
128
|
+
# Two of the four change consent and two do not: contact.help and
|
|
129
|
+
# contact.custom_keyword report the state the contact already had. Read opt_out
|
|
130
|
+
# for the state and the envelope's event for what happened, rather than inferring
|
|
131
|
+
# one from the other.
|
|
132
|
+
#
|
|
133
|
+
# Fields are ordered identity → resulting state → provenance → join keys. The two
|
|
134
|
+
# parties are from and to. Note that the message family has not moved to those
|
|
135
|
+
# names yet — message.received still calls the same two parties inbound_number and
|
|
136
|
+
# outbound_number. Nothing here restates the envelope: which signal occurred is
|
|
137
|
+
# the envelope's event, and when it was emitted is its timestamp. Retries carry
|
|
138
|
+
# the same X-Webhook-Event-ID header, which is what to deduplicate on.
|
|
90
139
|
sig do
|
|
91
140
|
params(
|
|
92
141
|
opt_out: T::Boolean,
|
|
93
142
|
source: String,
|
|
94
143
|
account_id: String,
|
|
144
|
+
agent_id: T.nilable(String),
|
|
95
145
|
channel: String,
|
|
96
146
|
contact_id: String,
|
|
147
|
+
from: String,
|
|
97
148
|
message_id: T.nilable(String),
|
|
98
|
-
|
|
99
|
-
text: T.nilable(String)
|
|
149
|
+
template_id: T.nilable(String),
|
|
150
|
+
text: T.nilable(String),
|
|
151
|
+
to: T.nilable(String)
|
|
100
152
|
).returns(T.attached_class)
|
|
101
153
|
end
|
|
102
154
|
def self.new(
|
|
103
155
|
# Whether the contact is opted out after this signal — the state to write to your
|
|
104
|
-
# own record. Same meaning as opt_out on the contact resource. On contact.help
|
|
105
|
-
# this reports the contact's existing state, which
|
|
156
|
+
# own record. Same meaning as opt_out on the contact resource. On contact.help and
|
|
157
|
+
# contact.custom_keyword this reports the contact's existing state, which neither
|
|
158
|
+
# changes.
|
|
106
159
|
#
|
|
107
160
|
# Two signals from the same contact can arrive out of order, because each one is
|
|
108
161
|
# queued on its own rather than against the contact. Compare the envelope's
|
|
@@ -118,12 +171,24 @@ module Sentdm
|
|
|
118
171
|
# The account the contact belongs to. Present so one endpoint can serve several
|
|
119
172
|
# accounts.
|
|
120
173
|
account_id: nil,
|
|
174
|
+
# The RCS agent the signal reached, when it reached one.
|
|
175
|
+
#
|
|
176
|
+
# Omitted entirely on channels that have no agent, rather than sent as null — an
|
|
177
|
+
# SMS or WhatsApp payload does not carry this key at all. On RCS it is the
|
|
178
|
+
# counterpart to To: a contact reaches an agent rather than a number, so exactly
|
|
179
|
+
# one of the two is populated and never both. If you run more than one agent, this
|
|
180
|
+
# is what tells you which of them the contact acted on.
|
|
181
|
+
agent_id: nil,
|
|
121
182
|
# The channel the signal arrived on, for example sms or whatsapp.
|
|
122
183
|
channel: nil,
|
|
123
184
|
# The contact who raised the signal. Always populated, including for contact.help
|
|
124
|
-
# from a number you have not messaged before — the
|
|
125
|
-
# not exist yet, so this identifier is always
|
|
185
|
+
# or contact.custom_keyword from a number you have not messaged before — the
|
|
186
|
+
# contact is created if it does not exist yet, so this identifier is always
|
|
187
|
+
# resolvable against the contacts API.
|
|
126
188
|
contact_id: nil,
|
|
189
|
+
# The contact's number, in E.164 format with the leading + — who raised the
|
|
190
|
+
# signal. The same party message.received publishes as inbound_number.
|
|
191
|
+
from: nil,
|
|
127
192
|
# The inbound message that carried the signal, matching message_id on the
|
|
128
193
|
# corresponding message.received event so the two can be joined.
|
|
129
194
|
#
|
|
@@ -133,13 +198,35 @@ module Sentdm
|
|
|
133
198
|
# number. The field is always present, so read it and check for null rather than
|
|
134
199
|
# checking whether the key exists.
|
|
135
200
|
message_id: nil,
|
|
136
|
-
# The
|
|
137
|
-
#
|
|
138
|
-
|
|
201
|
+
# The auto-reply template whose keyword the contact matched, joinable against the
|
|
202
|
+
# templates API.
|
|
203
|
+
#
|
|
204
|
+
# This is what identifies which signal arrived on contact.custom_keyword: every
|
|
205
|
+
# custom template reports the same event name, so the event alone cannot tell your
|
|
206
|
+
# booking keyword from your opening-hours one. One template holds as many keywords
|
|
207
|
+
# as you configured, so this is steadier to switch on than text.
|
|
208
|
+
#
|
|
209
|
+
# Populated on the compliance sub-types too, where it names the template that
|
|
210
|
+
# replied. Sent as null when no template was involved — a network-reported opt-out
|
|
211
|
+
# matches no keyword. The field is always present, so read it and check for null.
|
|
212
|
+
template_id: nil,
|
|
139
213
|
# The text the contact sent, for example STOP or UNSUBSCRIBE. Sent as null when
|
|
140
214
|
# the signal did not arrive as text. The field is always present, so read it and
|
|
141
215
|
# check for null rather than checking whether the key exists.
|
|
142
|
-
text: nil
|
|
216
|
+
text: nil,
|
|
217
|
+
# The number of yours that received the signal, in E.164 format with the leading
|
|
218
|
+
# +. Tells a multi-number account which of its senders the contact acted on, which
|
|
219
|
+
# nothing else on this payload answers.
|
|
220
|
+
#
|
|
221
|
+
# This is your number, not the contact's. That is the opposite of what to means on
|
|
222
|
+
# POST /v3/messages, where it is the list of recipients you are sending to. Reply
|
|
223
|
+
# to From, not to this field, or the message goes back to yourself.
|
|
224
|
+
#
|
|
225
|
+
# Sent as null when the signal did not arrive at a number of yours — an RCS signal
|
|
226
|
+
# terminates at an agent rather than a number, and a provider-reported opt-out may
|
|
227
|
+
# name no receiving number at all. The field is always present, so read it and
|
|
228
|
+
# check for null rather than checking whether the key exists.
|
|
229
|
+
to: nil
|
|
143
230
|
)
|
|
144
231
|
end
|
|
145
232
|
|
|
@@ -149,11 +236,14 @@ module Sentdm
|
|
|
149
236
|
opt_out: T::Boolean,
|
|
150
237
|
source: String,
|
|
151
238
|
account_id: String,
|
|
239
|
+
agent_id: T.nilable(String),
|
|
152
240
|
channel: String,
|
|
153
241
|
contact_id: String,
|
|
242
|
+
from: String,
|
|
154
243
|
message_id: T.nilable(String),
|
|
155
|
-
|
|
156
|
-
text: T.nilable(String)
|
|
244
|
+
template_id: T.nilable(String),
|
|
245
|
+
text: T.nilable(String),
|
|
246
|
+
to: T.nilable(String)
|
|
157
247
|
}
|
|
158
248
|
)
|
|
159
249
|
end
|
|
@@ -112,7 +112,14 @@ module Sentdm
|
|
|
112
112
|
attr_accessor :events
|
|
113
113
|
|
|
114
114
|
# Structured message body format for database storage. Preserves channel-specific
|
|
115
|
-
# components (header, body, footer, buttons).
|
|
115
|
+
# components (header, header media, body, footer, buttons, MMS subject and media).
|
|
116
|
+
#
|
|
117
|
+
# Persisted as the messageBody jsonb column on Messages. Every write path goes
|
|
118
|
+
# through MessageUtils.MessageBodyJsonOptions, which writes nulls, so the envelope
|
|
119
|
+
# shape is stable regardless of channel or status. Anything that rebuilds this
|
|
120
|
+
# object field by field — the four IMessageBodyStrategy implementations and
|
|
121
|
+
# MessageUtils.BuildSegmentBody — has to carry every member, or that member is
|
|
122
|
+
# silently dropped on whichever path forgot it.
|
|
116
123
|
sig do
|
|
117
124
|
returns(
|
|
118
125
|
T.nilable(Sentdm::ConversationMessagesList::Message::MessageBody)
|
|
@@ -145,6 +152,20 @@ module Sentdm
|
|
|
145
152
|
sig { returns(T.nilable(Float)) }
|
|
146
153
|
attr_accessor :price
|
|
147
154
|
|
|
155
|
+
# A human-readable sentence for reason_code, for example "Insufficient balance".
|
|
156
|
+
# Omitted whenever reason_code is.
|
|
157
|
+
sig { returns(T.nilable(String)) }
|
|
158
|
+
attr_accessor :reason
|
|
159
|
+
|
|
160
|
+
# Why the message is at its current status, as a stable platform code such as
|
|
161
|
+
# DELIVERY_007, BUSINESS_003 or DELIVERY_003. Present when the current status is
|
|
162
|
+
# FAILED, FILTERED or BLOCKED and the lifecycle was loaded; omitted otherwise.
|
|
163
|
+
# Switch on this rather than on reason: the code is stable, the wording may be
|
|
164
|
+
# improved. It is the platform's classification of the outcome, never a carrier or
|
|
165
|
+
# vendor code.
|
|
166
|
+
sig { returns(T.nilable(String)) }
|
|
167
|
+
attr_accessor :reason_code
|
|
168
|
+
|
|
148
169
|
sig { returns(T.nilable(String)) }
|
|
149
170
|
attr_reader :region_code
|
|
150
171
|
|
|
@@ -166,7 +187,12 @@ module Sentdm
|
|
|
166
187
|
sig { returns(T.nilable(String)) }
|
|
167
188
|
attr_accessor :template_name
|
|
168
189
|
|
|
169
|
-
# Message response for v3 API — same shape as v2 with snake_case JSON conventions
|
|
190
|
+
# Message response for v3 API — same shape as v2 with snake_case JSON conventions.
|
|
191
|
+
#
|
|
192
|
+
# The shape of a message that was sent immediately: it never has a scheduled_at
|
|
193
|
+
# key. A message that is or was held for a later instant is a
|
|
194
|
+
# ScheduledMessageResponse, and the endpoint decides which of the two to answer
|
|
195
|
+
# with. From always returns this type.
|
|
170
196
|
sig do
|
|
171
197
|
params(
|
|
172
198
|
id: String,
|
|
@@ -189,6 +215,8 @@ module Sentdm
|
|
|
189
215
|
phone: String,
|
|
190
216
|
phone_international: String,
|
|
191
217
|
price: T.nilable(Float),
|
|
218
|
+
reason: T.nilable(String),
|
|
219
|
+
reason_code: T.nilable(String),
|
|
192
220
|
region_code: String,
|
|
193
221
|
status: String,
|
|
194
222
|
template_category: T.nilable(String),
|
|
@@ -206,11 +234,28 @@ module Sentdm
|
|
|
206
234
|
direction: nil,
|
|
207
235
|
events: nil,
|
|
208
236
|
# Structured message body format for database storage. Preserves channel-specific
|
|
209
|
-
# components (header, body, footer, buttons).
|
|
237
|
+
# components (header, header media, body, footer, buttons, MMS subject and media).
|
|
238
|
+
#
|
|
239
|
+
# Persisted as the messageBody jsonb column on Messages. Every write path goes
|
|
240
|
+
# through MessageUtils.MessageBodyJsonOptions, which writes nulls, so the envelope
|
|
241
|
+
# shape is stable regardless of channel or status. Anything that rebuilds this
|
|
242
|
+
# object field by field — the four IMessageBodyStrategy implementations and
|
|
243
|
+
# MessageUtils.BuildSegmentBody — has to carry every member, or that member is
|
|
244
|
+
# silently dropped on whichever path forgot it.
|
|
210
245
|
message_body: nil,
|
|
211
246
|
phone: nil,
|
|
212
247
|
phone_international: nil,
|
|
213
248
|
price: nil,
|
|
249
|
+
# A human-readable sentence for reason_code, for example "Insufficient balance".
|
|
250
|
+
# Omitted whenever reason_code is.
|
|
251
|
+
reason: nil,
|
|
252
|
+
# Why the message is at its current status, as a stable platform code such as
|
|
253
|
+
# DELIVERY_007, BUSINESS_003 or DELIVERY_003. Present when the current status is
|
|
254
|
+
# FAILED, FILTERED or BLOCKED and the lifecycle was loaded; omitted otherwise.
|
|
255
|
+
# Switch on this rather than on reason: the code is stable, the wording may be
|
|
256
|
+
# improved. It is the platform's classification of the outcome, never a carrier or
|
|
257
|
+
# vendor code.
|
|
258
|
+
reason_code: nil,
|
|
214
259
|
region_code: nil,
|
|
215
260
|
status: nil,
|
|
216
261
|
template_category: nil,
|
|
@@ -240,6 +285,8 @@ module Sentdm
|
|
|
240
285
|
phone: String,
|
|
241
286
|
phone_international: String,
|
|
242
287
|
price: T.nilable(Float),
|
|
288
|
+
reason: T.nilable(String),
|
|
289
|
+
reason_code: T.nilable(String),
|
|
243
290
|
region_code: String,
|
|
244
291
|
status: String,
|
|
245
292
|
template_category: T.nilable(String),
|
|
@@ -269,15 +316,39 @@ module Sentdm
|
|
|
269
316
|
sig { returns(T.nilable(String)) }
|
|
270
317
|
attr_accessor :description
|
|
271
318
|
|
|
319
|
+
# A human-readable sentence for reason_code. Omitted whenever reason_code is.
|
|
320
|
+
sig { returns(T.nilable(String)) }
|
|
321
|
+
attr_accessor :reason
|
|
322
|
+
|
|
323
|
+
# Why the message reached this status, as a stable platform code such as
|
|
324
|
+
# DELIVERY_007. Present on FAILED, FILTERED and BLOCKED events; omitted on every
|
|
325
|
+
# status that needs no explanation. Same wire name and vocabulary as on the
|
|
326
|
+
# activities list and the webhook.
|
|
327
|
+
sig { returns(T.nilable(String)) }
|
|
328
|
+
attr_accessor :reason_code
|
|
329
|
+
|
|
272
330
|
# Represents a status change event in a message's lifecycle (v3)
|
|
273
331
|
sig do
|
|
274
332
|
params(
|
|
275
333
|
status: String,
|
|
276
334
|
timestamp: Time,
|
|
277
|
-
description: T.nilable(String)
|
|
335
|
+
description: T.nilable(String),
|
|
336
|
+
reason: T.nilable(String),
|
|
337
|
+
reason_code: T.nilable(String)
|
|
278
338
|
).returns(T.attached_class)
|
|
279
339
|
end
|
|
280
|
-
def self.new(
|
|
340
|
+
def self.new(
|
|
341
|
+
status:,
|
|
342
|
+
timestamp:,
|
|
343
|
+
description: nil,
|
|
344
|
+
# A human-readable sentence for reason_code. Omitted whenever reason_code is.
|
|
345
|
+
reason: nil,
|
|
346
|
+
# Why the message reached this status, as a stable platform code such as
|
|
347
|
+
# DELIVERY_007. Present on FAILED, FILTERED and BLOCKED events; omitted on every
|
|
348
|
+
# status that needs no explanation. Same wire name and vocabulary as on the
|
|
349
|
+
# activities list and the webhook.
|
|
350
|
+
reason_code: nil
|
|
351
|
+
)
|
|
281
352
|
end
|
|
282
353
|
|
|
283
354
|
sig do
|
|
@@ -285,7 +356,9 @@ module Sentdm
|
|
|
285
356
|
{
|
|
286
357
|
status: String,
|
|
287
358
|
timestamp: Time,
|
|
288
|
-
description: T.nilable(String)
|
|
359
|
+
description: T.nilable(String),
|
|
360
|
+
reason: T.nilable(String),
|
|
361
|
+
reason_code: T.nilable(String)
|
|
289
362
|
}
|
|
290
363
|
)
|
|
291
364
|
end
|
|
@@ -325,8 +398,57 @@ module Sentdm
|
|
|
325
398
|
sig { returns(T.nilable(String)) }
|
|
326
399
|
attr_accessor :header
|
|
327
400
|
|
|
401
|
+
# The media asset that rode a message's header, recorded as sent.
|
|
402
|
+
sig do
|
|
403
|
+
returns(
|
|
404
|
+
T.nilable(
|
|
405
|
+
Sentdm::ConversationMessagesList::Message::MessageBody::HeaderMedia
|
|
406
|
+
)
|
|
407
|
+
)
|
|
408
|
+
end
|
|
409
|
+
attr_reader :header_media
|
|
410
|
+
|
|
411
|
+
sig do
|
|
412
|
+
params(
|
|
413
|
+
header_media:
|
|
414
|
+
T.nilable(
|
|
415
|
+
Sentdm::ConversationMessagesList::Message::MessageBody::HeaderMedia::OrHash
|
|
416
|
+
)
|
|
417
|
+
).void
|
|
418
|
+
end
|
|
419
|
+
attr_writer :header_media
|
|
420
|
+
|
|
421
|
+
# MMS attachments, as the publicly fetchable URLs handed to the carrier. Null on
|
|
422
|
+
# every other channel.
|
|
423
|
+
#
|
|
424
|
+
# Persisted rather than derived because a resend and a curfew release rebuild the
|
|
425
|
+
# send from the stored row — MessageReplayCommandBuilder reads templateId and
|
|
426
|
+
# templateVariables and nothing else — so media that lives only on the original
|
|
427
|
+
# request would silently turn a replayed MMS into a text message.
|
|
428
|
+
sig do
|
|
429
|
+
returns(
|
|
430
|
+
T.nilable(
|
|
431
|
+
T::Array[
|
|
432
|
+
Sentdm::ConversationMessagesList::Message::MessageBody::Media
|
|
433
|
+
]
|
|
434
|
+
)
|
|
435
|
+
)
|
|
436
|
+
end
|
|
437
|
+
attr_accessor :media
|
|
438
|
+
|
|
439
|
+
# MMS subject line. Null on every other channel.
|
|
440
|
+
sig { returns(T.nilable(String)) }
|
|
441
|
+
attr_accessor :subject
|
|
442
|
+
|
|
328
443
|
# Structured message body format for database storage. Preserves channel-specific
|
|
329
|
-
# components (header, body, footer, buttons).
|
|
444
|
+
# components (header, header media, body, footer, buttons, MMS subject and media).
|
|
445
|
+
#
|
|
446
|
+
# Persisted as the messageBody jsonb column on Messages. Every write path goes
|
|
447
|
+
# through MessageUtils.MessageBodyJsonOptions, which writes nulls, so the envelope
|
|
448
|
+
# shape is stable regardless of channel or status. Anything that rebuilds this
|
|
449
|
+
# object field by field — the four IMessageBodyStrategy implementations and
|
|
450
|
+
# MessageUtils.BuildSegmentBody — has to carry every member, or that member is
|
|
451
|
+
# silently dropped on whichever path forgot it.
|
|
330
452
|
sig do
|
|
331
453
|
params(
|
|
332
454
|
buttons:
|
|
@@ -337,10 +459,38 @@ module Sentdm
|
|
|
337
459
|
),
|
|
338
460
|
content: String,
|
|
339
461
|
footer: T.nilable(String),
|
|
340
|
-
header: T.nilable(String)
|
|
462
|
+
header: T.nilable(String),
|
|
463
|
+
header_media:
|
|
464
|
+
T.nilable(
|
|
465
|
+
Sentdm::ConversationMessagesList::Message::MessageBody::HeaderMedia::OrHash
|
|
466
|
+
),
|
|
467
|
+
media:
|
|
468
|
+
T.nilable(
|
|
469
|
+
T::Array[
|
|
470
|
+
Sentdm::ConversationMessagesList::Message::MessageBody::Media::OrHash
|
|
471
|
+
]
|
|
472
|
+
),
|
|
473
|
+
subject: T.nilable(String)
|
|
341
474
|
).returns(T.attached_class)
|
|
342
475
|
end
|
|
343
|
-
def self.new(
|
|
476
|
+
def self.new(
|
|
477
|
+
buttons: nil,
|
|
478
|
+
content: nil,
|
|
479
|
+
footer: nil,
|
|
480
|
+
header: nil,
|
|
481
|
+
# The media asset that rode a message's header, recorded as sent.
|
|
482
|
+
header_media: nil,
|
|
483
|
+
# MMS attachments, as the publicly fetchable URLs handed to the carrier. Null on
|
|
484
|
+
# every other channel.
|
|
485
|
+
#
|
|
486
|
+
# Persisted rather than derived because a resend and a curfew release rebuild the
|
|
487
|
+
# send from the stored row — MessageReplayCommandBuilder reads templateId and
|
|
488
|
+
# templateVariables and nothing else — so media that lives only on the original
|
|
489
|
+
# request would silently turn a replayed MMS into a text message.
|
|
490
|
+
media: nil,
|
|
491
|
+
# MMS subject line. Null on every other channel.
|
|
492
|
+
subject: nil
|
|
493
|
+
)
|
|
344
494
|
end
|
|
345
495
|
|
|
346
496
|
sig do
|
|
@@ -354,7 +504,18 @@ module Sentdm
|
|
|
354
504
|
),
|
|
355
505
|
content: String,
|
|
356
506
|
footer: T.nilable(String),
|
|
357
|
-
header: T.nilable(String)
|
|
507
|
+
header: T.nilable(String),
|
|
508
|
+
header_media:
|
|
509
|
+
T.nilable(
|
|
510
|
+
Sentdm::ConversationMessagesList::Message::MessageBody::HeaderMedia
|
|
511
|
+
),
|
|
512
|
+
media:
|
|
513
|
+
T.nilable(
|
|
514
|
+
T::Array[
|
|
515
|
+
Sentdm::ConversationMessagesList::Message::MessageBody::Media
|
|
516
|
+
]
|
|
517
|
+
),
|
|
518
|
+
subject: T.nilable(String)
|
|
358
519
|
}
|
|
359
520
|
)
|
|
360
521
|
end
|
|
@@ -412,6 +573,143 @@ module Sentdm
|
|
|
412
573
|
def to_hash
|
|
413
574
|
end
|
|
414
575
|
end
|
|
576
|
+
|
|
577
|
+
class HeaderMedia < Sentdm::Internal::Type::BaseModel
|
|
578
|
+
OrHash =
|
|
579
|
+
T.type_alias do
|
|
580
|
+
T.any(
|
|
581
|
+
Sentdm::ConversationMessagesList::Message::MessageBody::HeaderMedia,
|
|
582
|
+
Sentdm::Internal::AnyHash
|
|
583
|
+
)
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
# "image", "video" or "document" — taken from the header's media variable.
|
|
587
|
+
sig { returns(T.nilable(String)) }
|
|
588
|
+
attr_reader :type
|
|
589
|
+
|
|
590
|
+
sig { params(type: String).void }
|
|
591
|
+
attr_writer :type
|
|
592
|
+
|
|
593
|
+
# The https URL the caller supplied for this send. Never the template's stored
|
|
594
|
+
# props.sample, which is Meta's expiring header_handle rather than what was
|
|
595
|
+
# delivered.
|
|
596
|
+
sig { returns(T.nilable(String)) }
|
|
597
|
+
attr_reader :url
|
|
598
|
+
|
|
599
|
+
sig { params(url: String).void }
|
|
600
|
+
attr_writer :url
|
|
601
|
+
|
|
602
|
+
# The media asset that rode a message's header, recorded as sent.
|
|
603
|
+
sig { params(type: String, url: String).returns(T.attached_class) }
|
|
604
|
+
def self.new(
|
|
605
|
+
# "image", "video" or "document" — taken from the header's media variable.
|
|
606
|
+
type: nil,
|
|
607
|
+
# The https URL the caller supplied for this send. Never the template's stored
|
|
608
|
+
# props.sample, which is Meta's expiring header_handle rather than what was
|
|
609
|
+
# delivered.
|
|
610
|
+
url: nil
|
|
611
|
+
)
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
sig { override.returns({ type: String, url: String }) }
|
|
615
|
+
def to_hash
|
|
616
|
+
end
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
class Media < Sentdm::Internal::Type::BaseModel
|
|
620
|
+
OrHash =
|
|
621
|
+
T.type_alias do
|
|
622
|
+
T.any(
|
|
623
|
+
Sentdm::ConversationMessagesList::Message::MessageBody::Media,
|
|
624
|
+
Sentdm::Internal::AnyHash
|
|
625
|
+
)
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
# One of MmsMediaTypes when the content type is known. Advisory — a reader should
|
|
629
|
+
# trust the fetched object's own Content-Type.
|
|
630
|
+
sig { returns(T.nilable(String)) }
|
|
631
|
+
attr_accessor :media_type
|
|
632
|
+
|
|
633
|
+
# Content type as the provider declared it. Null when it declared none.
|
|
634
|
+
sig { returns(T.nilable(String)) }
|
|
635
|
+
attr_accessor :mime_type
|
|
636
|
+
|
|
637
|
+
# Size as the provider declared it. Never measured here — nothing downloads the
|
|
638
|
+
# file.
|
|
639
|
+
sig { returns(T.nilable(Integer)) }
|
|
640
|
+
attr_accessor :size_bytes
|
|
641
|
+
|
|
642
|
+
# Inbound only: the SHA-256 the provider declared alongside the attachment, when
|
|
643
|
+
# it declared one. Relayed to the customer so they can verify what they fetch
|
|
644
|
+
# matches what the carrier said it sent. It is the only integrity signal available
|
|
645
|
+
# on an attachment nobody here has read.
|
|
646
|
+
sig { returns(T.nilable(String)) }
|
|
647
|
+
attr_accessor :source_hash_sha256
|
|
648
|
+
|
|
649
|
+
# Where the file lives. Outbound: the URL the customer gave us and the carrier
|
|
650
|
+
# fetched. Inbound: the URL the carrier hosts it at, relayed unchanged.
|
|
651
|
+
sig { returns(T.nilable(String)) }
|
|
652
|
+
attr_accessor :url
|
|
653
|
+
|
|
654
|
+
# One attachment on a message, in either direction — and in both, a URL somebody
|
|
655
|
+
# else hosts.
|
|
656
|
+
#
|
|
657
|
+
# Outbound: the customer supplied a public URL and we handed it to the carrier.
|
|
658
|
+
# Inbound: the carrier hosts the file and we record where. sent.dm never holds the
|
|
659
|
+
# bytes, so there is no key, no expiry bookkeeping and nothing minted per read —
|
|
660
|
+
# what is stored is what is served.
|
|
661
|
+
#
|
|
662
|
+
# An inbound link expires on the carrier's own schedule and is unauthenticated.
|
|
663
|
+
# That is the customer's to manage, and it is documented where they will see it
|
|
664
|
+
# rather than only here — a recipient who needs an attachment to outlive that
|
|
665
|
+
# window copies it on receipt.
|
|
666
|
+
#
|
|
667
|
+
# Storing a presigned URL is the specific mistake this shape still avoids:
|
|
668
|
+
# M260826130000 and M260826140000 exist because RCS assets were stored as signed
|
|
669
|
+
# URLs and went stale. Nothing here is signed.
|
|
670
|
+
sig do
|
|
671
|
+
params(
|
|
672
|
+
media_type: T.nilable(String),
|
|
673
|
+
mime_type: T.nilable(String),
|
|
674
|
+
size_bytes: T.nilable(Integer),
|
|
675
|
+
source_hash_sha256: T.nilable(String),
|
|
676
|
+
url: T.nilable(String)
|
|
677
|
+
).returns(T.attached_class)
|
|
678
|
+
end
|
|
679
|
+
def self.new(
|
|
680
|
+
# One of MmsMediaTypes when the content type is known. Advisory — a reader should
|
|
681
|
+
# trust the fetched object's own Content-Type.
|
|
682
|
+
media_type: nil,
|
|
683
|
+
# Content type as the provider declared it. Null when it declared none.
|
|
684
|
+
mime_type: nil,
|
|
685
|
+
# Size as the provider declared it. Never measured here — nothing downloads the
|
|
686
|
+
# file.
|
|
687
|
+
size_bytes: nil,
|
|
688
|
+
# Inbound only: the SHA-256 the provider declared alongside the attachment, when
|
|
689
|
+
# it declared one. Relayed to the customer so they can verify what they fetch
|
|
690
|
+
# matches what the carrier said it sent. It is the only integrity signal available
|
|
691
|
+
# on an attachment nobody here has read.
|
|
692
|
+
source_hash_sha256: nil,
|
|
693
|
+
# Where the file lives. Outbound: the URL the customer gave us and the carrier
|
|
694
|
+
# fetched. Inbound: the URL the carrier hosts it at, relayed unchanged.
|
|
695
|
+
url: nil
|
|
696
|
+
)
|
|
697
|
+
end
|
|
698
|
+
|
|
699
|
+
sig do
|
|
700
|
+
override.returns(
|
|
701
|
+
{
|
|
702
|
+
media_type: T.nilable(String),
|
|
703
|
+
mime_type: T.nilable(String),
|
|
704
|
+
size_bytes: T.nilable(Integer),
|
|
705
|
+
source_hash_sha256: T.nilable(String),
|
|
706
|
+
url: T.nilable(String)
|
|
707
|
+
}
|
|
708
|
+
)
|
|
709
|
+
end
|
|
710
|
+
def to_hash
|
|
711
|
+
end
|
|
712
|
+
end
|
|
415
713
|
end
|
|
416
714
|
end
|
|
417
715
|
end
|