Outcome
Read retained message versions, approval and transport state for one enrollment.
Before you start
- Verify the environment, credential, authorized organization and the published action permission workspace:read.
- Discover an accessible Engage workspace and bind resourceType=workspace and its resourceId separately from action input. Fixture identifiers grant no access.
Discover the target
- Select the authorized Engage workspace and discover enrollment id through enrollments.list or retain the id from an earlier approved enrollments.create. Read enrollments.get before acting on its status.
topolo whoami --json
topolo actions get app_topolo_engage.enrollments.messages.list --json
topolo workspaces list topolo-engage --json
Input fields
| Field | Required | Meaning and constraints |
|---|---|---|
id | Yes | Required enrollment identifier returned by enrollments.create or enrollments.list in the selected workspace, trimmed non-empty string up to 128 characters. This is not a campaign, contact or message id. |
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.enrollments.messages.list --data @payload.json --json
topolo actions plan app_topolo_engage.enrollments.messages.list --resource-type workspace --resource-id "$ENGAGE_WORKSPACE_ID" --data @payload.json --json
Execute the unchanged validated read in the selected workspace.
: "${ENGAGE_WORKSPACE_ID:?Set ENGAGE_WORKSPACE_ID to your selected workspace identifier}"
topolo actions call app_topolo_engage.enrollments.messages.list --resource-type workspace --resource-id "$ENGAGE_WORKSPACE_ID" --data @payload.json --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 enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "sent",
"holdReason": null,
"messageId": "wa_stub_1",
"scheduledFor": "2026-10-06T08:30:00.000Z",
"sentAt": "2026-10-06T08:30:00.000Z",
"attempts": 0,
"version": 1,
"approvalState": "approved",
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
},
{
"id": "eng_msg_2",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_2",
"stepPosition": 2,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": null,
"messageId": null,
"scheduledFor": "2026-10-08T08:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: the runner on a WhatsApp template step > sends the approved template to an opted-in contact on channel whatsapp and moves on. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": "mailbox_not_connected",
"messageId": null,
"scheduledFor": "2026-10-06T09:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: runner with the Nexus transport contract > holds a seat without a mailbox as mailbox_not_connected. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "skipped",
"holdReason": "whatsapp_no_phone",
"messageId": null,
"scheduledFor": "2026-10-06T08:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
},
{
"id": "eng_msg_2",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_2",
"stepPosition": 2,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": null,
"messageId": null,
"scheduledFor": "2026-10-08T08:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: the runner on a WhatsApp template step > skips the step (not the enrollment) without a phone, without an opt-in, after a revoke and with an unresolved variable. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": "Quick question, Ada",
"text": "Hi Ada,\n\nHow is Analytical handling outreach?\n\nAsh\n\n--\nTest opt-out sentence for fixtures.\nExample Ltd\n1 Example Street\nLondon",
"status": "sent",
"holdReason": null,
"messageId": "email_stub_2707f2dfbb8a394b00969d941f11089b",
"scheduledFor": "2026-10-06T08:30:00.000Z",
"sentAt": "2026-10-06T08:30:00.000Z",
"attempts": 0,
"version": 1,
"approvalState": "approved",
"footerSource": "manual",
"brandKitRef": null,
"brandVersionRef": null
},
{
"id": "eng_msg_2",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_2",
"stepPosition": 2,
"subject": "Re: Quick question, Ada",
"text": "Following up, Ada.\n\n--\nTest opt-out sentence for fixtures.\nExample Ltd\n1 Example Street\nLondon",
"status": "sent",
"holdReason": null,
"messageId": "email_stub_8ed9c21ca5faba2963419f3e20cccb29",
"scheduledFor": "2026-10-07T08:30:00.000Z",
"sentAt": "2026-10-07T08:30:00.000Z",
"attempts": 0,
"version": 1,
"approvalState": "approved",
"footerSource": "manual",
"brandKitRef": null,
"brandVersionRef": null
},
{
"id": "eng_msg_3",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_3",
"stepPosition": 3,
"subject": "Re: Quick question, Ada",
"text": "Last one, Ada.\n\n--\nTest opt-out sentence for fixtures.\nExample Ltd\n1 Example Street\nLondon",
"status": "sent",
"holdReason": null,
"messageId": "email_stub_b76fc0d15956d7cc8c43a31dacbf9ec9",
"scheduledFor": "2026-10-08T08:30:00.000Z",
"sentAt": "2026-10-08T08:30:00.000Z",
"attempts": 0,
"version": 1,
"approvalState": "approved",
"footerSource": "manual",
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: runner with the Nexus transport contract > threads every follow-up from the stored RFC Message-IDs: In-Reply-To the previous, References root … previous. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "skipped",
"holdReason": "manual_task",
"messageId": null,
"scheduledFor": "2026-10-06T08:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: the runner on a task step > creates one task for the campaign owner, waits for it, and moves on when it is done. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": "Quick question, Ada",
"text": "Hi Ada,\n\nHow is Analytical handling outreach?\n\nAsh\n\n--\nTest opt-out sentence for fixtures.\nExample Ltd\n1 Example Street\nLondon",
"status": "sent",
"holdReason": null,
"messageId": "email_stub_2707f2dfbb8a394b00969d941f11089b",
"scheduledFor": "2026-10-06T08:30:00.000Z",
"sentAt": "2026-10-06T08:30:00.000Z",
"attempts": 0,
"version": 1,
"approvalState": "approved",
"footerSource": "manual",
"brandKitRef": null,
"brandVersionRef": null
},
{
"id": "eng_msg_2",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_2",
"stepPosition": 2,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": null,
"messageId": null,
"scheduledFor": "2026-10-10T08:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: runDueSends > renders, reserves, sends, consumes and schedules the threaded follow-up. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "skipped",
"holdReason": "manual_task",
"messageId": null,
"scheduledFor": "2026-10-06T08:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
},
{
"id": "eng_msg_2",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_2",
"stepPosition": 2,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": null,
"messageId": null,
"scheduledFor": "2026-10-09T08:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: the runner on a task step > creates one task for the campaign owner, waits for it, and moves on when it is done. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "skipped",
"holdReason": "whatsapp_no_optin",
"messageId": null,
"scheduledFor": "2026-10-06T08:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": "approved",
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
},
{
"id": "eng_msg_2",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_2",
"stepPosition": 2,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": null,
"messageId": null,
"scheduledFor": "2026-10-08T08:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: the runner on a WhatsApp template step > skips the step (not the enrollment) without a phone, without an opt-in, after a revoke and with an unresolved variable. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": "Quick question, Ada",
"text": "Hi Ada,\n\nHow is Analytical handling outreach?\n\nAsh\n\n--\nTest opt-out sentence for fixtures.\nExample Ltd\n1 Example Street\nLondon",
"status": "sent",
"holdReason": null,
"messageId": "email_stub_2707f2dfbb8a394b00969d941f11089b",
"scheduledFor": "2026-10-06T08:30:00.000Z",
"sentAt": "2026-10-06T08:30:00.000Z",
"attempts": 0,
"version": 1,
"approvalState": "approved",
"footerSource": "manual",
"brandKitRef": null,
"brandVersionRef": null
},
{
"id": "eng_msg_2",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_2",
"stepPosition": 2,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": "outside_send_window",
"messageId": null,
"scheduledFor": "2026-10-13T07:00:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: runDueSends > renders, reserves, sends, consumes and schedules the threaded follow-up. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "skipped",
"holdReason": "whatsapp_unresolved_variable",
"messageId": null,
"scheduledFor": "2026-10-06T08:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
},
{
"id": "eng_msg_2",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_2",
"stepPosition": 2,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": null,
"messageId": null,
"scheduledFor": "2026-10-08T08:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: the runner on a WhatsApp template step > skips the step (not the enrollment) without a phone, without an opt-in, after a revoke and with an unresolved variable. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": "Quick question, Ada",
"text": "Hi Ada,\n\nHow is Analytical handling outreach?\n\nAsh\n\n--\nTest opt-out sentence for fixtures.\nExample Ltd\n1 Example Street\nLondon",
"status": "sent",
"holdReason": null,
"messageId": "email_stub_2707f2dfbb8a394b00969d941f11089b",
"scheduledFor": "2026-10-06T08:30:00.000Z",
"sentAt": "2026-10-06T08:30:00.000Z",
"attempts": 0,
"version": 1,
"approvalState": "approved",
"footerSource": "manual",
"brandKitRef": null,
"brandVersionRef": null
},
{
"id": "eng_msg_7",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_2",
"stepPosition": 2,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": null,
"messageId": null,
"scheduledFor": "2026-10-10T08:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: engagement analytics > reports first-touch rates by campaign: each contact once, bounced never replied, test campaigns excluded. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_2"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_2",
"enrollmentId": "eng_enr_2",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": "Quick question, Ada",
"text": "Hi Ada,\n\nHow is Analytical handling outreach?\n\nAsh\n\n--\nTest opt-out sentence for fixtures.\nExample Ltd\n1 Example Street\nLondon",
"status": "sent",
"holdReason": null,
"messageId": "email_stub_17bbf03852103719df5ae98f06eedb76",
"scheduledFor": "2026-10-06T08:30:00.000Z",
"sentAt": "2026-10-06T08:30:00.000Z",
"attempts": 0,
"version": 1,
"approvalState": "approved",
"footerSource": "manual",
"brandKitRef": null,
"brandVersionRef": null
},
{
"id": "eng_msg_8",
"enrollmentId": "eng_enr_2",
"stepId": "eng_stp_2",
"stepPosition": 2,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": null,
"messageId": null,
"scheduledFor": "2026-10-10T08:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: engagement analytics > reports first-touch rates by campaign: each contact once, bounced never replied, test campaigns excluded. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_3"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_3",
"enrollmentId": "eng_enr_3",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": "Quick question, Ada",
"text": "Hi Ada,\n\nHow is Analytical handling outreach?\n\nAsh\n\n--\nTest opt-out sentence for fixtures.\nExample Ltd\n1 Example Street\nLondon",
"status": "sent",
"holdReason": null,
"messageId": "email_stub_7dcf5b7ceec2bed0b492bf1678c0a081",
"scheduledFor": "2026-10-06T08:30:00.000Z",
"sentAt": "2026-10-06T08:30:00.000Z",
"attempts": 0,
"version": 1,
"approvalState": "approved",
"footerSource": "manual",
"brandKitRef": null,
"brandVersionRef": null
},
{
"id": "eng_msg_9",
"enrollmentId": "eng_enr_3",
"stepId": "eng_stp_2",
"stepPosition": 2,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": null,
"messageId": null,
"scheduledFor": "2026-10-10T08:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: engagement analytics > reports first-touch rates by campaign: each contact once, bounced never replied, test campaigns excluded. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": "emergency_stop",
"messageId": null,
"scheduledFor": "2026-10-06T09:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: the runner on a task step > holds while an emergency stop is on and creates nothing; resume releases it. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_2"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_2",
"enrollmentId": "eng_enr_2",
"stepId": "eng_stp_3",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": null,
"messageId": null,
"scheduledFor": "2026-10-06T08:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: runDueSends > honours the batch limit and the workspace scope. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": "campaign_not_active",
"messageId": null,
"scheduledFor": "2026-10-06T09:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: the runner on a task step > holds while the campaign is paused. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_2"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_2",
"enrollmentId": "eng_enr_2",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": "global_cap_reached",
"messageId": null,
"scheduledFor": "2026-10-07T07:00:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: runDueSends > hold order > global cap first: holds once the workspace reached its daily cap, until the next day's window. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": "Quick question, Ada",
"text": "Hi Ada,\n\nHow is Analytical handling outreach?\n\nAsh\n\n--\nTest opt-out sentence for fixtures.\nExample Ltd\n1 Example Street\nLondon",
"status": "scheduled",
"holdReason": "idempotency_key_in_progress",
"messageId": null,
"scheduledFor": "2026-10-06T09:30:00.000Z",
"sentAt": null,
"attempts": 1,
"version": 1,
"approvalState": "approved",
"footerSource": "manual",
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: runner with the Nexus transport contract > idempotency_key_in_progress holds the message and releases its reservation. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": "seat_paused",
"messageId": null,
"scheduledFor": "2026-10-06T09:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: runDueSends > hold order > seat health next: a manually paused seat holds its messages. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": "outside_send_window",
"messageId": null,
"scheduledFor": "2026-10-06T12:00:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: runDueSends > hold order > then the recipient-local window, then the warm-up cap. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": "warmup_cap_reached",
"messageId": null,
"scheduledFor": "2026-10-07T12:00:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: runDueSends > hold order > then the recipient-local window, then the warm-up cap. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": "Quick question, Ada",
"text": "Hi Ada,\n\nHow is Analytical handling outreach?\n\nAsh\n\n--\nTest opt-out sentence for fixtures.\nExample Ltd\n1 Example Street\nLondon",
"status": "failed",
"holdReason": "idempotency_key_reused",
"messageId": null,
"scheduledFor": "2026-10-06T08:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": "approved",
"footerSource": "manual",
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: runner with the Nexus transport contract > idempotency_key_reused fails the message and stops the enrollment. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": null,
"messageId": null,
"scheduledFor": "2026-10-07T08:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: the runner on a task step > never renders a task step ahead for approval. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_2"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_2",
"enrollmentId": "eng_enr_2",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": "warmup_cap_reached",
"messageId": null,
"scheduledFor": "2026-10-07T07:00:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: runDueSends > hold order > warm-up cap counts the seat's sends today. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": "ineligible:quiet_hours",
"messageId": null,
"scheduledFor": "2026-10-06T09:00:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": "approved",
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: the runner on a WhatsApp template step > holds for quiet hours in the contact's time zone until they end. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": "Quick question, Ada",
"text": "Hi Ada,\n\nHow is Analytical handling outreach?\n\nAsh\n\n--\nTest opt-out sentence for fixtures.\nExample Ltd\n1 Example Street\nLondon",
"status": "scheduled",
"holdReason": "seat_misconfigured",
"messageId": null,
"scheduledFor": "2026-10-06T09:30:00.000Z",
"sentAt": null,
"attempts": 1,
"version": 1,
"approvalState": "approved",
"footerSource": "manual",
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: runner with the Nexus transport contract > outreach_requires_mailbox_sender marks the seat misconfigured, pauses it and holds. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": "Quick question, Ada",
"text": "Hi Ada,\n\nHow is Analytical handling outreach?\n\nAsh\n\n--\nTest opt-out sentence for fixtures.\nExample Ltd\n1 Example Street\nLondon",
"status": "scheduled",
"holdReason": "seat_misconfigured",
"messageId": null,
"scheduledFor": "2026-10-07T07:00:00.000Z",
"sentAt": null,
"attempts": 1,
"version": 1,
"approvalState": "approved",
"footerSource": "manual",
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: runner with the Nexus transport contract > outreach_requires_mailbox_sender marks the seat misconfigured, pauses it and holds. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": "Quick question, Ada",
"text": "Hi Ada,\n\nHow is Analytical handling outreach?\n\nAsh\n\n--\nTest opt-out sentence for fixtures.\nExample Ltd\n1 Example Street\nLondon",
"status": "scheduled",
"holdReason": "ineligible:quiet_hours",
"messageId": null,
"scheduledFor": "2026-10-06T09:00:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": "approved",
"footerSource": "manual",
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: runDueSends > hold order > then Engage eligibility: time-bound refusals hold, suppressions end the enrollment. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": "Quick question, Ada",
"text": "Hi Ada,\n\nHow is Analytical handling outreach?\n\nAsh\n\n--\nTest opt-out sentence for fixtures.\nExample Ltd\n1 Example Street\nLondon",
"status": "skipped",
"holdReason": "manual_block",
"messageId": null,
"scheduledFor": "2026-10-06T09:00:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": "approved",
"footerSource": "manual",
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: runDueSends > hold order > then Engage eligibility: time-bound refusals hold, suppressions end the enrollment. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": "reservation_expired",
"messageId": null,
"scheduledFor": "2026-10-06T08:30:00.000Z",
"sentAt": null,
"attempts": 1,
"version": 1,
"approvalState": "approved",
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: the runner on a WhatsApp template step > holds a reservation that expired under this key and retries under a fresh one. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": "whatsapp_not_permitted",
"messageId": null,
"scheduledFor": "2026-10-07T08:30:00.000Z",
"sentAt": null,
"attempts": 1,
"version": 1,
"approvalState": "approved",
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: the runner on a WhatsApp template step > holds as whatsapp_not_permitted when Nexus refuses Engage, for a day, and surfaces it in ops holds. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": "Quick question, Ada",
"text": "Hi Ada,\n\nHow is Analytical handling outreach?\n\nAsh\n\n--\nTest opt-out sentence for fixtures.\nExample Ltd\n1 Example Street\nLondon",
"status": "scheduled",
"holdReason": "seat_paused",
"messageId": null,
"scheduledFor": "2026-10-06T09:30:00.000Z",
"sentAt": null,
"attempts": 1,
"version": 1,
"approvalState": "approved",
"footerSource": "manual",
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: runner with the Nexus transport contract > mailbox_not_allowed_for_engage pauses the seat with that reason and holds. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": "whatsapp_not_configured",
"messageId": null,
"scheduledFor": "2026-10-07T08:30:00.000Z",
"sentAt": null,
"attempts": 1,
"version": 1,
"approvalState": "approved",
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: the runner on a WhatsApp template step > holds as whatsapp_not_configured, and skips a step Nexus rejects. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "skipped",
"holdReason": "whatsapp_rejected",
"messageId": null,
"scheduledFor": "2026-10-07T08:30:00.000Z",
"sentAt": null,
"attempts": 1,
"version": 1,
"approvalState": "approved",
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
},
{
"id": "eng_msg_2",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_2",
"stepPosition": 2,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": null,
"messageId": null,
"scheduledFor": "2026-10-09T08:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: the runner on a WhatsApp template step > holds as whatsapp_not_configured, and skips a step Nexus rejects. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "skipped",
"holdReason": "unresolved_merge_field:company",
"messageId": null,
"scheduledFor": "2026-10-06T08:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: runDueSends > skips a message with an unresolved merge field and never sends it empty. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": "transport_error",
"messageId": null,
"scheduledFor": "2026-10-06T09:30:00.000Z",
"sentAt": null,
"attempts": 1,
"version": 1,
"approvalState": "approved",
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: the runner on a WhatsApp template step > counts transport errors and fails the message after three. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "failed",
"holdReason": "send_failed",
"messageId": null,
"scheduledFor": "2026-10-08T09:30:00.000Z",
"sentAt": null,
"attempts": 3,
"version": 1,
"approvalState": "approved",
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: the runner on a WhatsApp template step > counts transport errors and fails the message after three. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": "postal_address_missing",
"messageId": null,
"scheduledFor": "2026-10-06T09:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: runDueSends > holds without the workspace postal address, then sends once it exists. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": "campaign_not_active",
"messageId": null,
"scheduledFor": "2026-10-07T07:00:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: the runner on a WhatsApp template step > respects the emergency stop, a paused campaign, approval and the touch limit. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": "opt_out_text_missing",
"messageId": null,
"scheduledFor": "2026-10-06T09:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: runDueSends > holds without the workspace opt-out sentence, and names both gaps when both are missing. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": "postal_address_missing,opt_out_text_missing",
"messageId": null,
"scheduledFor": "2026-10-07T07:00:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: runDueSends > holds without the workspace opt-out sentence, and names both gaps when both are missing. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": "pending_approval",
"messageId": null,
"scheduledFor": "2026-10-06T08:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": "pending_approval",
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: the runner on a WhatsApp template step > respects the emergency stop, a paused campaign, approval and the touch limit. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": "Quick question, Ada",
"text": "Hi Ada,\n\nHow is Analytical handling outreach?\n\nAsh\n\n--\nTest opt-out sentence for fixtures.\nExample Ltd\n1 Example Street\nLondon",
"status": "skipped",
"holdReason": "provider_suppressed",
"messageId": null,
"scheduledFor": "2026-10-06T08:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": "approved",
"footerSource": "manual",
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: runner with the Nexus transport contract > recipient_suppressed records a manual block (provider_suppressed) and stops the enrollment. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": "Quick question, Ada",
"text": "Hi Ada,\n\nHow is Analytical handling outreach?\n\nAsh\n\n--\nTest opt-out sentence for fixtures.\nExample Ltd\n1 Example Street\nLondon",
"status": "scheduled",
"holdReason": "transport_unavailable",
"messageId": null,
"scheduledFor": "2026-10-06T09:30:00.000Z",
"sentAt": null,
"attempts": 1,
"version": 1,
"approvalState": "approved",
"footerSource": "manual",
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: runDueSends > production refuses to send with the stub: the message waits with transport_unavailable. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": "Hello Ada",
"text": "Hi Ada\n\n--\nTest opt-out sentence for fixtures.\nExample Ltd\n1 Example Street\nLondon",
"status": "sent",
"holdReason": null,
"messageId": "email_stub_2707f2dfbb8a394b00969d941f11089b",
"scheduledFor": "2026-10-06T08:30:00.000Z",
"sentAt": "2026-10-06T08:30:00.000Z",
"attempts": 0,
"version": 1,
"approvalState": "approved",
"footerSource": "manual",
"brandKitRef": null,
"brandVersionRef": null
},
{
"id": "eng_msg_2",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_2",
"stepPosition": 2,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": "campaign_cap_reached",
"messageId": null,
"scheduledFor": "2026-10-07T07:00:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: the runner on a WhatsApp template step > holds at the campaign volume cap until the next day. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": "Quick question, Ada",
"text": "Hi Ada,\n\nHow is Analytical handling outreach?\n\nAsh\n\n--\nTest opt-out sentence for fixtures.\nExample Ltd\n1 Example Street\nLondon",
"status": "scheduled",
"holdReason": "transport_skipped",
"messageId": null,
"scheduledFor": "2026-10-06T09:30:00.000Z",
"sentAt": null,
"attempts": 1,
"version": 1,
"approvalState": "approved",
"footerSource": "manual",
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: runDueSends > a transport skip without a reason is held as transport_skipped. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": "Quick question, Ada",
"text": "Hi Ada,\n\nHow is Analytical handling outreach?\n\nAsh\n\n--\nTest opt-out sentence for fixtures.\nExample Ltd\n1 Example Street\nLondon",
"status": "scheduled",
"holdReason": "transport_error",
"messageId": null,
"scheduledFor": "2026-10-06T09:30:00.000Z",
"sentAt": null,
"attempts": 1,
"version": 1,
"approvalState": "approved",
"footerSource": "manual",
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: runDueSends > retries transport errors, then fails the message and stops the enrollment. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": "Quick question, Ada",
"text": "Hi Ada,\n\nHow is Analytical handling outreach?\n\nAsh\n\n--\nTest opt-out sentence for fixtures.\nExample Ltd\n1 Example Street\nLondon",
"status": "failed",
"holdReason": "send_failed",
"messageId": null,
"scheduledFor": "2026-10-07T08:00:00.000Z",
"sentAt": null,
"attempts": 3,
"version": 1,
"approvalState": "approved",
"footerSource": "manual",
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: runDueSends > retries transport errors, then fails the message and stops the enrollment. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": null,
"messageId": null,
"scheduledFor": "2026-10-06T08:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: runDueSends > re-reserves after an expired reservation. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": "Quick question, Ada",
"text": "Hi Ada,\n\nHow is Analytical handling outreach?\n\nAsh\n\n--\nTest opt-out sentence for fixtures.\nExample Ltd\n1 Example Street\nLondon",
"status": "scheduled",
"holdReason": "reservation_expired",
"messageId": null,
"scheduledFor": "2026-10-06T08:30:00.000Z",
"sentAt": null,
"attempts": 1,
"version": 1,
"approvalState": "approved",
"footerSource": "manual",
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: runDueSends > re-reserves after an expired reservation. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "cancelled",
"holdReason": "enrollment_ended",
"messageId": null,
"scheduledFor": "2026-10-06T08:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: runDueSends > ending enrollments the runner cannot continue > cancels messages of an ended enrollment. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": "outside_send_window",
"messageId": null,
"scheduledFor": "2026-10-07T08:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: runDueSends > ending enrollments the runner cannot continue > falls back a day when the settings leave no sending weekday. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "scheduled",
"holdReason": "campaign_not_active",
"messageId": null,
"scheduledFor": "2026-10-08T09:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: runDueSends > ending enrollments the runner cannot continue > falls back a day when the settings leave no sending weekday. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": "Quick question, Ada",
"text": "Hi Ada,\n\nHow is Analytical handling outreach?\n\nAsh\n\n--\nTest opt-out sentence for fixtures.\nExample Ltd\n1 Example Street\nLondon",
"status": "sent",
"holdReason": null,
"messageId": "email_stub_2707f2dfbb8a394b00969d941f11089b",
"scheduledFor": "2026-10-06T08:30:00.000Z",
"sentAt": "2026-10-06T08:30:00.000Z",
"attempts": 0,
"version": 1,
"approvalState": "approved",
"footerSource": "manual",
"brandKitRef": null,
"brandVersionRef": null
},
{
"id": "eng_msg_2",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_2",
"stepPosition": 2,
"subject": null,
"text": null,
"status": "cancelled",
"holdReason": "reply",
"messageId": null,
"scheduledFor": "2026-10-10T08:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: outcomes.record > classifies a reply, stops an active enrollment as replied, replaces a classification and is audited. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.messages.list · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"messages": [
{
"id": "eng_msg_1",
"enrollmentId": "eng_enr_1",
"stepId": "eng_stp_1",
"stepPosition": 1,
"subject": null,
"text": null,
"status": "cancelled",
"holdReason": "Asked by sales",
"messageId": null,
"scheduledFor": "2026-10-06T08:30:00.000Z",
"sentAt": null,
"attempts": 0,
"version": 1,
"approvalState": null,
"footerSource": null,
"brandKitRef": null,
"brandVersionRef": null
}
]
}
Source test: enrollment routes > stops an enrollment, cancelling its scheduled messages, once. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Effects and response meaning
- Returns messages[] ordered by stepPosition then createdAt, without pagination. Each includes rendered subject/text when available, version, approvalState, status, holdReason, transport messageId, scheduledFor, sentAt, attempts and footer/brand references.
- History can contain edited/superseded message versions; position alone is not message identity. A null subject/text can mean the message has not been rendered. Scheduled or approved is not delivery proof.
Verify the result
- Retain messages[].id and version and compare enrollmentId to the selected enrollment. Inspect approvalState/status/holdReason and timestamps for the exact message version when verifying an approval, rejection or stop.
- A transport id or sentAt reflects application transport state; corroborate provider delivery separately rather than treating this fixture read as recipient delivery proof.
Recover
Retry this read after a transient failure using the same authorized workspace and discovered identifier. Missing data does not authorize creation, re-enrollment or a different resource context.
Refused responses
Captured enrollments.messages.list · HTTP 404
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"error": {
"code": "not_found",
"message": "Enrollment was not found in this workspace."
}
}
Source test: enrollment routes > keeps enrollments inside the workspace and enforces permissions. 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: Validate the current strict input schema. Creation requires campaignId, seatId and exactly one of contactIds or listId; do not provide both or neither. - HTTP 404
not_found: Rediscover the exact campaign, seat, list or enrollment in the same authorized workspace. Inspect the refusal message to identify which lookup failed; do not substitute another organization or guessed resource id.
Related actions
Evidence and limits
-
contracts/sending.mjs -
apps/api/src/enrollments/enrollments-handlers.ts -
apps/api/src/enrollments/enrollments-repository.ts -
apps/api/src/enrollments/enrollments-domain.ts -
apps/api/src/enrollments/messages-repository.ts -
apps/api/src/audience/lists-repository.ts -
apps/api/src/tasks/tasks-repository.ts -
apps/api/src/sending/outreach-eligibility.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.