Topolo Docs Topolo Platform
Open workspace

List enrollment messages

Read retained message versions, approval and transport state for one enrollment.

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

FieldRequiredMeaning and constraints
idYesRequired 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.

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.