AgentExchange

Help & Support

Elevate Your Agents with AgentExchange Solutions
Easily enhance Agentforce with pre-built AI solutions from AgentExchange. Browse, install, and deploy agents, actions, and apps directly into your environment. Tell Me More

Have Questions?

Service De Channel Routing Configure

Beta
undefinedFiles
  • SKILL.md

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-consent-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:

Type SessionHandler target Id prefix FallbackQueue
Omni-Channel Queue Group (Type=Queue) 00G must be null
Omni-Flow FlowDefinition (ProcessType=RoutingFlow) 300 required
Agentforce Service Agent (ASA) BotDefinition (Type=ExternalCopilot) 0Xx required
Digital Worker AgenticCtxtDecorDefinition 1iE required
User User (with a RoutingConfiguration) 005 must be null

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

Reference file Load when
references/queue-creation.md The user picked "create a new queue" on the Queue routing path — full Metadata API scaffold → deploy → ID lookup → optional member add.
references/target-locate.md You need the per-domain SOQL to locate and validate an eligible target (Queue, Flow, ASA, Digital Worker, User) and the FallbackQueue requirement matrix.
references/asa-routing.md The user picked ASA (Agentforce Service Agent) routing — precondition check, enumerating live ASAs, and selection.
references/gotchas.md Troubleshooting an unexpected result, or before modifying this skill — the known gotchas.
references/worked-examples.md You want a reference run of the reuse-existing-queue, create-new-queue, Flow, or ASA paths.

When NOT to use this skill

  • The channel already has SessionHandlerId or FallbackQueueId set. This skill no-ops and tells you — nothing to do. Proceed to activation.
  • 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 SessionHandlerId manually 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-char MessagingChannel.Id (prefix 0Mj). The channel must already exist.
  • {ORG_ALIAS} — optional; the sf CLI target-org alias. Default: whatever sf config get target-org returns. All SOQL, PATCH, and Metadata deploys run against this org.

Output (to caller)

One of:

Success — no change needed:

{"ok": true, "noop": true, "routingType": "queue|flow|asa|digital_worker|user", "sessionHandlerId": "00G...|300...|0Xx...|1iE...|005...", "fallbackQueueId": "00G...|null", "targetName": "...", "message": "Routing already configured"}

Success — Queue routing configured:

{"ok": true, "routingType": "queue", "sessionHandlerId": "00G...", "fallbackQueueId": null, "queueName": "...", "queueDeveloperName": "...", "created": true|false}

Success — Flow routing configured:

{"ok": true, "routingType": "flow", "sessionHandlerId": "300...", "fallbackQueueId": "00G...", "flowName": "...", "flowDeveloperName": "...", "created": false}

Success — ASA routing configured:

{"ok": true, "routingType": "asa", "sessionHandlerId": "0Xx...", "fallbackQueueId": "00G...", "asaName": "...", "asaDeveloperName": "...", "botUserId": "005...", "botVersionId": "0X9...", "created": false}

Success — Digital Worker routing configured:

{"ok": true, "routingType": "digital_worker", "sessionHandlerId": "1iE...", "fallbackQueueId": "00G...", "workerName": "...", "created": false}

Success — User routing configured:

{"ok": true, "routingType": "user", "sessionHandlerId": "005...", "fallbackQueueId": null, "userName": "...", "created": false}

Precondition not met:

{"ok": false, "kind": "no-eligible-target", "routingType": "flow|asa|digital_worker|user", "hint": "no eligible <target> found on this org — <how to create one in Setup>, then re-run this skill"}
{"ok": false, "kind": "no-fallback-queue", "hint": "Flow/ASA/Digital Worker routing requires a FallbackQueue but no MessagingSession-capable queue exists — create one (Queue routing path) first"}
{"ok": false, "kind": "asa-not-supported", "hint": "this org doesn't have BotDefinition (Agentforce not licensed); use Queue routing instead"}
{"ok": false, "kind": "standard-channel", "hint": "this is a Standard (SCRT1) channel — only Flow routing is supported; these skills only create Enhanced channels, so this is unexpected"}

Failure:

{"ok": false, "kind": "metadata-deploy-failed", "message": "..."}
{"ok": false, "kind": "patch-failed", "message": "..."}
{"ok": false, "kind": "verify-failed", "hint": "PATCH returned success but re-read shows SessionHandlerId still null — permission or trigger issue"}

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.

sf data query --target-org '{ORG_ALIAS}' \
  --query "SELECT Id, DeveloperName, SessionHandlerId, FallbackQueueId FROM MessagingChannel WHERE Id = '{CHANNEL_ID}'" \
  --json > /tmp/ccr-channel.json

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):

Prefix Domain / Target Lookup query routingType
00G Group (Type=Queue) SELECT Id, Name, DeveloperName FROM Group WHERE Id='<sh>' AND Type='Queue' queue
300 FlowDefinition SELECT DurableId, Label, ApiName FROM FlowDefinitionView WHERE DurableId='<sh>' flow
0Xx BotDefinition (ASA) SELECT Id, DeveloperName, MasterLabel, AgentType FROM BotDefinition WHERE Id='<sh>' asa
1iE AgenticCtxtDecorDefinition (Digital Worker) SELECT Id, DeveloperName, MasterLabel FROM AgenticCtxtDecorDefinition WHERE Id='<sh>' digital_worker
005 User SELECT Id, Name FROM User WHERE Id='<sh>' user
SESSION_HANDLER_ID="$(node -e 'console.log(JSON.parse(require("fs").readFileSync("/tmp/ccr-channel.json","utf8")).result.records[0].SessionHandlerId)')"
PREFIX="${SESSION_HANDLER_ID:0:3}"
case "$PREFIX" in
  00G) sf data query --target-org '{ORG_ALIAS}' --query "SELECT Id, Name, DeveloperName FROM Group WHERE Id = '$SESSION_HANDLER_ID' AND Type = 'Queue'" --json > /tmp/ccr-noop-target.json ;;
  300) sf data query --target-org '{ORG_ALIAS}' --query "SELECT DurableId, Label, ApiName FROM FlowDefinitionView WHERE DurableId = '$SESSION_HANDLER_ID'" --json > /tmp/ccr-noop-target.json ;;
  0Xx) sf data query --target-org '{ORG_ALIAS}' --query "SELECT Id, DeveloperName, MasterLabel, AgentType FROM BotDefinition WHERE Id = '$SESSION_HANDLER_ID'" --json > /tmp/ccr-noop-target.json ;;
  1iE) sf data query --target-org '{ORG_ALIAS}' --query "SELECT Id, DeveloperName, MasterLabel FROM AgenticCtxtDecorDefinition WHERE Id = '$SESSION_HANDLER_ID'" --json > /tmp/ccr-noop-target.json ;;
  005) sf data query --target-org '{ORG_ALIAS}' --query "SELECT Id, Name FROM User WHERE Id = '$SESSION_HANDLER_ID'" --json > /tmp/ccr-noop-target.json ;;
  *)   echo "{\"records\":[{\"Id\":\"$SESSION_HANDLER_ID\"}]}" > /tmp/ccr-noop-target.json ;;
esac

Report the success-noop envelope (include the existing FallbackQueueId from the Stage 1 read) and 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:

sf data query --target-org '{ORG_ALIAS}' \
  --query "SELECT Id, PlatformType FROM MessagingChannel WHERE Id = '{CHANNEL_ID}'" --json > /tmp/ccr-platform.json

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):

The MessagingChannel '{developerName}' ({CHANNEL_ID}) has no routing configured.
Which routing type do you want?

  1) queue          — Omni-Channel queue routing
  2) flow           — Omni-Flow (RoutingFlow) + fallback queue
  3) asa            — Agentforce Service Agent (requires Agentforce license) + fallback queue
  4) digital_worker — Digital Worker (Agentic) + fallback queue
  5) user           — Direct to a specific user (requires a RoutingConfiguration on the user)

Pick [1-5]:

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 User that 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.

# MessagingSession-capable queues that are also Omni-enabled (QueueRoutingConfigId != null)
sf data query --target-org '{ORG_ALIAS}' \
  --query "SELECT Id, Name, DeveloperName FROM Group WHERE Type = 'Queue' AND QueueRoutingConfigId != null AND Id IN (SELECT QueueId FROM QueueSobject WHERE SobjectType = 'MessagingSession') ORDER BY Name" \
  --json > /tmp/ccr-queues.json

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:

Omni-enabled MessagingSession queues on {ORG_ALIAS}:

  1) Messaging Queue (Messaging_Queue) — 00GSG0000000LSf2AM
  2) MyQueue (MyQueue) — 00GSG000000144n2AA
  ...
  N) Create a new queue

Pick [1-N]:
  • If the user picks an existing queue: record its Id as {QUEUE_ID} (this is the Group.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}:

routingType {TARGET_ID} FallbackQueue write
queue 00G Group.Id none (must stay null)
flow 300 FlowDefinition id set FallbackQueueId={FALLBACK_QUEUE_ID}
asa 0Xx BotDefinition.Id set FallbackQueueId={FALLBACK_QUEUE_ID}
digital_worker 1iE AgenticCtxtDecorDefinition.Id set FallbackQueueId={FALLBACK_QUEUE_ID}
user 005 User.Id none (must stay null)

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:

# queue / user — SessionHandler only
sf data update record --target-org '{ORG_ALIAS}' --sobject MessagingChannel \
  --record-id '{CHANNEL_ID}' \
  --values 'SessionHandlerId={TARGET_ID}' --json > /tmp/ccr-patch.json

# flow / asa / digital_worker — SessionHandler + FallbackQueue together
sf data update record --target-org '{ORG_ALIAS}' --sobject MessagingChannel \
  --record-id '{CHANNEL_ID}' \
  --values 'SessionHandlerId={TARGET_ID} FallbackQueueId={FALLBACK_QUEUE_ID}' --json > /tmp/ccr-patch.json

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:

sf data query --target-org '{ORG_ALIAS}' \
  --query "SELECT Id, SessionHandlerId, FallbackQueueId FROM MessagingChannel WHERE Id = '{CHANNEL_ID}'" \
  --json

If the freshly-read SessionHandlerId doesn't equal {TARGET_ID} — or (for flow/asa/digital_worker) FallbackQueueId doesn't equal {FALLBACK_QUEUE_ID}:

{"ok": false, "kind": "verify-failed", "hint": "PATCH returned success but re-read shows SessionHandlerId/FallbackQueueId not persisted — check user permissions or field-level security on MessagingChannel"}

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.

Related Skills

  • SkillSkill
    Salesforce
    Service De Channel Activate

    Salesforce

    Activate an Enhanced `MessagingChannel` (WhatsApp / Apple / Facebook / SMS / RCS) by PATCHing `MessagingChannelUsage.DeploymentStatus` from `Disabled` to `Provisioning` via the standard REST sobject endpoint. The UDD save-hook fires on the REST write, dis

  • SkillSkill
    Salesforce
    Service De Channel Consent Configure

    Salesforce

    Configure consent (opt-in/opt-out) on an Enhanced `MessagingChannel` so activation will accept it. Given a `{CHANNEL_ID}`, sets the channel's `ConsentType` (ImplicitOptIn / ExplicitOptIn / DoubleOptIn) and the `OptInPrompt` / `DoubleOptInPrompt` prompt fi

  • SkillSkill
    Salesforce
    Service De Channel Create

    Salesforce

    **INTERNAL USE ONLY — invoked by `service-de-headless-channel-configure` orchestrator.** Insert an Enhanced messaging `MessagingChannel` record (plus its child `MessagingChannelUsage` as a free server-side side-effect) of any supported type — WhatsApp, LI

  • SkillSkill
    Salesforce
    Service De Headless Channel Configure

    Salesforce

    Top-level orchestrator: given a messaging channel type and its type-specific inputs, produces an activated Enhanced `MessagingChannel` in the target Salesforce org — without the Meta/LINE/Apple setup popups. Prompts for `{MESSAGE_TYPE}` if not supplied, t