Skip to content
Draft
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
15 changes: 15 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,21 @@ Folders contain inboxes; inboxes receive messages, grouped into threads.
- **get-contact-export**: Get the status of a contact export job. `url` is populated when `status: finished`.


#### Email Campaigns

- **list-email-campaigns**: List the account's email campaigns, newest first, with page-token pagination (`token`, `per_page`) and optional `search` filter by name.
- **get-email-campaign**: Get an email campaign by ID.
- **create-email-campaign**: Create a `draft` campaign. Requires `name`, `domain_id` (sending domain ID), `from_local_part`, and `template_attributes.subject`.
- **update-email-campaign**: Update a `draft` campaign (partial; template edited in place). Only `draft` campaigns can be updated.
- **delete-email-campaign**: Delete a campaign by ID (must not be in a sending state).
- **start-email-campaign**: Start sending a `draft` campaign immediately.
- **schedule-email-campaign**: Schedule a `draft` campaign; `datetime` (ISO 8601) must be in the future and no more than 1 month ahead.
- **cancel-email-campaign**: Cancel a `scheduled` campaign, returning it to `draft`.
- **terminate-email-campaign**: Terminate a sending campaign (`started`/`queued`/`paused`), aborting the in-flight send.
- **reset-email-campaign**: Reset a `scheduled` campaign back to `draft`.
- **get-email-campaign-stats**: Aggregated campaign stats (counts + rates), optional `start_date`/`end_date` window (`YYYY-MM-DD`).


#### General / Account-admin

- **list-accounts**: List Mailtrap accounts the API token can access (with each account's `access_levels`).
Expand Down
107 changes: 107 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,113 @@ Get the status of a contact export job. Once `status` is `finished`, the `url` f

- `export_id` (required): ID of the contact export job

### list-email-campaigns

List the account's email campaigns, newest first, with page-token pagination. Optionally filter by name with `search`.

**Parameters:**

- `token` (optional): Page number to retrieve (page-token pagination). Defaults to `1`
- `per_page` (optional): Number of campaigns per page. Defaults to `50`, maximum `100`
- `search` (optional): Filter campaigns by name (case-insensitive partial match)

### get-email-campaign

Get an email campaign by ID.

**Parameters:**

- `email_campaign_id` (required): ID of the email campaign

### create-email-campaign

Create a new email campaign. The campaign is always created in the `draft` state; scheduling and starting are separate tools (`schedule-email-campaign`, `start-email-campaign`).

**Parameters:**

- `name` (required): Campaign name
- `domain_id` (required): ID of the verified sending domain used for the campaign, as returned by the Sending Domains endpoints
- `from_local_part` (required): Local part (before the @) of the From address
- `template_attributes` (required): Inline email template. Has:
- `subject` (required): Email subject line (max 255 chars). Supports merge tags, e.g. `Hi {{first_name}}`
- `body_html` (optional): HTML body (the design). Required before the campaign can be scheduled or started. Include an unsubscribe link via an anchor whose `href` contains the `__unsubscribe_url__` placeholder
- `body_text` (optional): Plain-text alternative of the email body
- `merge_tags` (optional): Bare names of the merge tags referenced in the subject/body, e.g. `["first_name"]`
- `from_display_name` (optional): Display name shown in the From header
- `reply_to` (optional): Reply-To address parts (`display_name`, `local_part`, `domain`)
- `delivery_mode` (optional): `rapid` (send as fast as possible) or `gradual` (throttle to `delivery_options.emails_per_hour`)
- `delivery_options` (optional): Delivery throttling options (`emails_per_hour`)
- `contact_list_ids` (optional): IDs of contact lists to send to (treated as the full set of included lists)
- `contact_segment_ids` (optional): IDs of contact segments to send to (treated as the full set of included segments)

### update-email-campaign

Update a `draft` email campaign. Only the provided fields change; the template is edited in place. Campaigns in any other state cannot be updated.

**Parameters:**

- `email_campaign_id` (required): ID of the email campaign to update
- All other parameters are optional and identical to `create-email-campaign` (`name`, `domain_id`, `from_local_part`, `from_display_name`, `reply_to`, `template_attributes`, `delivery_mode`, `delivery_options`, `contact_list_ids`, `contact_segment_ids`)

### delete-email-campaign

Delete an email campaign by ID. The campaign must not be in a sending state.

**Parameters:**

- `email_campaign_id` (required): ID of the email campaign to delete

### start-email-campaign

Start sending a `draft` email campaign immediately. Only `draft` campaigns can be started; the template must have a `body_html` design and the audience and verified sending domain must be set.

**Parameters:**

- `email_campaign_id` (required): ID of the email campaign to start

### schedule-email-campaign

Schedule a `draft` email campaign to start sending at a future time. Only `draft` campaigns can be scheduled.

**Parameters:**

- `email_campaign_id` (required): ID of the email campaign to schedule
- `datetime` (required): When to send the campaign (ISO 8601). Must be in the future and no more than 1 month ahead

### cancel-email-campaign

Cancel a `scheduled` email campaign, returning it to `draft`. Only `scheduled` campaigns can be cancelled.

**Parameters:**

- `email_campaign_id` (required): ID of the email campaign to cancel

### terminate-email-campaign

Terminate an email campaign that is currently sending (`started`, `queued`, or `paused`), aborting the in-flight send.

**Parameters:**

- `email_campaign_id` (required): ID of the email campaign to terminate

### reset-email-campaign

Reset a `scheduled` email campaign back to `draft`. Only `scheduled` campaigns can be reset.

**Parameters:**

- `email_campaign_id` (required): ID of the email campaign to reset

### get-email-campaign-stats

Get aggregated performance statistics for an email campaign (counts and rates for deliveries, opens, clicks, bounces, spam complaints, and unsubscriptions).

**Parameters:**

- `email_campaign_id` (required): ID of the email campaign
- `start_date` (optional): Start of the aggregation window (inclusive), `YYYY-MM-DD`. Defaults to the day the campaign was last started
- `end_date` (optional): End of the aggregation window (inclusive), `YYYY-MM-DD`. Defaults to the current date

### list-accounts

List Mailtrap accounts the current API token can access, with each account's access levels.
Expand Down
133 changes: 133 additions & 0 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,30 @@ import {
getContactExport,
getContactExportSchema,
} from "./tools/contactExports";
import {
listEmailCampaigns,
listEmailCampaignsSchema,
getEmailCampaign,
getEmailCampaignSchema,
createEmailCampaign,
createEmailCampaignSchema,
updateEmailCampaign,
updateEmailCampaignSchema,
deleteEmailCampaign,
deleteEmailCampaignSchema,
startEmailCampaign,
startEmailCampaignSchema,
scheduleEmailCampaign,
scheduleEmailCampaignSchema,
cancelEmailCampaign,
cancelEmailCampaignSchema,
terminateEmailCampaign,
terminateEmailCampaignSchema,
resetEmailCampaign,
resetEmailCampaignSchema,
getEmailCampaignStats,
getEmailCampaignStatsSchema,
} from "./tools/emailCampaigns";
import { listAccounts, listAccountsSchema } from "./tools/accounts";
import { getBillingUsage, getBillingUsageSchema } from "./tools/billing";
import {
Expand Down Expand Up @@ -940,6 +964,115 @@ const tools = [
readOnlyHint: true,
},
},
{
name: "list-email-campaigns",
description:
"List the account's email campaigns, newest first, with page-token pagination (`token`, `per_page`). Optionally filter by name with `search`.",
inputSchema: listEmailCampaignsSchema,
handler: listEmailCampaigns,
annotations: {
readOnlyHint: true,
},
},
{
name: "get-email-campaign",
description: "Get an email campaign by ID.",
inputSchema: getEmailCampaignSchema,
handler: getEmailCampaign,
annotations: {
readOnlyHint: true,
},
},
{
name: "create-email-campaign",
description:
"Create a new email campaign in the `draft` state; requires `name`, `domain_id`, `from_local_part`, and a `template_attributes.subject`.",
inputSchema: createEmailCampaignSchema,
handler: createEmailCampaign,
annotations: {
destructiveHint: false,
},
},
{
name: "update-email-campaign",
description:
"Update a `draft` email campaign; only the provided fields change, and campaigns in any other state cannot be updated.",
inputSchema: updateEmailCampaignSchema,
handler: updateEmailCampaign,
annotations: {
destructiveHint: true,
},
},
{
name: "delete-email-campaign",
description:
"Delete an email campaign by ID; the campaign must not be in a sending state.",
inputSchema: deleteEmailCampaignSchema,
handler: deleteEmailCampaign,
annotations: {
destructiveHint: true,
},
},
{
name: "start-email-campaign",
description:
"Start sending a `draft` email campaign immediately; only `draft` campaigns can be started.",
inputSchema: startEmailCampaignSchema,
handler: startEmailCampaign,
annotations: {
destructiveHint: true,
},
},
{
name: "schedule-email-campaign",
description:
"Schedule a `draft` email campaign to start sending at `datetime` (ISO 8601, must be in the future and no more than 1 month ahead); only `draft` campaigns can be scheduled.",
inputSchema: scheduleEmailCampaignSchema,
handler: scheduleEmailCampaign,
annotations: {
destructiveHint: true,
},
},
{
name: "cancel-email-campaign",
description:
"Cancel a `scheduled` email campaign, returning it to `draft`; only `scheduled` campaigns can be cancelled.",
inputSchema: cancelEmailCampaignSchema,
handler: cancelEmailCampaign,
annotations: {
destructiveHint: true,
},
},
{
name: "terminate-email-campaign",
description:
"Terminate an email campaign that is currently sending (`started`, `queued`, or `paused`), aborting the in-flight send.",
inputSchema: terminateEmailCampaignSchema,
handler: terminateEmailCampaign,
annotations: {
destructiveHint: true,
},
},
{
name: "reset-email-campaign",
description:
"Reset a `scheduled` email campaign back to `draft`; only `scheduled` campaigns can be reset.",
inputSchema: resetEmailCampaignSchema,
handler: resetEmailCampaign,
annotations: {
destructiveHint: true,
},
},
{
name: "get-email-campaign-stats",
description:
"Get aggregated performance statistics for an email campaign; optionally narrow the window with `start_date`/`end_date` (`YYYY-MM-DD`).",
inputSchema: getEmailCampaignStatsSchema,
handler: getEmailCampaignStats,
annotations: {
readOnlyHint: true,
},
},
{
name: "list-accounts",
description:
Expand Down
47 changes: 47 additions & 0 deletions src/tools/emailCampaigns/__tests__/cancelEmailCampaign.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import cancelEmailCampaign from "../cancelEmailCampaign";
import { requireClient } from "../../../client";

const mockClient = {
emailCampaigns: {
cancel: jest.fn(),
},
};

jest.mock("../../../client", () => ({
requireClient: jest.fn(() => mockClient),
}));

describe("cancelEmailCampaign", () => {
beforeEach(() => {
jest.clearAllMocks();
(requireClient as jest.Mock).mockReturnValue(mockClient);
});

it("cancels the campaign and returns the unwrapped campaign", async () => {
mockClient.emailCampaigns.cancel.mockResolvedValue({
data: { id: 4567, current_state: "draft" },
});

const result = await cancelEmailCampaign({ email_campaign_id: 4567 });

expect(requireClient).toHaveBeenCalledWith("email campaigns", {
requireAccountId: false,
});
expect(mockClient.emailCampaigns.cancel).toHaveBeenCalledWith(4567);
expect(result.content[0].text).toContain('"current_state": "draft"');
expect(result.isError).toBeUndefined();
});

it("surfaces API errors", async () => {
mockClient.emailCampaigns.cancel.mockRejectedValue(
new Error("Campaign is not scheduled")
);

const result = await cancelEmailCampaign({ email_campaign_id: 4567 });

expect(result.isError).toBe(true);
expect(result.content[0].text).toBe(
"Failed to cancel email campaign: Campaign is not scheduled"
);
});
});
Loading
Loading