Skip to content

Commit 723366e

Browse files
authored
Require blast campaigns to be scheduled separately (#16)
1 parent 2f0b40b commit 723366e

File tree

4 files changed

+25
-56
lines changed

4 files changed

+25
-56
lines changed

src/client/campaigns.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,10 @@ export function Campaigns<T extends Constructor<BaseIterableClient>>(Base: T) {
7979
async createBlastCampaign(
8080
params: CreateBlastCampaignParams
8181
): Promise<CreateCampaignResponse> {
82-
const response = await this.client.post("/api/campaigns/create", params);
82+
const response = await this.client.post("/api/campaigns/create", {
83+
...params,
84+
scheduleSend: false,
85+
});
8386
return this.validateResponse(response, CreateCampaignResponseSchema);
8487
}
8588

src/types/campaigns.ts

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -164,37 +164,14 @@ export const CreateBlastCampaignParamsSchema = z.object({
164164
.array(z.number())
165165
.min(1)
166166
.describe("Array of list IDs to which the campaign should be sent"),
167-
scheduleSend: z
168-
.boolean()
169-
.default(false)
170-
.describe(
171-
"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."
172-
),
173-
sendAt: IterableDateTimeSchema.describe(
174-
"Scheduled send time (YYYY-MM-DD HH:MM:SS UTC)"
175-
),
176167
campaignDataFields: campaignDataFieldsSchema,
177-
sendMode: z
178-
.enum(["ProjectTimeZone", "RecipientTimeZone"])
179-
.optional()
180-
.describe("Send mode for blast campaigns"),
181-
startTimeZone: z
182-
.string()
183-
.optional()
184-
.describe("Starting timezone for recipient timezone sends (IANA format)"),
185-
defaultTimeZone: z
186-
.string()
187-
.optional()
188-
.describe(
189-
"Default timezone for recipients without known timezone (IANA format)"
190-
),
191168
suppressionListIds: z
192169
.array(z.number())
193170
.optional()
194171
.describe("Array of suppression list IDs"),
195172
});
196173

197-
export type CreateBlastCampaignParams = z.input<
174+
export type CreateBlastCampaignParams = z.infer<
198175
typeof CreateBlastCampaignParamsSchema
199176
>;
200177

tests/integration/campaigns.test.ts

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ describe("Campaign Management Integration Tests", () => {
3232
name: string;
3333
templateId: number;
3434
listIds: number[];
35-
sendAt: string;
3635
}) => {
3736
const createResponse = await retryRateLimited(
3837
() => withTimeout(client.createBlastCampaign(params)),
@@ -514,21 +513,25 @@ describe("Campaign Management Integration Tests", () => {
514513
it("should create, schedule, and cancel a blast campaign", async () => {
515514
const campaignName = uniqueId("MCP-Test-Schedule");
516515

517-
// Schedule for 24 hours in the future (YYYY-MM-DD HH:MM:SS format)
518-
const sendAtDate = new Date(Date.now() + 24 * 60 * 60 * 1000);
519-
const sendAt = sendAtDate.toISOString().replace('T', ' ').substring(0, 19);
520-
521516
const campaignId = await createTestBlastCampaign({
522517
name: campaignName,
523518
templateId: testTemplateId,
524519
listIds: [testListId],
525-
sendAt,
526520
});
527521

528522
try {
523+
// Schedule for 24 hours in the future (ISO-8601 format)
524+
const sendAtDate = new Date(Date.now() + 24 * 60 * 60 * 1000);
525+
const sendAt = sendAtDate.toISOString();
526+
527+
await performCampaignAction(
528+
() => withTimeout(client.scheduleCampaign({ campaignId, sendAt })),
529+
"Schedule blast campaign"
530+
);
531+
529532
const campaign = await retryRateLimited(
530533
() => withTimeout(client.getCampaign({ id: campaignId })),
531-
"Get created blast campaign"
534+
"Get scheduled blast campaign"
532535
);
533536

534537
expect(campaign.type).toBe("Blast");
@@ -703,17 +706,22 @@ describe("Campaign Management Integration Tests", () => {
703706
}, 60000);
704707

705708
it("should send a scheduled campaign immediately", async () => {
706-
const sendAtDate = new Date(Date.now() + 24 * 60 * 60 * 1000);
707-
const sendAt = sendAtDate.toISOString().replace('T', ' ').substring(0, 19);
708-
709709
const campaignId = await createTestBlastCampaign({
710710
name: uniqueId("MCP-Test-Send"),
711711
templateId: testTemplateId,
712712
listIds: [testListId],
713-
sendAt,
714713
});
715714

716715
try {
716+
// Schedule for 24 hours in the future, then send immediately
717+
const sendAtDate = new Date(Date.now() + 24 * 60 * 60 * 1000);
718+
const sendAt = sendAtDate.toISOString();
719+
720+
await performCampaignAction(
721+
() => withTimeout(client.scheduleCampaign({ campaignId, sendAt })),
722+
"Schedule campaign for later"
723+
);
724+
717725
const campaign = await retryRateLimited(
718726
() => withTimeout(client.getCampaign({ id: campaignId })),
719727
"Get scheduled campaign"

tests/unit/campaigns.test.ts

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -788,7 +788,6 @@ describe("Campaign Management", () => {
788788
name: "Test Campaign",
789789
templateId: 123,
790790
listIds: [456],
791-
sendAt: "2026-03-01 10:00:00",
792791
})
793792
).not.toThrow();
794793

@@ -798,30 +797,16 @@ describe("Campaign Management", () => {
798797
name: "Test Campaign",
799798
templateId: 123,
800799
listIds: [456, 789],
801-
sendAt: "2026-03-01 10:00:00",
802-
sendMode: "RecipientTimeZone",
803-
startTimeZone: "America/New_York",
804-
defaultTimeZone: "America/Los_Angeles",
805800
suppressionListIds: [100],
806801
campaignDataFields: { key: "value" },
807802
})
808803
).not.toThrow();
809804

810-
// Missing sendAt
811-
expect(() =>
812-
CreateBlastCampaignParamsSchema.parse({
813-
name: "Test Campaign",
814-
templateId: 123,
815-
listIds: [456],
816-
})
817-
).toThrow();
818-
819805
// Missing listIds
820806
expect(() =>
821807
CreateBlastCampaignParamsSchema.parse({
822808
name: "Test Campaign",
823809
templateId: 123,
824-
sendAt: "2026-03-01 10:00:00",
825810
})
826811
).toThrow();
827812

@@ -831,7 +816,6 @@ describe("Campaign Management", () => {
831816
name: "Test Campaign",
832817
templateId: 123,
833818
listIds: [],
834-
sendAt: "2026-03-01 10:00:00",
835819
})
836820
).toThrow();
837821

@@ -840,7 +824,6 @@ describe("Campaign Management", () => {
840824
CreateBlastCampaignParamsSchema.parse({
841825
templateId: 123,
842826
listIds: [456],
843-
sendAt: "2026-03-01 10:00:00",
844827
})
845828
).toThrow();
846829

@@ -849,7 +832,6 @@ describe("Campaign Management", () => {
849832
CreateBlastCampaignParamsSchema.parse({
850833
name: "Test Campaign",
851834
listIds: [456],
852-
sendAt: "2026-03-01 10:00:00",
853835
})
854836
).toThrow();
855837
});
@@ -897,14 +879,13 @@ describe("Campaign Management", () => {
897879
name: "Test Blast",
898880
templateId: 100,
899881
listIds: [200],
900-
sendAt: "2026-03-01 10:00:00",
901882
};
902883

903884
const result = await client.createBlastCampaign(params);
904885

905886
expect(mockAxiosInstance.post).toHaveBeenCalledWith(
906887
"/api/campaigns/create",
907-
params
888+
{ ...params, scheduleSend: false }
908889
);
909890
expect(result).toEqual({ campaignId: 12345 });
910891
});

0 commit comments

Comments
 (0)