From b38ab23e876f069d71d96a80f6d09aac0e874da5 Mon Sep 17 00:00:00 2001 From: Greg Methvin Date: Tue, 17 Mar 2026 16:32:52 -0700 Subject: [PATCH] Require blast campaigns to be scheduled separately --- src/client/campaigns.ts | 5 ++++- src/types/campaigns.ts | 25 +----------------------- tests/integration/campaigns.test.ts | 30 ++++++++++++++++++----------- tests/unit/campaigns.test.ts | 21 +------------------- 4 files changed, 25 insertions(+), 56 deletions(-) diff --git a/src/client/campaigns.ts b/src/client/campaigns.ts index b145a29..59b7160 100644 --- a/src/client/campaigns.ts +++ b/src/client/campaigns.ts @@ -79,7 +79,10 @@ export function Campaigns>(Base: T) { async createBlastCampaign( params: CreateBlastCampaignParams ): Promise { - const response = await this.client.post("/api/campaigns/create", params); + const response = await this.client.post("/api/campaigns/create", { + ...params, + scheduleSend: false, + }); return this.validateResponse(response, CreateCampaignResponseSchema); } diff --git a/src/types/campaigns.ts b/src/types/campaigns.ts index 3cf9a33..527f0f4 100644 --- a/src/types/campaigns.ts +++ b/src/types/campaigns.ts @@ -164,37 +164,14 @@ export const CreateBlastCampaignParamsSchema = z.object({ .array(z.number()) .min(1) .describe("Array of list IDs to which the campaign should be sent"), - scheduleSend: z - .boolean() - .default(false) - .describe( - "Whether to immediately schedule the blast campaign for sending. Set to true to schedule the campaign on creation. When false, the campaign can be scheduled later using POST /api/campaigns/{campaignId}/schedule." - ), - sendAt: IterableDateTimeSchema.describe( - "Scheduled send time (YYYY-MM-DD HH:MM:SS UTC)" - ), campaignDataFields: campaignDataFieldsSchema, - sendMode: z - .enum(["ProjectTimeZone", "RecipientTimeZone"]) - .optional() - .describe("Send mode for blast campaigns"), - startTimeZone: z - .string() - .optional() - .describe("Starting timezone for recipient timezone sends (IANA format)"), - defaultTimeZone: z - .string() - .optional() - .describe( - "Default timezone for recipients without known timezone (IANA format)" - ), suppressionListIds: z .array(z.number()) .optional() .describe("Array of suppression list IDs"), }); -export type CreateBlastCampaignParams = z.input< +export type CreateBlastCampaignParams = z.infer< typeof CreateBlastCampaignParamsSchema >; diff --git a/tests/integration/campaigns.test.ts b/tests/integration/campaigns.test.ts index 2778137..69fafce 100644 --- a/tests/integration/campaigns.test.ts +++ b/tests/integration/campaigns.test.ts @@ -32,7 +32,6 @@ describe("Campaign Management Integration Tests", () => { name: string; templateId: number; listIds: number[]; - sendAt: string; }) => { const createResponse = await retryRateLimited( () => withTimeout(client.createBlastCampaign(params)), @@ -514,21 +513,25 @@ describe("Campaign Management Integration Tests", () => { it("should create, schedule, and cancel a blast campaign", async () => { const campaignName = uniqueId("MCP-Test-Schedule"); - // Schedule for 24 hours in the future (YYYY-MM-DD HH:MM:SS format) - const sendAtDate = new Date(Date.now() + 24 * 60 * 60 * 1000); - const sendAt = sendAtDate.toISOString().replace('T', ' ').substring(0, 19); - const campaignId = await createTestBlastCampaign({ name: campaignName, templateId: testTemplateId, listIds: [testListId], - sendAt, }); try { + // Schedule for 24 hours in the future (ISO-8601 format) + const sendAtDate = new Date(Date.now() + 24 * 60 * 60 * 1000); + const sendAt = sendAtDate.toISOString(); + + await performCampaignAction( + () => withTimeout(client.scheduleCampaign({ campaignId, sendAt })), + "Schedule blast campaign" + ); + const campaign = await retryRateLimited( () => withTimeout(client.getCampaign({ id: campaignId })), - "Get created blast campaign" + "Get scheduled blast campaign" ); expect(campaign.type).toBe("Blast"); @@ -703,17 +706,22 @@ describe("Campaign Management Integration Tests", () => { }, 60000); it("should send a scheduled campaign immediately", async () => { - const sendAtDate = new Date(Date.now() + 24 * 60 * 60 * 1000); - const sendAt = sendAtDate.toISOString().replace('T', ' ').substring(0, 19); - const campaignId = await createTestBlastCampaign({ name: uniqueId("MCP-Test-Send"), templateId: testTemplateId, listIds: [testListId], - sendAt, }); try { + // Schedule for 24 hours in the future, then send immediately + const sendAtDate = new Date(Date.now() + 24 * 60 * 60 * 1000); + const sendAt = sendAtDate.toISOString(); + + await performCampaignAction( + () => withTimeout(client.scheduleCampaign({ campaignId, sendAt })), + "Schedule campaign for later" + ); + const campaign = await retryRateLimited( () => withTimeout(client.getCampaign({ id: campaignId })), "Get scheduled campaign" diff --git a/tests/unit/campaigns.test.ts b/tests/unit/campaigns.test.ts index a272111..130f66f 100644 --- a/tests/unit/campaigns.test.ts +++ b/tests/unit/campaigns.test.ts @@ -788,7 +788,6 @@ describe("Campaign Management", () => { name: "Test Campaign", templateId: 123, listIds: [456], - sendAt: "2026-03-01 10:00:00", }) ).not.toThrow(); @@ -798,30 +797,16 @@ describe("Campaign Management", () => { name: "Test Campaign", templateId: 123, listIds: [456, 789], - sendAt: "2026-03-01 10:00:00", - sendMode: "RecipientTimeZone", - startTimeZone: "America/New_York", - defaultTimeZone: "America/Los_Angeles", suppressionListIds: [100], campaignDataFields: { key: "value" }, }) ).not.toThrow(); - // Missing sendAt - expect(() => - CreateBlastCampaignParamsSchema.parse({ - name: "Test Campaign", - templateId: 123, - listIds: [456], - }) - ).toThrow(); - // Missing listIds expect(() => CreateBlastCampaignParamsSchema.parse({ name: "Test Campaign", templateId: 123, - sendAt: "2026-03-01 10:00:00", }) ).toThrow(); @@ -831,7 +816,6 @@ describe("Campaign Management", () => { name: "Test Campaign", templateId: 123, listIds: [], - sendAt: "2026-03-01 10:00:00", }) ).toThrow(); @@ -840,7 +824,6 @@ describe("Campaign Management", () => { CreateBlastCampaignParamsSchema.parse({ templateId: 123, listIds: [456], - sendAt: "2026-03-01 10:00:00", }) ).toThrow(); @@ -849,7 +832,6 @@ describe("Campaign Management", () => { CreateBlastCampaignParamsSchema.parse({ name: "Test Campaign", listIds: [456], - sendAt: "2026-03-01 10:00:00", }) ).toThrow(); }); @@ -897,14 +879,13 @@ describe("Campaign Management", () => { name: "Test Blast", templateId: 100, listIds: [200], - sendAt: "2026-03-01 10:00:00", }; const result = await client.createBlastCampaign(params); expect(mockAxiosInstance.post).toHaveBeenCalledWith( "/api/campaigns/create", - params + { ...params, scheduleSend: false } ); expect(result).toEqual({ campaignId: 12345 }); });