Skip to content

Commit 8bfe786

Browse files
authored
Separate triggered and blast campaigns (#13)
1 parent c70aaa6 commit 8bfe786

4 files changed

Lines changed: 246 additions & 67 deletions

File tree

src/client/campaigns.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ import {
66
ArchiveCampaignsResponseSchema,
77
CampaignMetricsResponse,
88
CancelCampaignParams,
9-
CreateCampaignParams,
9+
CreateAndScheduleCampaignParams,
1010
CreateCampaignResponse,
1111
CreateCampaignResponseSchema,
12+
CreateTriggeredCampaignParams,
1213
DeactivateTriggeredCampaignParams,
1314
GetCampaignMetricsParams,
1415
GetCampaignParams,
@@ -75,8 +76,15 @@ export function Campaigns<T extends Constructor<BaseIterableClient>>(Base: T) {
7576
return this.validateResponse(response, GetCampaignResponseSchema);
7677
}
7778

78-
async createCampaign(
79-
params: CreateCampaignParams
79+
async createAndScheduleCampaign(
80+
params: CreateAndScheduleCampaignParams
81+
): Promise<CreateCampaignResponse> {
82+
const response = await this.client.post("/api/campaigns/create", params);
83+
return this.validateResponse(response, CreateCampaignResponseSchema);
84+
}
85+
86+
async createTriggeredCampaign(
87+
params: CreateTriggeredCampaignParams
8088
): Promise<CreateCampaignResponse> {
8189
const response = await this.client.post("/api/campaigns/create", params);
8290
return this.validateResponse(response, CreateCampaignResponseSchema);

src/types/campaigns.ts

Lines changed: 61 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@ export type GetCampaignMetricsParams = z.infer<
117117
export type CampaignMetricsResponse = z.infer<
118118
typeof CampaignMetricsResponseSchema
119119
>;
120-
export type CreateCampaignParams = z.infer<typeof CreateCampaignParamsSchema>;
121120
export type CreateCampaignResponse = z.infer<
122121
typeof CreateCampaignResponseSchema
123122
>;
@@ -145,56 +144,68 @@ export const CampaignMetricsResponseSchema = z
145144
.array(z.record(z.string(), z.string()))
146145
.describe("Parsed campaign metrics data");
147146

148-
// Campaign creation schemas
149-
export const CreateCampaignParamsSchema = z
150-
.object({
151-
name: z
152-
.string()
153-
.describe("The name to use in Iterable for the new campaign"),
154-
templateId: z
155-
.number()
156-
.describe("The ID of a template to associate with the new campaign"),
157-
listIds: z
158-
.array(z.number())
159-
.describe(
160-
"Array of list IDs to which the campaign should be sent (for blast campaigns)"
161-
)
162-
.optional(),
163-
campaignDataFields: z
164-
.record(z.string(), z.any())
165-
.optional()
166-
.describe(
167-
"A JSON object containing campaign-level data fields that are available as merge parameters (for example, {{field}}) during message rendering. These fields are available in templates, data feed URLs, and all other contexts where merge parameters are supported. Campaign-level fields are overridden by user and event data fields of the same name."
168-
),
169-
sendAt: IterableDateTimeSchema.optional().describe(
170-
"Scheduled send time for blast campaign (YYYY-MM-DD HH:MM:SS UTC). Required when listIds is provided."
147+
// Shared campaign data fields used by both blast and triggered campaign creation
148+
const campaignDataFieldsSchema = z
149+
.record(z.string(), z.any())
150+
.optional()
151+
.describe(
152+
"A JSON object containing campaign-level data fields that are available as merge parameters (for example, {{field}}) during message rendering. These fields are available in templates, data feed URLs, and all other contexts where merge parameters are supported. Campaign-level fields are overridden by user and event data fields of the same name."
153+
);
154+
155+
// Create and schedule a blast campaign
156+
export const CreateAndScheduleCampaignParamsSchema = z.object({
157+
name: z
158+
.string()
159+
.describe("The name to use in Iterable for the new campaign"),
160+
templateId: z
161+
.number()
162+
.describe("The ID of a template to associate with the new campaign"),
163+
listIds: z
164+
.array(z.number())
165+
.min(1)
166+
.describe("Array of list IDs to which the campaign should be sent"),
167+
sendAt: IterableDateTimeSchema.describe(
168+
"Scheduled send time (YYYY-MM-DD HH:MM:SS UTC)"
169+
),
170+
campaignDataFields: campaignDataFieldsSchema,
171+
sendMode: z
172+
.enum(["ProjectTimeZone", "RecipientTimeZone"])
173+
.optional()
174+
.describe("Send mode for blast campaigns"),
175+
startTimeZone: z
176+
.string()
177+
.optional()
178+
.describe("Starting timezone for recipient timezone sends (IANA format)"),
179+
defaultTimeZone: z
180+
.string()
181+
.optional()
182+
.describe(
183+
"Default timezone for recipients without known timezone (IANA format)"
171184
),
172-
sendMode: z
173-
.enum(["ProjectTimeZone", "RecipientTimeZone"])
174-
.optional()
175-
.describe("Send mode for blast campaigns"),
176-
startTimeZone: z
177-
.string()
178-
.optional()
179-
.describe(
180-
"Starting timezone for recipient timezone sends (IANA format)"
181-
),
182-
defaultTimeZone: z
183-
.string()
184-
.optional()
185-
.describe(
186-
"Default timezone for recipients without known timezone (IANA format)"
187-
),
188-
suppressionListIds: z
189-
.array(z.number())
190-
.optional()
191-
.describe("Array of suppression list IDs"),
192-
})
193-
.refine((data) => !data.listIds?.length || data.sendAt, {
194-
message:
195-
"sendAt is required for blast campaigns (when listIds is provided).",
196-
path: ["sendAt"],
197-
});
185+
suppressionListIds: z
186+
.array(z.number())
187+
.optional()
188+
.describe("Array of suppression list IDs"),
189+
});
190+
191+
export type CreateAndScheduleCampaignParams = z.infer<
192+
typeof CreateAndScheduleCampaignParamsSchema
193+
>;
194+
195+
// Create a triggered campaign
196+
export const CreateTriggeredCampaignParamsSchema = z.object({
197+
name: z
198+
.string()
199+
.describe("The name to use in Iterable for the new campaign"),
200+
templateId: z
201+
.number()
202+
.describe("The ID of a template to associate with the new campaign"),
203+
campaignDataFields: campaignDataFieldsSchema,
204+
});
205+
206+
export type CreateTriggeredCampaignParams = z.infer<
207+
typeof CreateTriggeredCampaignParamsSchema
208+
>;
198209

199210
export const CreateCampaignResponseSchema = z.object({
200211
campaignId: z.number(),

tests/integration/campaigns.test.ts

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,26 @@ describe("Campaign Management Integration Tests", () => {
2828
return parseInt(templateIdMatch[1]);
2929
};
3030

31-
const createTestCampaign = async (params: {
31+
const createTestBlastCampaign = async (params: {
3232
name: string;
3333
templateId: number;
34-
listIds?: number[];
35-
sendAt?: string;
34+
listIds: number[];
35+
sendAt: string;
3636
}) => {
3737
const createResponse = await retryRateLimited(
38-
() => withTimeout(client.createCampaign(params)),
39-
`Create campaign: ${params.name}`
38+
() => withTimeout(client.createAndScheduleCampaign(params)),
39+
`Create blast campaign: ${params.name}`
40+
);
41+
return createResponse.campaignId;
42+
};
43+
44+
const createTestTriggeredCampaign = async (params: {
45+
name: string;
46+
templateId: number;
47+
}) => {
48+
const createResponse = await retryRateLimited(
49+
() => withTimeout(client.createTriggeredCampaign(params)),
50+
`Create triggered campaign: ${params.name}`
4051
);
4152
return createResponse.campaignId;
4253
};
@@ -464,7 +475,7 @@ describe("Campaign Management Integration Tests", () => {
464475

465476
it("should create and archive a test campaign", async () => {
466477
const campaignName = uniqueId("MCP-Test-Campaign");
467-
const campaignId = await createTestCampaign({
478+
const campaignId = await createTestTriggeredCampaign({
468479
name: campaignName,
469480
templateId: testTemplateId,
470481
});
@@ -507,7 +518,7 @@ describe("Campaign Management Integration Tests", () => {
507518
const sendAtDate = new Date(Date.now() + 24 * 60 * 60 * 1000);
508519
const sendAt = sendAtDate.toISOString().replace('T', ' ').substring(0, 19);
509520

510-
const campaignId = await createTestCampaign({
521+
const campaignId = await createTestBlastCampaign({
511522
name: campaignName,
512523
templateId: testTemplateId,
513524
listIds: [testListId],
@@ -546,12 +557,12 @@ describe("Campaign Management Integration Tests", () => {
546557
}, 60000);
547558

548559
it("should archive multiple campaigns at once", async () => {
549-
const campaignId1 = await createTestCampaign({
560+
const campaignId1 = await createTestTriggeredCampaign({
550561
name: uniqueId("MCP-Test-Bulk-1"),
551562
templateId: testTemplateId,
552563
});
553564

554-
const campaignId2 = await createTestCampaign({
565+
const campaignId2 = await createTestTriggeredCampaign({
555566
name: uniqueId("MCP-Test-Bulk-2"),
556567
templateId: testTemplateId,
557568
});
@@ -588,7 +599,7 @@ describe("Campaign Management Integration Tests", () => {
588599
}, 60000);
589600

590601
it("should abort a campaign", async () => {
591-
const campaignId = await createTestCampaign({
602+
const campaignId = await createTestTriggeredCampaign({
592603
name: uniqueId("MCP-Test-Abort"),
593604
templateId: testTemplateId,
594605
});
@@ -611,8 +622,7 @@ describe("Campaign Management Integration Tests", () => {
611622
}, 60000);
612623

613624
it("should activate and deactivate a triggered campaign", async () => {
614-
// No listIds = Triggered campaign
615-
const campaignId = await createTestCampaign({
625+
const campaignId = await createTestTriggeredCampaign({
616626
name: uniqueId("MCP-Test-Triggered"),
617627
templateId: testTemplateId,
618628
});
@@ -659,7 +669,7 @@ describe("Campaign Management Integration Tests", () => {
659669
}, 60000);
660670

661671
it("should trigger a campaign", async () => {
662-
const campaignId = await createTestCampaign({
672+
const campaignId = await createTestTriggeredCampaign({
663673
name: uniqueId("MCP-Test-Trigger"),
664674
templateId: testTemplateId,
665675
});
@@ -696,7 +706,7 @@ describe("Campaign Management Integration Tests", () => {
696706
const sendAtDate = new Date(Date.now() + 24 * 60 * 60 * 1000);
697707
const sendAt = sendAtDate.toISOString().replace('T', ' ').substring(0, 19);
698708

699-
const campaignId = await createTestCampaign({
709+
const campaignId = await createTestBlastCampaign({
700710
name: uniqueId("MCP-Test-Send"),
701711
templateId: testTemplateId,
702712
listIds: [testListId],

0 commit comments

Comments
 (0)