Skip to content

Commit 2e53624

Browse files
joe-ayoub-segmentkc-ong-taguchi
authored andcommitted
STRATCONN-6008 - [Twilio Messaging] - WIP tags support (#3101)
* [Twilio Messaging] - WIP tags support * adding some more validation * regen key * saving test status * tested and ready to deploy * removing test mappings * cleaning up linting issue * More linting issues
1 parent 903026a commit 2e53624

File tree

5 files changed

+125
-3
lines changed

5 files changed

+125
-3
lines changed

packages/destination-actions/src/destinations/twilio-messaging/sendMessage/__tests__/index.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,59 @@ describe('TwilioMessaging.sendMessage', () => {
3838
})
3939
})
4040

41+
it('should send messsage with tags', async () => {
42+
const body = "To=%2B1234567890&From=%2B19876543210&Body=Hello+World%21&Tags=%7B%22campaign_name%22%3A%22Spring+Sale+2022%22%2C%22message_type%22%3A%22cart_abandoned%22%2C%22number_tag%22%3A%2212345%22%2C%22boolean_tag%22%3A%22true%22%7D"
43+
nock('https://api.twilio.com')
44+
.post(`/2010-04-01/Accounts/${defaultSettings.accountSID}/Messages.json`, body)
45+
.reply(200, {
46+
sid: 'SM1234567890abcdef1234567890abcdef',
47+
status: 'sent'
48+
})
49+
50+
await testDestination.testAction('sendMessage', {
51+
settings: defaultSettings,
52+
mapping: {
53+
channel: CHANNELS.SMS,
54+
senderType: SENDER_TYPE.PHONE_NUMBER,
55+
toPhoneNumber: '+1234567890',
56+
fromPhoneNumber: '+19876543210',
57+
contentTemplateType: 'Inline',
58+
inlineBody: 'Hello World!',
59+
tags: {
60+
campaign_name: 'Spring Sale 2022',
61+
message_type: 'cart_abandoned',
62+
number_tag: 12345,
63+
boolean_tag: true,
64+
null_tag: null,
65+
empty_string_tag: ''
66+
}
67+
}
68+
})
69+
})
70+
71+
it('should thow error if tags malformed', async () => {
72+
await expect( testDestination.testAction('sendMessage', {
73+
settings: defaultSettings,
74+
mapping: {
75+
channel: CHANNELS.SMS,
76+
senderType: SENDER_TYPE.PHONE_NUMBER,
77+
toPhoneNumber: '+1234567890',
78+
fromPhoneNumber: '+19876543210',
79+
contentTemplateType: 'Inline',
80+
inlineBody: 'Hello World!',
81+
tags: {
82+
campaign_name: 'Spring Sale 2022',
83+
message_type: 'cart_abandoned',
84+
number_tag: 12345,
85+
boolean_tag: true,
86+
null_tag: null,
87+
empty_string_tag: '',
88+
super_bad_tag: "$%^&*&^%$"
89+
}
90+
}
91+
})).rejects.toThrow("Tag value \"$%^&*&^%$\" for key \"super_bad_tag\" contains invalid characters. Only alphanumeric, space, hyphen (-), and underscore (_) are allowed.")
92+
})
93+
4194
it('should send MMS with messaging service', async () => {
4295
nock('https://api.twilio.com').post(`/2010-04-01/Accounts/${defaultSettings.accountSID}/Messages.json`).reply(200, {
4396
sid: 'SM1234567890abcdef1234567890abcdef',

packages/destination-actions/src/destinations/twilio-messaging/sendMessage/fields.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export const fields: Record<string, InputField> = {
1010
choices: [
1111
{ label: 'SMS', value: CHANNELS.SMS },
1212
{ label: 'MMS', value: CHANNELS.MMS },
13-
{ label: 'WhatsApp', value: CHANNELS.WHATSAPP },
13+
{ label: 'WhatsApp', value: CHANNELS.WHATSAPP }
1414
//{ label: 'RCS', value: CHANNELS.RCS } Will be hidden for private beta
1515
]
1616
},
@@ -213,5 +213,12 @@ export const fields: Record<string, InputField> = {
213213
format: 'date-time',
214214
required: false,
215215
default: undefined
216+
},
217+
tags: {
218+
label: 'Tags',
219+
description: 'Custom tags to be included in the message. Key:value pairs of strings are allowed.',
220+
type: 'object',
221+
required: false,
222+
defaultObjectUI: 'keyvalue'
216223
}
217224
}

packages/destination-actions/src/destinations/twilio-messaging/sendMessage/generated-types.ts

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/destination-actions/src/destinations/twilio-messaging/sendMessage/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export type TwilioPayload = {
55
SendAt?: string
66
ValidityPeriod?: number
77
MediaUrl?: string[]
8+
Tags?: string
89
} & Sender &
910
Content
1011

packages/destination-actions/src/destinations/twilio-messaging/sendMessage/utils.ts

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ export async function send(request: RequestClient, payload: Payload, settings: S
2626
sendAt,
2727
inlineMediaUrls,
2828
inlineBody,
29-
contentTemplateType
29+
contentTemplateType,
30+
tags
3031
} = payload
3132

3233
const getTo = (): string => {
@@ -146,13 +147,66 @@ export async function send(request: RequestClient, payload: Payload, settings: S
146147
return urls.length > 0 ? { MediaUrl: urls } : {}
147148
}
148149

150+
const getTags = (): { [k: string]: string } | undefined => {
151+
if (!tags || typeof tags !== 'object') return undefined
152+
153+
const allowedPattern = /^[a-zA-Z0-9 _-]+$/
154+
155+
for (const k in tags) {
156+
const v = tags[k]
157+
158+
if (v === null || String(v).trim() === '') {
159+
delete tags[k]
160+
continue
161+
}
162+
163+
if (typeof v === 'object') {
164+
throw new PayloadValidationError(`Tag value for key "${k}" cannot be an object or array.`)
165+
}
166+
167+
if (k.length > 128) {
168+
throw new PayloadValidationError(`Tag key "${k}" exceeds the maximum tag key length of 128 characters.`)
169+
}
170+
171+
const trimmedValue = String(v).trim()
172+
173+
// Validate allowed characters in key and value
174+
if (!allowedPattern.test(k)) {
175+
throw new PayloadValidationError(
176+
`Tag key "${k}" contains invalid characters. Only alphanumeric, space, hyphen (-), and underscore (_) are allowed.`
177+
)
178+
}
179+
180+
if (!allowedPattern.test(trimmedValue)) {
181+
throw new PayloadValidationError(
182+
`Tag value "${trimmedValue}" for key "${k}" contains invalid characters. Only alphanumeric, space, hyphen (-), and underscore (_) are allowed.`
183+
)
184+
}
185+
186+
if (trimmedValue.length > 128) {
187+
throw new PayloadValidationError(
188+
`Tag value for key "${k}" exceeds the maximum tag value length of 128 characters.`
189+
)
190+
}
191+
192+
tags[k] = trimmedValue
193+
}
194+
195+
if (Object.keys(tags as { [k: string]: string }).length > 10) {
196+
throw new PayloadValidationError('Tags cannot contain more than 10 key-value pairs.')
197+
}
198+
199+
return Object.keys(tags as { [k: string]: string }).length > 0 ? { Tags: JSON.stringify(tags) } : undefined
200+
}
201+
149202
const twilioPayload: TwilioPayload = (() => ({
150203
To: getTo(),
151204
...getSendAt(),
152205
...getValidityPeriod(),
153206
...getSender(),
154207
...getContent(),
155-
...getInlineMediaUrls()
208+
...getInlineMediaUrls(),
209+
...getTags()
156210
}))()
157211

158212
const encodedBody = encode(twilioPayload)
@@ -186,6 +240,7 @@ function encode(twilioPayload: TwilioPayload): string {
186240

187241
return encodedSmsBody.toString()
188242
}
243+
189244
export function validateContentSid(contentSid: string) {
190245
return /^HX[0-9a-fA-F]{32}$/.test(contentSid)
191246
}

0 commit comments

Comments
 (0)