Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/client/campaigns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@ export function Campaigns<T extends Constructor<BaseIterableClient>>(Base: T) {
async createBlastCampaign(
params: CreateBlastCampaignParams
): Promise<CreateCampaignResponse> {
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);
}

Expand Down
25 changes: 1 addition & 24 deletions src/types/campaigns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
>;

Expand Down
30 changes: 19 additions & 11 deletions tests/integration/campaigns.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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"
Expand Down
21 changes: 1 addition & 20 deletions tests/unit/campaigns.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -788,7 +788,6 @@ describe("Campaign Management", () => {
name: "Test Campaign",
templateId: 123,
listIds: [456],
sendAt: "2026-03-01 10:00:00",
})
).not.toThrow();

Expand All @@ -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();

Expand All @@ -831,7 +816,6 @@ describe("Campaign Management", () => {
name: "Test Campaign",
templateId: 123,
listIds: [],
sendAt: "2026-03-01 10:00:00",
})
).toThrow();

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

Expand All @@ -849,7 +832,6 @@ describe("Campaign Management", () => {
CreateBlastCampaignParamsSchema.parse({
name: "Test Campaign",
listIds: [456],
sendAt: "2026-03-01 10:00:00",
})
).toThrow();
});
Expand Down Expand Up @@ -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 });
});
Expand Down
Loading