Skip to content

Commit 54cb9fa

Browse files
authored
add missing fields in template content schemas (#24)
1 parent f1983d8 commit 54cb9fa

File tree

3 files changed

+264
-118
lines changed

3 files changed

+264
-118
lines changed

src/types/templates.ts

Lines changed: 68 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -42,92 +42,90 @@ const BaseTemplateSchema = z.object({
4242
updatedAt: UnixTimestampSchema.optional().describe("Date last updated"),
4343
});
4444

45-
export const EmailTemplateSchema = BaseTemplateSchema.extend({
45+
const campaignDataFieldsSchema = z
46+
.record(z.string(), z.any())
47+
.optional()
48+
.describe(
49+
"Campaign-level data fields available as {{field}} merge parameters during message rendering. These fields are overridden by user and event data fields of the same name."
50+
);
51+
52+
// Content fields for each template type, shared between response and param schemas
53+
const EmailContentFields = {
54+
subject: z.string().optional().describe("Subject"),
55+
preheaderText: z.string().optional().describe("Preheader text"),
56+
fromName: z.string().optional().describe("From name"),
57+
fromEmail: z
58+
.string()
59+
.optional()
60+
.describe("From email (must be an authorized sender)"),
61+
replyToEmail: z.string().optional().describe("Reply to email"),
62+
ccEmails: z.array(z.string()).optional().describe("CC emails"),
4663
bccEmails: z.array(z.string()).optional().describe("BCC emails"),
64+
html: z.string().optional().describe("HTML contents"),
65+
plainText: z.string().optional().describe("Plain text contents"),
4766
cacheDataFeed: z
4867
.boolean()
4968
.optional()
5069
.describe("Cache data feed lookups for 1 hour"),
51-
campaignDataFields: z
52-
.record(z.string(), z.any())
70+
dataFeedIds: z
71+
.array(z.number())
5372
.optional()
54-
.describe(
55-
"Campaign-level data fields available as {{field}} merge parameters during message rendering. These fields are overridden by user and event data fields of the same name."
56-
),
57-
ccEmails: z.array(z.string()).optional().describe("CC emails"),
73+
.describe("Ids for data feeds used in template rendering"),
5874
dataFeedId: z
5975
.number()
6076
.optional()
6177
.describe(
62-
"[Deprecated - use dataFeedIds instead] Id for data feed used in template rendering"
78+
"[Deprecated - use dataFeedIds instead] ID for data feed used in template rendering"
6379
),
64-
dataFeedIds: z
65-
.array(z.number())
66-
.optional()
67-
.describe("Ids for data feeds used in template rendering"),
68-
fromEmail: z
69-
.string()
80+
mergeDataFeedContext: z
81+
.boolean()
7082
.optional()
71-
.describe("From email (must be an authorized sender)"),
72-
fromName: z.string().optional().describe("From name"),
83+
.describe(
84+
"Merge data feed contents into user context, so fields can be referenced by {{field}} instead of [[field]]"
85+
),
7386
googleAnalyticsCampaignName: z
7487
.string()
7588
.optional()
7689
.describe("Google analytics utm_campaign value"),
77-
html: z.string().optional().describe("HTML contents"),
7890
linkParams: z
7991
.array(z.any())
8092
.optional()
8193
.describe("Parameters to append to each URL in html contents"),
82-
mergeDataFeedContext: z
83-
.boolean()
84-
.optional()
85-
.describe(
86-
"Merge data feed contents into user context, so fields be referenced by {{field}} instead of [[field]]"
87-
),
88-
metadata: z.any().optional().describe("Metadata"),
89-
plainText: z.string().optional().describe("Plain text contents"),
90-
preheaderText: z.string().optional().describe("Preheader text"),
91-
replyToEmail: z.string().optional().describe("Reply to email"),
92-
subject: z.string().optional().describe("Subject"),
93-
});
94+
campaignDataFields: campaignDataFieldsSchema,
95+
};
9496

95-
export const SMSTemplateSchema = BaseTemplateSchema.extend({
96-
campaignDataFields: z
97-
.record(z.string(), z.any())
98-
.optional()
99-
.describe(
100-
"Campaign-level data fields available as {{field}} merge parameters during message rendering. These fields are overridden by user and event data fields of the same name."
101-
),
97+
const SMSContentFields = {
98+
message: z.string().optional().describe("SMS message"),
99+
imageUrl: z.string().optional().describe("Image URL"),
102100
googleAnalyticsCampaignName: z
103101
.string()
104102
.optional()
105103
.describe("Google analytics utm_campaign value"),
106-
imageUrl: z.string().optional().describe("Image Url"),
107104
linkParams: z
108105
.array(z.any())
109106
.optional()
110107
.describe("Parameters to append to each URL in contents"),
111-
message: z.string().optional().describe("SMS message"),
112108
trackingDomain: z.string().optional().describe("Tracking Domain"),
113-
});
109+
campaignDataFields: campaignDataFieldsSchema,
110+
};
114111

115-
export const PushTemplateSchema = BaseTemplateSchema.extend({
112+
const PushContentFields = {
113+
message: z.string().optional().describe("Push message"),
114+
title: z.string().optional().describe("Push message title"),
116115
badge: z.string().optional().describe("Badge to set for push notification"),
117116
buttons: z
118117
.array(z.any())
119118
.optional()
120119
.describe("Array of buttons that appear to respond to the push. Max of 3"),
120+
sound: z.string().optional().describe("Sound"),
121+
payload: z
122+
.record(z.string(), z.any())
123+
.optional()
124+
.describe("Payload to send with push notification"),
121125
cacheDataFeed: z
122126
.boolean()
123127
.optional()
124128
.describe("Cache data feed lookups for 1 hour"),
125-
campaignDataFields: z
126-
.record(z.string(), z.any())
127-
.optional()
128-
.describe(
129-
"Campaign-level data fields available as {{field}} merge parameters during message rendering. These fields are overridden by user and event data fields of the same name."
130-
),
131129
dataFeedIds: z
132130
.array(z.number())
133131
.optional()
@@ -139,7 +137,7 @@ export const PushTemplateSchema = BaseTemplateSchema.extend({
139137
"Deep Link. A mapping that accepts two optional properties: 'ios' & 'android' and their respective deep link values"
140138
),
141139
interruptionLevel: z
142-
.string()
140+
.enum(["passive", "active", "time-sensitive", "critical"])
143141
.optional()
144142
.describe(
145143
"An interruption level helps iOS determine when to alert a user about the arrival of a push notification"
@@ -154,11 +152,6 @@ export const PushTemplateSchema = BaseTemplateSchema.extend({
154152
.describe(
155153
"Merge data feed contents into user context, so fields can be referenced by {{field}} instead of [[field]]"
156154
),
157-
message: z.string().optional().describe("Push message"),
158-
payload: z
159-
.record(z.string(), z.any())
160-
.optional()
161-
.describe("Payload to send with push notification"),
162155
relevanceScore: z
163156
.number()
164157
.optional()
@@ -171,23 +164,17 @@ export const PushTemplateSchema = BaseTemplateSchema.extend({
171164
.describe(
172165
"Rich Media URL. A mapping that accepts two optional properties: 'ios' & 'android' and their respective rich media url values"
173166
),
174-
sound: z.string().optional().describe("Sound"),
175-
title: z.string().optional().describe("Push message title"),
176167
wake: z
177168
.boolean()
178169
.optional()
179170
.describe(
180171
"Set the content-available flag on iOS notifications, which will wake the app in the background"
181172
),
182-
});
173+
campaignDataFields: campaignDataFieldsSchema,
174+
};
183175

184-
export const InAppTemplateSchema = BaseTemplateSchema.extend({
185-
campaignDataFields: z
186-
.record(z.string(), z.any())
187-
.optional()
188-
.describe(
189-
"Campaign-level data fields available as {{field}} merge parameters during message rendering. These fields are overridden by user and event data fields of the same name."
190-
),
176+
const InAppContentFields = {
177+
html: z.string().optional().describe("Html of the in-app notification"),
191178
expirationDateTime: z
192179
.string()
193180
.optional()
@@ -200,7 +187,6 @@ export const InAppTemplateSchema = BaseTemplateSchema.extend({
200187
.describe(
201188
"The in-app message's expiration time, relative to its send time. Should be an expression such as now+90d"
202189
),
203-
html: z.string().optional().describe("Html of the in-app notification"),
204190
inAppDisplaySettings: z
205191
.record(z.string(), z.any())
206192
.optional()
@@ -214,8 +200,21 @@ export const InAppTemplateSchema = BaseTemplateSchema.extend({
214200
.record(z.string(), z.any())
215201
.optional()
216202
.describe("Web In-app Display settings"),
203+
campaignDataFields: campaignDataFieldsSchema,
204+
};
205+
206+
export const EmailTemplateSchema = BaseTemplateSchema.extend({
207+
...EmailContentFields,
208+
metadata: z.any().optional().describe("Metadata"),
217209
});
218210

211+
export const SMSTemplateSchema = BaseTemplateSchema.extend(SMSContentFields);
212+
213+
export const PushTemplateSchema = BaseTemplateSchema.extend(PushContentFields);
214+
215+
export const InAppTemplateSchema =
216+
BaseTemplateSchema.extend(InAppContentFields);
217+
219218
export type EmailTemplate = z.infer<typeof EmailTemplateSchema>;
220219
export type SMSTemplate = z.infer<typeof SMSTemplateSchema>;
221220
export type PushTemplate = z.infer<typeof PushTemplateSchema>;
@@ -348,6 +347,12 @@ export type GetTemplateByClientIdResponse = z.infer<
348347
const BaseTemplateParamsSchema = z.object({
349348
name: z.string().optional().describe("Template name"),
350349
locale: z.string().optional().describe("Template locale"),
350+
isDefaultLocale: z
351+
.boolean()
352+
.optional()
353+
.describe(
354+
"Sets the locale associated with the request content as the template's default"
355+
),
351356
messageTypeId: z.number().optional().describe("Message type ID"),
352357
creatorUserId: z.string().optional().describe("Creator user ID"),
353358
campaignId: z.number().optional().describe("Associated campaign ID"),
@@ -363,58 +368,6 @@ const UpdateTemplateParamsSchema = BaseTemplateParamsSchema.extend({
363368
templateId: z.number().describe("Template ID to update"),
364369
});
365370

366-
// Content field objects for each template type
367-
const EmailContentFields = {
368-
subject: z.string().optional().describe("Email subject"),
369-
fromName: z.string().optional().describe("From name"),
370-
fromEmail: z.email().optional().describe("From email"),
371-
html: z.string().optional().describe("HTML content"),
372-
plainText: z.string().optional().describe("Plain text content"),
373-
campaignDataFields: z
374-
.record(z.string(), z.any())
375-
.optional()
376-
.describe(
377-
"Campaign-level data fields available as {{field}} merge parameters during message rendering. These fields are overridden by user and event data fields of the same name."
378-
),
379-
};
380-
381-
const SMSContentFields = {
382-
message: z.string().optional().describe("SMS message content"),
383-
campaignDataFields: z
384-
.record(z.string(), z.any())
385-
.optional()
386-
.describe(
387-
"Campaign-level data fields available as {{field}} merge parameters during message rendering. These fields are overridden by user and event data fields of the same name."
388-
),
389-
};
390-
391-
const PushContentFields = {
392-
message: z.string().optional().describe("Push notification message"),
393-
title: z.string().optional().describe("Push notification title"),
394-
badge: z.number().optional().describe("Badge count"),
395-
sound: z.string().optional().describe("Sound file"),
396-
payload: z.record(z.string(), z.any()).optional().describe("Custom payload"),
397-
campaignDataFields: z
398-
.record(z.string(), z.any())
399-
.optional()
400-
.describe(
401-
"Campaign-level data fields available as {{field}} merge parameters during message rendering. These fields are overridden by user and event data fields of the same name."
402-
),
403-
};
404-
405-
const InAppContentFields = {
406-
html: z
407-
.string()
408-
.optional()
409-
.describe("HTML content of the in-app notification"),
410-
campaignDataFields: z
411-
.record(z.string(), z.any())
412-
.optional()
413-
.describe(
414-
"Campaign-level data fields available as {{field}} merge parameters during message rendering. These fields are overridden by user and event data fields of the same name."
415-
),
416-
};
417-
418371
// Email template upsert (create or update by clientTemplateId)
419372
export const UpsertEmailTemplateParamsSchema =
420373
UpsertTemplateParamsSchema.extend(EmailContentFields);

tests/integration/templates.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ describe("Template Management Integration Tests", () => {
3333
name: uniqueId("Test-Email-Template"),
3434
clientTemplateId: uniqueId("test-email-template"),
3535
subject: "Integration Test Email",
36+
preheaderText: "Integration test preheader",
3637
fromName: "Alex Newman",
3738
fromEmail: "alex.newman@iterable.com",
3839
html: "<html><body><h1>Test Email</h1><p>Hello {{firstName}}!</p><p><a href='{{unsubscribeUrl}}'>Unsubscribe</a></p></body></html>",
@@ -200,7 +201,14 @@ describe("Template Management Integration Tests", () => {
200201
templateId,
201202
name: `${originalData.name} (Updated)`,
202203
subject: `${originalData.subject} (Updated)`,
204+
preheaderText: "Updated preheader",
203205
}),
206+
verifyGet: (template: any, originalData: any) => {
207+
expect(template.preheaderText).toBe(originalData.preheaderText);
208+
},
209+
verifyUpdate: (template: any) => {
210+
expect(template.preheaderText).toBe("Updated preheader");
211+
},
204212
proofData: (templateId: number, recipientEmail: string) => ({
205213
templateId,
206214
recipientEmail,
@@ -260,7 +268,15 @@ describe("Template Management Integration Tests", () => {
260268
createData,
261269
updateData,
262270
proofData,
271+
...rest
263272
}) => {
273+
const verifyGet = (rest as any).verifyGet as
274+
| ((template: any, originalData: any) => void)
275+
| undefined;
276+
const verifyUpdate = (rest as any).verifyUpdate as
277+
| ((template: any) => void)
278+
| undefined;
279+
264280
describe(`${type} Templates`, () => {
265281
it(`should create, get, update, and delete ${type.toLowerCase()} template`, async () => {
266282
const templateData = createData();
@@ -274,6 +290,7 @@ describe("Template Management Integration Tests", () => {
274290

275291
const getResponse = await waitForTemplate(templateId, getMethod);
276292
expect(getResponse.templateId).toBe(templateId);
293+
verifyGet?.(getResponse, templateData);
277294

278295
const updateParams = updateData(templateId, templateData);
279296
await withTimeout((client as any)[updateMethod](updateParams));
@@ -283,6 +300,7 @@ describe("Template Management Integration Tests", () => {
283300
getMethod
284301
);
285302
expect(updatedTemplate.name).toBe(updateParams.name);
303+
verifyUpdate?.(updatedTemplate);
286304

287305
const deleteResponse = await withTimeout(
288306
client.deleteTemplates({ ids: [templateId] })

0 commit comments

Comments
 (0)