Outcome
Create a draft email outreach campaign without sending a message.
Before you start
- Verify the environment, credential, authorized organization and the published action permission workspace:write.
- Discover an accessible Engage workspace and bind resourceType=workspace and its resourceId separately from action input. Fixture identifiers grant no access.
- Approve the exact mutation and its effects before CLI --confirm or MCP confirm:true. This documentation does not authorize outreach.
Discover the target
- Select an authorized canonical Engage workspace. The server generates campaign.id; never supply an id or infer it from the name.
- Read campaigns.list before creation when reconciling an earlier uncertain create. Names are not declared unique or idempotency keys.
topolo whoami --json
topolo actions get app_topolo_engage.campaigns.create --json
topolo workspaces list topolo-engage --json
Input fields
| Field | Required | Meaning and constraints |
|---|---|---|
name | Yes | Required trimmed campaign name, 1–120 characters. It is not a unique identifier. |
goal | No | Optional nullable text up to 2,000 characters; blank or omitted values normalize to null. |
isTest | No | Optional boolean, default false. true excludes the campaign from engagement analytics. |
Validate, plan and execute
Set ENGAGE_WORKSPACE_ID to the identifier of your selected workspace. Prepare payload.json from the current input schema and discovered identifiers. Use the same payload for validation, planning and execution.
: "${ENGAGE_WORKSPACE_ID:?Set ENGAGE_WORKSPACE_ID to your selected workspace identifier}"
topolo actions validate app_topolo_engage.campaigns.create --data @payload.json --json
topolo actions plan app_topolo_engage.campaigns.create --resource-type workspace --resource-id "$ENGAGE_WORKSPACE_ID" --data @payload.json --confirm --json
Review the plan and obtain approval for the exact effects before executing the unchanged request.
: "${ENGAGE_WORKSPACE_ID:?Set ENGAGE_WORKSPACE_ID to your selected workspace identifier}"
topolo actions call app_topolo_engage.campaigns.create --resource-type workspace --resource-id "$ENGAGE_WORKSPACE_ID" --data @payload.json --confirm --json
For MCP, use topolo_get_action, topolo_validate_action, topolo_plan_action and topolo_call_action. Pass the selected resource context separately; confirm:true is required for mutations.
Worked requests and responses
Captured campaigns.create · HTTP 201
Action input
{
"name": "Q4"
}
Actual local response
{
"campaign": {
"id": "eng_cmp_1",
"name": "Q4",
"goal": null,
"channel": "email",
"status": "draft",
"isTest": false,
"createdBy": "user_1",
"updatedBy": "user_1",
"createdAt": "2026-10-06T08:30:00.000Z",
"updatedAt": "2026-10-06T08:30:00.000Z",
"archivedAt": null
}
}
Source test: task steps in sequences > stores linkedin_task and call_task steps with instructions and a due offset, and copies them into a new version. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Effects and response meaning
- In one database batch, inserts the campaign and its initial autonomy policy version 1. The policy starts with per-message approval.
- Returns HTTP 201 and campaign.id. The campaign starts draft on channel email; isTest defaults false. The goal is trimmed and blank or omitted goals become null.
- isTest=true excludes this campaign from engagement analytics. Creation does not activate a campaign, enroll contacts, or send outreach.
Verify the result
- Read campaigns.get using returned campaign.id in the same workspace. Compare name, normalized goal, isTest and draft status.
- Read campaigns.policy.get with id=campaign.id to verify policy version 1 and per_message approval separately.
Recover
Creation has no declared idempotency token. After an uncertain response, inspect campaigns.list and campaigns.get against the intended name, goal, test flag and creation time before attempting another create. If the result remains ambiguous, stop for review; a blind retry can duplicate campaigns. Archiving a mistaken draft is a separately reviewed terminal transition, not deletion.
Refused responses
Captured campaigns.create · HTTP 403
Action input
{
"name": "x"
}
Actual local response
{
"error": {
"code": "permission_denied",
"message": "The credential lacks workspace:write.",
"details": {
"requiredPermission": "workspace:write"
}
}
}
Source test: campaign routes > refuses writes without workspace:write (403) but allows reads. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured campaigns.create · HTTP 422
Refused input; do not execute
{
"name": ""
}
Actual local response
{
"error": {
"code": "invalid_action_input",
"message": "Input for campaigns.create is invalid.",
"details": [
{
"origin": "string",
"code": "too_small",
"minimum": 1,
"inclusive": true,
"path": [
"name"
],
"message": "Too small: expected string to have >=1 characters"
}
]
}
}
Source test: campaign routes > returns 422 for input the contract refuses. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Error recovery
- HTTP 403
permission_denied: Stop and inspect the permission required by this action in the current credential. Do not broaden permissions or switch organization automatically. - HTTP 422
invalid_action_input: Correct the request against the current input schema and validate it again before planning.
Related actions
Evidence and limits
-
contracts/campaigns.mjs -
apps/api/src/campaigns/campaigns-routes.ts -
apps/api/src/campaigns/campaigns-handlers.ts -
apps/api/src/campaigns/campaigns-domain.ts -
apps/api/src/campaigns/campaigns-repository.ts -
apps/api/test/campaigns-routes.test.ts -
Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
-
Widget/bootstrap use actual app handler but mocked worker runtime and app identity.
-
No gateway execution, live permissions, external delivery or deployed proof.
The examples are local route captures with synthetic context and simulated providers. They do not prove live authorization, CLI/MCP gateway execution, production state or delivery.