Configuring Channel Routing
What this skill does
Ensures a MessagingChannel has valid routing configured before activation. The channel's SessionHandler field is a polymorphic foreign key (verified against MessagingChannel.entity.xml, domain="Queue, FlowDefinition, User, BotDefinition, AgenticCtxtDecorDefinition") — it names who the channel routes incoming sessions to. Some targets additionally require a FallbackQueue (an Omni-Channel Queue that catches sessions the primary target can't take).
These skills create Enhanced channels (PlatformType=Enhanced, SCRT2). All five SessionHandler domains are writable on Enhanced channels. (A Standard/SCRT1 channel would only accept a Flow as SessionHandler — the server rejects any other domain with "Only flows of type Omni-Channel are supported". These skills never create Standard channels, so that path isn't handled here.)
Where this fits: the channel is inserted by service-de-channel-create (or a per-type leaf); this skill sets routing; service-de-channel-settings-configure sets consent; then service-de-channel-activate flips it live — activation requires both routing and consent. The service-de-headless-channel-configure orchestrator runs all four in sequence.
Supported routing types — all set SessionHandlerId, some also set FallbackQueueId:
Provisioning behavior:
- Queue — pick an existing
MessagingSession-capable Queue, or create a new Queue + QueueRoutingConfig via Metadata API. - Flow / ASA / Digital Worker / User — locate an existing eligible target and PATCH it. These skills do not create Flows, bots, digital workers, or users — if none eligible exist, the skill reports the precondition and points the user at Setup.
Reference File Index
When NOT to use this skill
- The channel already has
SessionHandlerIdorFallbackQueueIdset. This skill revalidates the existing target before no-op. An ASA must still have a BotUser and an Active BotVersion; otherwise it reportsasa-target-inactiveand does not claim readiness. - The channel doesn't exist yet. Run the insertion skill first; this skill expects a real
MessagingChannel.Id. - You want to replace existing routing. Safer to clear
SessionHandlerIdmanually in the UI, then re-run this skill. The no-op check is a guardrail, not a limitation worth bypassing automatically.
Inputs (from caller)
{CHANNEL_ID}— a 15- or 18-charMessagingChannel.Id(prefix0Mj). The channel must already exist.{ORG_ALIAS}— optional; thesfCLI target-org alias. Default: whateversf config get target-orgreturns. All SOQL, PATCH, and Metadata deploys run against this org.
Output (to caller)
One of:
Success — no change needed:
Success — Queue routing configured:
Success — Flow routing configured:
Success — ASA routing configured:
Success — Digital Worker routing configured:
Success — User routing configured:
Precondition not met:
Failure:
The PATCH failure message often carries the server-side validation error verbatim (from MessagingChannelFunctionsHelper.validateSessionHandler). Surface it — it tells the user exactly which linking rule failed. Common ones: MissingFallbackQueueForFlowRouting / ...ForAsaRouting / ...ForDigitalWorkerRouting (FallbackQueue required but null), UnsupportedFallbackQueue (FallbackQueue set on a Queue/User path where it must be null), InvalidSessionHandlerFlowType (Flow isn't ProcessType=RoutingFlow, or the channel is Standard/SCRT1), NoRoutingConfigDefined (User has no RoutingConfiguration).
Stage 1: Read current routing state
Query the channel. If it already has SessionHandlerId OR FallbackQueueId, no-op. Otherwise, continue.
Parse with node -e or jq. If the record is missing — halt with Error: Channel {CHANNEL_ID} not found — check the id, or run the insertion skill first.
If SessionHandlerId is non-null, branch on the Id prefix to figure out what the existing routing target is, then no-op with the right envelope. The prefix maps 1:1 to the SessionHandler domain (verified against MessagingChannel.entity.xml). Existing routing is not automatically healthy: the target lookup must return exactly one eligible row before reporting a successful no-op.
For an existing ASA (0Xx), require a non-null BotUserId and one returned BotVersions row with Status='Active'. If either is missing, return the following failure instead of a successful no-op. Do not clear or replace the channel routing automatically:
For other prefixes, require exactly one target lookup row. Report the success-noop envelope (include the existing FallbackQueueId from the Stage 1 read) only after the target-specific eligibility check succeeds, then return.
If FallbackQueueId is non-null but SessionHandlerId is null — still no-op. That's a partially-configured Flow/ASA state; don't touch it, but flag it in the envelope message ("FallbackQueue set but SessionHandler null — incomplete routing, review in Setup") since activation will still fail readiness without a SessionHandler.
Stage 2: Confirm the channel is Enhanced, then choose routing type
First confirm PlatformType. The Stage 1 read didn't include it — add it, or re-query:
If PlatformType != 'Enhanced' (i.e. Standard/SCRT1), only Flow routing is writable. These skills only create Enhanced channels, so a Standard channel here is unexpected — emit {ok:false, kind:"standard-channel", ...} and stop rather than guessing.
Then ask the user (via a prompt — do NOT auto-pick):
Branch on the user's pick. For every non-queue path, load references/target-locate.md — it holds the per-domain locate SOQL, eligibility filters, FallbackQueue rules, and exact PATCH shape:
- 1 (queue) — continue to Stage 3-Queue below.
- 2 (flow) — locate an eligible
FlowDefinition(ProcessType=RoutingFlow, active version) + a FallbackQueue, then Stage 5. - 3 (asa) — continue to Stage 3-ASA; ASA requires a FallbackQueue too.
- 4 (digital_worker) — locate an eligible
AgenticCtxtDecorDefinition+ a FallbackQueue, then Stage 5. - 5 (user) — locate a
Userthat has a RoutingConfiguration, then Stage 5.
If a path finds no eligible target, emit {ok:false, kind:"no-eligible-target", routingType, hint} (see references/target-locate.md for the per-domain Setup pointer) and return — do not fabricate an id.
Stage 3-Queue: Pick an existing queue, or create a new one
(This stage runs only on the Queue path. ASA path: see Stage 3-ASA below.)
Enumerate eligible queues. The UI's routing dropdown (MessagingRoutingMethodDataProviderController.getOmniQueues) lists Groups where QueueRoutingConfigId != null — i.e. Omni-Channel-enabled queues. For a MessagingChannel we additionally want the queue to accept MessagingSession. Query the intersection: enumerate MessagingSession-capable queues, then keep only those with a QueueRoutingConfig.
Each row's Id is the 00G Group.Id — that is what gets written to SessionHandlerId. (If the semi-join subquery errors on an org, fall back to two queries: list QueueSobject WHERE SobjectType='MessagingSession', then filter to those whose Group.QueueRoutingConfigId != null.)
Present the user with a numbered list plus a final "create new" option:
- If the user picks an existing queue: record its
Idas{QUEUE_ID}(this is theGroup.Id). Skip to Stage 5. - If the user picks "create new": go to Stage 4.
If the list is empty AND the user picks (1) "Create a new queue" implicitly, skip directly to Stage 4.
Stage 3-Queue, continued: Create a new Queue via Metadata API
(Continuation of the Queue path. Skip if the user picked an existing queue above.)
We deploy a Queue + QueueRoutingConfig pair using a minimal scratch sfdx project (the help-agent-accelerator pattern), then look up the new Queue's Id and optionally add the current user as a member.
To create a new Queue via the Metadata API (sfdx project scaffold → deploy → ID lookup → optional member add), load references/queue-creation.md and follow it — it is the complete guide for this flow.
After that stage, jump to Stage 5 (PATCH).
Stage 3-ASA: Pick an existing ASA
(This stage runs only on the ASA path. Queue path: skip to Stage 5.)
ASA routing points SessionHandlerId at an existing, live Agentforce Service Agent (BotDefinition). This skill never creates a new ASA — it verifies the org supports one (Agentforce licensed), enumerates the live/routable ones, and lets the user pick.
For ASA precondition checks, enumeration, and selection, load references/asa-routing.md. After selection, continue to Stage 5 (PATCH).
Stage 5: PATCH MessagingChannel.SessionHandlerId (+ FallbackQueueId)
By this point we have a routing target id in {TARGET_ID} and know its {ROUTING_TYPE}:
SessionHandler is a polymorphic FK spanning [Group, FlowDefinition, User, BotDefinition, AgenticCtxtDecorDefinition] (per MessagingChannel.entity.xml; the BotDefinition/AgenticCtxtDecorDefinition targets only materialize on Agentforce-licensed orgs). The standard REST sObject PATCH accepts whichever Id type fits. The FallbackQueue rule is enforced server-side (validateSessionHandler): Flow/ASA/Digital Worker reject a null FallbackQueue; Queue/User reject a non-null one.
For the FallbackQueue paths, write both fields in one PATCH so the record never passes through an invalid intermediate state:
If status !== 0: emit {ok:false, kind:"patch-failed", message: ...} and return. Surface the server message verbatim — it names the exact linking rule that failed (see the failure-envelope note under "Output (to caller)").
Stage 6: Verify
Re-read the channel:
If the freshly-read SessionHandlerId doesn't equal {TARGET_ID} — or (for flow/asa/digital_worker) FallbackQueueId doesn't equal {FALLBACK_QUEUE_ID}:
Otherwise, emit the path-appropriate success envelope (see "Output (to caller)" at the top of this file — one shape per routingType, each carrying fallbackQueueId).
Stage 7: Report to caller
Report the JSON envelope. If this skill is the leaf (user invoked it directly), render:
Queue path:
Success — Routing configured — channel {CHANNEL_ID} now routes to Queue '{QUEUE_LABEL}' ({QUEUE_ID}).{'' if created else ' (reused existing queue)'}Info: Routing already configured — Queue '{name}' ({QUEUE_ID}). No changes.(no-op path)
Flow path:
Success — Routing configured — channel {CHANNEL_ID} now routes to Omni-Flow '{FLOW_LABEL}' ({TARGET_ID}), fallback Queue {FALLBACK_QUEUE_ID}.
ASA path:
Success — Routing configured — channel {CHANNEL_ID} now routes to Agentforce Service Agent '{ASA_LABEL}' ({ASA_ID}), fallback Queue {FALLBACK_QUEUE_ID}. Bot user: {BOT_USER_ID}. Active version: {BOT_VERSION_ID}.Info: Routing already configured — ASA '{MasterLabel}' ({ASA_ID}). No changes.(no-op path)
Digital Worker path:
Success — Routing configured — channel {CHANNEL_ID} now routes to Digital Worker '{WORKER_LABEL}' ({TARGET_ID}), fallback Queue {FALLBACK_QUEUE_ID}.
User path:
Success — Routing configured — channel {CHANNEL_ID} now routes directly to User '{USER_NAME}' ({TARGET_ID}).
Failure / precondition:
Warning: No eligible {routingType} target found. {per-domain Setup pointer from references/target-locate.md}. Then re-run this skill.(no-eligible-target)Warning: {routingType} routing needs a fallback queue but none exists. Create a MessagingSession queue first (queue path), then re-run.(no-fallback-queue)Warning: This org doesn't have Agentforce licensed (no BotDefinition entity). Use Queue routing instead.(asa-not-supported)Warning: This is a Standard (SCRT1) channel — only Flow routing is supported. These skills only create Enhanced channels, so this is unexpected; check the channel.(standard-channel)Error: {kind}: {message}(other failures — the message names the server-side validation rule that failed)
Worked examples
For reference runs of both the reuse-an-existing-queue fast path (validated on test1) and the create-a-new-queue-via-Metadata-API path, see references/worked-examples.md.
Gotchas
Known gotchas — Group.Type filtering, DeveloperName uniqueness, QueueRoutingConfig naming, empty-queue caveats, Metadata-API-only queue creation, the per-domain FallbackQueue requirement matrix, the server-side nullQueueId / readiness enforcement point, the Standard-vs-Enhanced SessionHandler write restriction, and SessionHandler polymorphism across org shapes.
When troubleshooting an unexpected result, or before modifying this skill, load references/gotchas.md and follow it — it is the complete list.


