Skip to main content
Version: Latest

Agents, Queues, Routing, and Dialer

This phase adds the operational core of the Contact Center: agent presence, work queues, reservations, skill-aware routing, availability-based assignment, and an outbound dialer that routes voice calls through Contact Center Voice providers. Each capability is a separate, feature-gated module so tenants enable only what they need.

Features​

FeatureFeature IDPurpose
Contact Center AgentsCrestApps.OrchardCore.ContactCenter.AgentsAgent profiles, reason codes, and queue/campaign sign-in, plus canonical routing availability, durable sessions, heartbeat tracking, capacity projection, after-call recovery, and logout synchronization without requiring SignalR.
Contact Center Agent EntitlementsCrestApps.OrchardCore.ContactCenter.AgentEntitlementsOptional. Restricts which queues and campaigns each agent may sign in to, with the Agent entitlements administration screen. When it is disabled, any agent may sign in to any queue or campaign.
Contact Center Business HoursCrestApps.OrchardCore.ContactCenter.BusinessHoursBusiness-hours calendars, their administration screen, and the evaluation service used by queues, entry points, and automated sends. Enabled on its own or pulled in by Work Distribution.
Contact Center Work DistributionCrestApps.OrchardCore.ContactCenter.QueuesManaged skills, work queues, queue items, and reservations, plus policy-based routing strategies and availability-based activity assignment over Contact Center queues.
Contact Center Outbound DialerCrestApps.OrchardCore.ContactCenter.DialerOutbound profiles, callbacks, Preview activity loads routed through Contact Center Voice, and mandatory eligibility, suppression, retry, do-not-call, and calling-window enforcement.
Contact Center Paced DialingCrestApps.OrchardCore.ContactCenter.Dialer.PacedCompliance-gated Power and Progressive strategies, paced batch source, and scheduled pacing. Its base Dialer dependency includes the Dialer Profiles UI.
Contact Center Inbound VoiceCrestApps.OrchardCore.ContactCenter.InboundVoiceInbound voice entry-point administration, business-hours qualification, closed actions, and queue ingress.
Contact Center Call RecordingCrestApps.OrchardCore.ContactCenter.RecordingOptional recording orchestration and recording-state events over Contact Center Voice.
Contact Center Voice MediaCrestApps.OrchardCore.ContactCenter.Voice.MediaDependency-only, non-GA executable media resolution foundation; transport certification is deferred to R9.
Contact Center Real-TimeCrestApps.OrchardCore.ContactCenter.RealTimeShared SignalR hub and real-time presence, offer, and queue projections over the workforce availability state. Enabled by dependency only (auto-enabled by Contact Center Voice and Supervision).
Contact Center Supervision & Live DashboardCrestApps.OrchardCore.ContactCenter.SupervisionLive supervisor dashboard, queue and agent monitoring state, and provider-capability-gated monitoring actions.

The server-side voice orchestration (CrestApps.OrchardCore.ContactCenter.Voice) is enabled automatically as a dependency of Inbound Voice, the Dialer, Recording, and Supervision, so it is not a separately selectable feature. It in turn pulls in Contact Center Real-Time and the phone-number administration, which is where outbound lines are set up.

The enterprise report catalog (executive, interaction, queue/SLA, agent, transfer, recording, campaign, and subject reports plus CSV exports) is not a separately selectable feature. It activates automatically under the shared Reports area whenever both Contact Center Work Distribution (CrestApps.OrchardCore.ContactCenter.Queues) and the Reports framework (CrestApps.OrchardCore.Reports) are enabled.

The CRM-integrated Agent Workspace and each provider contact center adapter (for example Asterisk voice and Asterisk media) are integration glue rather than selectable features. The Agent Workspace activates whenever Contact Center Agents, Contact Center Voice, Contact Center Real-Time, and the Telephony soft phone are all enabled. A provider's voice adapter activates whenever that provider module and Contact Center Voice are both enabled (the Asterisk media adapter, whenever the Asterisk module and Contact Center Voice Media are both enabled), so an operator never enables a per-provider toggle that has to match the provider they already configured.

Agents and presence​

An agent profile links an Orchard user to Contact Center configuration: display name, capacity, administrator-assigned skills, queue membership, campaign membership, and live presence. Presence states include Offline, Available, Break, Away, DoNotDisturb, Meeting, Training, AfterHoursUnavailable, and system-managed states such as Reserved, Busy, and WrapUp.

Agents sign in from the floating Telephony soft phone. When the Contact Center queues feature is enabled, Contact Center contributes a Work tab where agents select the queues and campaigns they want to receive work from and sign out. Signing in sets presence to Available; signing out sets it to Offline, clears the current queue/campaign membership, and Orchard logout runs the same sign-out path after the Orchard logout request completes successfully so the browser is not left spinning on logout. When the Voice feature is enabled, signing in or returning to Available immediately offers any already-waiting inbound voice work from the selected queues instead of waiting for a new inbound call. The ContactCenterSignIntoQueues permission grants self-service sign-in.

Presence is a dropdown in the soft-phone header so agents can change availability without switching tabs. Request break is system-approved: if no assignment is in progress, the request is granted immediately and the agent enters Break; if a route/reservation is already in progress, the request is kept pending while the call continues, and the system grants Break automatically when that in-flight work is released. Agents in RequestBreak or Break are not eligible for new routing decisions.

Routing never treats the profile presence value by itself as proof that an agent can receive work. The Agents feature computes a canonical projection by joining an Available profile and queue entitlement with the agent's selected session queue, online connection state, fresh heartbeat, and remaining interaction capacity. The last connection disconnect therefore removes the agent from routing immediately without destroying the profile's requested presence during a transient reconnect; stale-session cleanup later performs the durable sign-out. Reservation creation repeats the canonical check while it holds the activity and agent transition locks, closing the race where a client disconnects after candidate selection.

Queue membership is normalized into a query-aligned index. Each agent profile projects one membership row per queue it is both entitled to and signed in to (the intersection of live sign-in and the administrator-owned allow-list), so selecting the candidate agents for a queue is a single indexed lookup by queue and Available presence rather than a tenant-wide scan of every available agent. The index stores queue identifiers lower-cased for portable, case-insensitive matching and preserves the fail-closed entitlement rule: an agent with no matching allow-list entry is never a member.

Who fills that allow-list depends on the optional Contact Center Agent Entitlements feature. When it is enabled, an administrator grants each agent's queues and campaigns on the Agent entitlements screen, and the agent can sign in only to those. When it is disabled, entitlements are permissive: any agent may sign in to any queue or campaign, and the queues and campaigns they pick are granted onto their profile as they sign in.

Availability policy is tenant configuration:

{
"CrestApps": {
"ContactCenter": {
"Availability": {
"HeartbeatTimeout": "00:01:30",
"MaximumWrapUpDuration": "00:15:00",
"OrphanedBusyGracePeriod": "00:01:00"
}
}
}
}

HeartbeatTimeout controls how old a session heartbeat may be before routing treats the session as disconnected. MaximumWrapUpDuration is the server-owned after-call deadline. A tenant background task runs each minute and releases agents whose wrap-up interaction exceeded that deadline or whose WrapUp presence has no matching pending wrap-up interaction. Deadline recovery records the interaction's wrap-up completion time and restores the pending/default presence; it does not complete the CRM activity or invent a disposition. OrphanedBusyGracePeriod (default one minute) is how long an agent may stay Busy after the call they accepted has ended; once it passes and the agent has no other active interaction, the same task returns them to work. Normally the call's own end releases the agent within moments, so this only catches an end the system missed.

Presence state reference​

StateSet byMeaning and routing behavior
OfflineAgent/systemSigned out and ineligible for all work.
AvailableAgent/systemReady for work. A transition to this state publishes an event that triggers queued voice recovery in a separate service scope.
ReservedSystemAn offer is assigned but not yet accepted. The agent cannot receive another offer beyond configured capacity.
BusySystemThe agent accepted and is actively handling an interaction.
WrapUpSystemThe answered interaction ended and after-call work is pending. The agent remains ineligible until the CRM activity is completed or the server-owned recovery deadline releases orphaned capacity.
RequestBreakAgentA request to enter Break; when work is already reserved or active, the request is stored and granted after the work is released or completed.
BreakAgent/systemA granted break. The agent is signed in but ineligible for work.
AwayAgentNot ready because the agent is away from the desk.
DoNotDisturbAgentNot ready and should not receive work.
MeetingAgentNot ready because the agent is in a meeting.
TrainingAgentNot ready because the agent is in training.
AfterHoursUnavailableAgent/systemNot ready outside staffed hours.

Reserved, Busy, and WrapUp are system-managed. Agents should not manually force those states. A completed wrap-up returns the agent to the pending requested state, when one exists, or otherwise to the default ready state (Available for a signed-in agent and Offline for a signed-out agent).

Every sign-in, sign-out, and presence transition is stored as a durable Contact Center event with the previous state, current state, requested state, reason code, queue memberships, campaign memberships, and transition time. This event history supports calculations such as available time, break time, not-ready time, queue/campaign staffing time, and state-transition audits. Voice interactions separately record creation, answer, end, wrap-up-start, and wrap-up-completion timestamps; reports include talk time, wrap-up time, and average handle time.

Agent state reason codes​

Administrators define reason codes from Interaction Center → Management → Agent states so agents pick an auditable, standardized reason when they go not ready. A reason code has a unique name, an optional description, the presence state it places the agent in (AppliesTo — Break, Away, DoNotDisturb, Meeting, Training, or AfterHoursUnavailable), a sort order, and an enabled flag. The catalog is managed with the same display-driver CRUD pattern as Skills and queues, and the ManageContactCenterAgents permission gates it.

When reason codes are configured, the soft-phone presence dropdown lists them (ordered by sort order) in place of the fixed not-ready states. A reason that applies to Break submits a break request with that reason, so the break starts at once when the agent is idle, or when the current work ends if work is holding them. Any other reason sets the agent's presence to the reason's AppliesTo state directly. Either way the reason is recorded on the agent profile and the AgentPresenceChanged event. If no reason codes exist, the dropdown falls back to the built-in not-ready states. Available and Offline are always listed.

The Agents feature seeds a standard set of reason codes at setup (short break, lunch, away from desk, team meeting, training, coaching, and system issue) by running the agent-state-reason-codes module recipe. Reason codes are also importable through the AgentStateReasonCode recipe step so they can be seeded or moved between tenants in deployment recipes.

Skills​

Administrators manage routeable capabilities from Interaction Center → Management → Skills. A skill has a unique name, description, and enabled state. Enabled skills appear in admin assignment surfaces and queue editor selectors; disabled skills remain on existing agents and queues but are hidden from new selections. Agents do not self-select skills from the soft phone because skills are routing eligibility data owned by supervisors/administrators.

Queues can require one or more skills. Skills are held at a proficiency from 1 to 5. A skill in the queue's Required skills list needs proficiency 3 or higher; the Skill requirements table can set a different minimum proficiency per skill, mark a skill as preferred rather than required, and relax a requirement after a wait. Agents must meet every enforced requirement to be eligible for that queue, and the routing chain filters out agents who miss one before the queue's scoring strategy runs.

Skill names are compared by the same rule wherever they are read: surrounding whitespace is not part of the name, and casing does not distinguish one skill from another, while interior spacing does — Tier 2 and Tier2 are two different skills. Because a queue's required skills and an agent's assigned skills are read through that one rule, an agent whose skills arrived through a recipe, a deployment, or an import is matched the same way as one edited in the administration screens.

Queues, reservations, and assignment​

A queue holds activities waiting for an agent, with a default priority, an SLA threshold, required skills, an optional inbound channel endpoint mapping, a reservation timeout, a routing policy, an optional business-hours calendar, and optional overflow settings. Activities enter a queue as queue items; the system pairs the highest-priority, oldest waiting item with an eligible available agent signed in to that queue and creates a short-lived reservation.

Administrators can organize queues under Interaction Center → Management → Queue groups and select an optional group in each queue editor. Queue groups are catalog and reporting metadata only: they do not provide routing defaults, SLA inheritance, agent entitlements, capacity, overflow, or any other queue behavior. The dedicated Manage Contact Center queue groups permission controls the group catalog, while queue configuration remains controlled by Manage Contact Center queues.

Queue-group reports use current-membership semantics. An interaction keeps its queue identifier, while the report resolves that queue's group from the current queue catalog when the report runs. Moving a queue to another group therefore changes the group attribution of its historical interactions; it does not rewrite or reroute those interactions. Deleting a queue group makes its assigned queues ungrouped. Queue Usage includes per-queue rows, queue-group aggregate rows, and a recalculated grand total.

If no eligible agent is available when an activity enters the queue, the activity remains durable waiting work; it is not rejected merely because no agent is immediately available. Signing in, returning to Available, the assignment background task, or another routing trigger can offer it later. Business-hours, overflow, reservation-timeout, voicemail, and rejection policies determine when waiting work should move or end.

Routing is strategy-based. The strategy chain first rejects agents that do not have every required queue skill, then rejects agents that are already handling their maximum number of concurrent interactions, then applies the queue's selected scoring strategy. Each assignment publishes an auditable routing-decision event that records the queue item, selected agent, candidate scores, and reasons, so later supervisor and analytics features can explain why work was offered to an agent.

Routing policy​

Each queue selects a primary routing strategy that decides which available, eligible agent receives the next item:

  • Longest idle (default) — offers work to the agent who has been available the longest.
  • Round robin — distributes work fairly by offering to the agent who least recently received an assignment (tracked on the agent's LastAssignedUtc, stamped when a reservation is created).
  • Least busy — offers work to the agent currently handling the fewest active interactions.

Only the selected strategy scores candidates; the other primary strategies stay inert for that queue.

When a queue enables prefer sticky agent, routing boosts the eligible candidate who most recently owned the activity (captured from the activity's assigned user when it is enqueued), so returning work prefers the agent the customer already worked with. The sticky preference is additive and never overrides skill or capacity eligibility.

When a queue enables SLA aging, a waiting item's effective priority increases by one step for every SLA-threshold interval it waits beyond the threshold, so aging work is routed ahead of newer higher-priority work instead of starving.

Agent capacity is enforced during candidate selection. Each agent profile defines MaxConcurrentInteractions (default 1), and the capacity routing strategy counts the agent's active (not ended and not failed) interactions before they can be offered new work, so an agent is never offered more concurrent interactions than they are configured to handle.

Business hours and overflow​

A queue can reference a reusable business-hours calendar (managed from Interaction Center → Management → Business hours). A calendar defines a time zone, a weekly open window per day, and all-day holiday dates. Weekly windows can cross midnight, and equal opening/closing times represent an enabled 24-hour day. While the calendar reports the queue closed, assignment pauses. The queue's after-hours action decides what happens to waiting items: Hold in queue keeps them until the queue reopens, and Overflow moves them to the configured overflow queue.

Independently of business hours, a queue may set an overflow queue and an overflow-after threshold. Waiting items that exceed the threshold are moved to the overflow queue so long-waiting work can be picked up by a broader team. Contact Center preserves the original enqueue time for SLA aging while separately tracking when the item entered its current queue, so every overflow hop receives its configured dwell time and visited queues cannot form a routing cycle. Overflow moves run each minute alongside reservation expiry and assignment.

A reservation locks the activity for one agent and can be accepted, rejected, canceled, or expired. The CRM activity moves through Available → Reserved → Assigned, mirrored on the queue item and agent presence. Canceled reservations always return the item to the queue. The Reservation timeout (seconds) setting controls how long an unanswered offer stays reserved, and Unanswered offer action controls what happens when that timeout expires: requeue the work, send the live voice call to voicemail, or reject the live voice call. Voicemail and reject are voice-only actions; the terminal reservation transition and provider-command intent commit together before provider execution, while the live interaction remains nonterminal until the provider confirms the action. A definitive provider rejection re-enqueues and reoffers the still-live call; an uncertain outcome remains isolated for reconciliation so the platform does not risk both redirecting and reoffering the same call. When there is no live provider call or command infrastructure to act on, the system safely falls back to requeueing the work instead of dropping it. An offer expires at its deadline: when the reservation is made its deadline is held in process, and within about a second of it the offer is expired and the caller moved on, under the same reservation lock and compare-and-set commit an accept takes, so an accept racing the deadline and the expiry never both win. A waiting caller's next overflow hop and maximum wait are held the same way, from when they enter a queue, and so is each queue's next treatment step (the welcome, the callback offer, the next periodic announcement); the queue-treatment background task makes one pass a minute as their backstop rather than ticking inside its own run. These in-process deadlines are an accelerator, not the record: when the tenant starts it re-arms them for every offer still ringing and every caller still waiting, and a background task also expires stale reservations and assigns waiting work every minute as the durable backstop for a restart or an offer made on another node, and — on a queue that keeps taking offers — each new or re-offered voice call also opportunistically reclaims a small bounded batch of due reservations before an agent is selected, so a lapsed offer's held capacity is freed on the next offer rather than waiting for the minute tick; this pass uses only a short, bounded lock wait so it adds at most a small, bounded delay to admitting the call, and the minute background task remains the authoritative backstop for an offer ignored on an otherwise idle queue.

Declining an inbound offer rejects that reservation and immediately makes the agent eligible according to their pending/default presence while routing tries the next eligible agent. If the agent does not respond before the reservation timeout, the queue's unanswered-offer action is applied. A call that goes back to the queue after an agent declined it, let it ring out, or transferred it there is not rung straight back to that agent while somebody else could take it: declining does not change how long an agent has been idle, and a direct call leaves the transferring agent no after-call work, so without this the same agent would be picked again at once while the next agent in line was never rung. The queue item records who declined it, in order, and who transferred it in, and routing offers it, among the agents who can take it now, to: an agent who has neither declined it nor transferred it away (chosen by the queue's routing strategy); then an agent who declined it before the latest decline, earliest first; then the agent who transferred it in; and only then the agent who declined it last. No routing strategy or sticky-agent preference overrides that order, but nobody is held back either: when the only agent free is the one who just declined (or the one who transferred the call in), the call is offered to them again straight away rather than the caller being held with an agent available. For example, an agent transfers a caller to a queue and the only other agent declines the offer: the call is offered back to the transferring agent at once. The declined offer's phone leg is hung up before the re-offer is routed, so the new offer never rings over the old one, and the queue's maximum wait, voicemail and overflow still move on a caller who keeps going unanswered. The decline order applies to queue offers only: a direct line rings the one agent it belongs to, and campaign inventory is paced by the dialer. Declining is bound to the reservation it names, so a second decline for an offer that has already been declined (from another window, or a second click) does nothing. Preview outbound work remains agent-controlled; power and progressive work is owned by the dialer pacing cycle rather than the generic inbound assignment loop. Automated dialer reservations without a valid interaction are released so pacing can retry safely instead of leaving the agent stuck in Reserved.

Assignment uses distributed locks for contention control and database invariants for correctness. Each queue's assignment runs under a per-queue lock; reservation creation acquires an activity lock and then an agent lock in a consistent order; and accept/reject/cancel/expiry transitions share a per-reservation lock. Before reserving, the service revalidates canonical session liveness and queue opt-in, the agent's current presence, pending reservation ownership, and active-interaction capacity. YesSql document-version checks make the queue, reservation, agent, and CRM activity updates one compare-and-set commit. Portable unique claim keys allow only one active queue item per activity, one pending or accepted reservation per activity, and one pending reservation per agent while retaining terminal history. A writer that loses the database race aborts the operation scope instead of reusing YesSql's canceled session, so lock expiry or overlapping holders cannot publish a second successful reservation.

Inbound offers are local atomic transitions. Assignment does not synchronously query the provider or dispatch a transport notification before commit; provider webhooks and reconciliation own provider truth, while the durable AgentReserved outbox projection delivers the offer after the reservation commit.

Dialer​

A dialer profile is an execution policy, not the source of CRM work. Activities, campaigns, subjects, activity load definitions, dispositions, and contact context still come from Omnichannel. The profile is a reusable set of dialing settings: which dialing mode is used, which Contact Center voice provider places calls, how pacing works, and how attempts/retries and compliance are bounded. It does not name a queue or a campaign. The campaign is chosen when activities are loaded: a Load Activities batch with the Dialer source picks both the campaign and the dialer profile, and for outbound work the campaign itself acts as the routing queue. Mandatory outbound compliance is part of the base Contact Center Outbound Dialer, so Preview, Power, and Progressive attempts cannot run without the eligibility and suppression gates. Enable Contact Center Paced Dialing for Power and Progressive profiles; it inherits the Dialer Profiles screen and compliance services from the base Dialer feature and runs its pacing cycle each minute. Preview profiles remain agent-driven. Dialer activity loads create unassigned activities and enqueue automated work immediately so the selected profile can reserve it without a separate operator enqueue step.

Dialing modes and safety​

Each automated mode is implemented as a dedicated IDialerStrategy, so unsupported modes are withheld rather than falling through to an unsafe default:

ModeBehavior
PreviewThe agent reviews the activity, then accepts or skips. Accepting the offer starts the outbound attempt through the configured Contact Center voice provider; no automated cycle runs.
PowerEach pacing cycle starts up to Calls per agent calls for the campaign, each reserving its own available agent. Calls per agent is capped at 3 (PowerDialerStrategy.MaxCallsPerAgent). Requires the Contact Center Paced Dialing feature.
ProgressivePlaces one call per available agent as agents become available, bounded at 100 calls per pacing cycle. Requires the Contact Center Paced Dialing feature.
PredictiveNot available. The editor hides it and saving it is rejected until answer-rate forecasting exists.

Manual is not a dialer-profile mode: agents dial freely from the soft phone under the separate manual-dialing screening described below. A profile saved as Manual by an earlier version opens and re-saves as Preview.

The Power and Progressive automated pacing modes, their strategies, scheduled pacing task, and automated batch source live in the Contact Center Paced Dialing feature, which depends on the base Contact Center Outbound Dialer. The base Dialer owns the dialer-profile editor and mandatory compliance services together with its runtime services, so enabling Paced Dialing exposes a complete configuration and execution surface while Orchard resolves the rest of the dependency graph. When that feature is disabled, the dialer-profile editor only offers Preview, and saving a Power or Progressive profile is rejected so a profile can never silently fail to pace. Preview remains on the base Contact Center Outbound Dialer feature.

Outbound compliance gate​

Before every attempt, IDialerEligibilityService runs and records an auditable DialSuppressed event when an attempt must be blocked. The default gate enforces, in order:

  • Destination present and canonical - the destination is resolved once to E.164 form before any other rule runs, using the profile's default region for numbers written in national form. A destination that cannot be resolved suppresses the attempt with NoDestination: a number the platform cannot identify cannot be screened against a do-not-call registry, and an unanswerable compliance question must not be answered by placing the call.
  • The maximum attempt count has not been reached.
  • Retry cool-down - a previous attempt must be older than RetryDelayMinutes.
  • Do-not-call / communication preferences - the contact's DoNotCall opt-out (when Respect do-not-call and communication preferences is enabled).
  • Calling window - when Enforce a calling window is enabled, the destination is only dialed while its business-hours calendar reports open. The profile selects a default calling calendar and optional per-region calendar overrides keyed by the destination's ISO 3166-1 alpha-2 region code; the calendar is evaluated in the contact's own time zone. A missing or disabled required calendar fails closed rather than silently allowing calls.
  • Abandonment cap - when Enforce an abandonment cap is enabled for an automated pacing mode (Power/Progressive), the profile's rolling live-answer/abandon statistics must stay at or below MaxAbandonmentRatePercent. The cap is only evaluated once the rolling window has accumulated at least AbandonmentSampleFloor live answers, and it fails closed: an automated profile that enforces the cap but cannot prove its current rate is suppressed. Preview binds an agent per call and is always permitted.
  • National do-not-call registries - any registered INationalDoNotCallRegistry (for example the USA FTC or Canada DNCL registries) is scrubbed when Respect do-not-call is enabled. Registries receive the canonical number, never a raw or partially normalized one, so a registry cannot be asked about a different number than the one being dialed. Registry screening fails closed: a registry that is unreachable or rejecting requests has reported nothing rather than reported the number as unlisted, and the attempt is suppressed with ComplianceScreeningUnavailable. That suppression is deliberately not terminal - the destination was never shown to be off limits, only unverified, so the activity stays available for a later cycle instead of being cancelled because a registry had a bad minute. A registry that has no credentials configured is simply not participating and does not suppress anything.

When an automated profile enforces the abandonment cap, safe-harbor messaging must be enabled with an announcement so a live party that no agent reaches hears a caller-identifying message instead of a silent drop. CrestApps:ContactCenter:Compliance:AbandonmentRollingWindowMinutes (default 30, range 1-1440) sets the rolling measurement window and is validated on start.

Answering-machine detection (AMD) outcomes reported by the provider are mapped to a provider-neutral AnswerClassification (Human, Machine, Fax, Unknown) and stored on the call session and interaction technical metadata under the stable amd_answer_classification key for downstream pacing and analytics.

Do-not-call, registry, missing-destination, and maximum-attempt suppressions are terminal and remove the queue item so the dialer cannot retry them forever. Calling-window, abandonment, cool-down, and unavailable-screening suppressions release the reservation and leave the activity available for a later cycle. Terminal provider dial failures also remove the queue item, while retryable failures remain available according to the configured retry policy.

Manual soft-phone screening​

The campaign gate above governs the automated and preview dialer paths. Agent-initiated soft-phone dials placed directly through the telephony hub do not flow through a dialer profile, so they are screened by a separate, provider-agnostic extension point. The Telephony module exposes IOutboundCallScreener; DefaultTelephonyService runs every registered screener before dispatching any origination and fails the dial closed if any screener denies it. Standalone Telephony with no screener registered dials as before.

The base Contact Center Outbound Dialer registers a manual-call screener that applies the same do-not-call and calling-window rules to soft-phone dials. The screener resolves the destination to E.164, looks up the matching contact, and suppresses the dial when the contact has opted out, when the destination is on a national do-not-call registry, or when Enforce a calling window is enabled and the destination's calendar reports closed. A destination that cannot be parsed fails closed while do-not-call enforcement is on. Every suppression publishes a ManualDialSuppressed audit event so manual originations carry the same suppression trail as campaign attempts.

Manual screening is configured under CrestApps:ContactCenter:Compliance:ManualDialing:

SettingDefaultDescription
RespectDoNotCalltrueScreens soft-phone dials against contact opt-out and national do-not-call registries, and fails closed on an unparseable destination.
EnforceCallingWindowfalseSuppresses soft-phone dials whose destination calendar reports closed.
CallingCalendarId(none)Business-hours calendar evaluated for the calling window in the contact's time zone.
DefaultRegionCode(none)ISO 3166-1 alpha-2 region used to canonicalize destinations written in national form.

Before an eligible automated attempt reaches the provider, the dialer commits the reservation as accepted, then stages the activity attempt count, interaction, stable provider command, domain event, and event-outbox record in one tenant database commit. The command identifier is sent on the provider request and in its metadata, allowing provider adapters and later reconciliation to correlate retries without creating a second logical command; an adapter for a provider API that accepts an idempotency key can forward it there. The request path never sends the provider operation itself: it only schedules a best-effort post-commit wake-up, while durable command recovery remains the authoritative executor after a process failure. If reservation acceptance fails, no interaction or provider request is created. If the atomic command-intent commit fails, compensation runs in a fresh Orchard child scope so a canceled YesSql session is never reused.

The durable provider-command state machine orchestrates these actions using explicit Pending, Claimed/Fenced, Sent, OutcomeUnknown, Confirmed, Compensating, and terminal states. Command registration is serialized per idempotency key; dispatch, reconciliation, and compensation use fenced leases; and a superseded caller treats a lost claim as a handoff to the authoritative recovery owner instead of reporting a safe-to-redial failure. Type-specific executors keep Dial compliance/projections separate from inbound Answer/Connect and timeout voicemail/reject behavior while sharing the same recovery and fencing rules. Server-side inbound offer acceptance, answered-outbound bridging, and unanswered-offer actions persist command intent before provider operations; agent-device-native acceptance remains local because the device owns its answer action. Unknown-outcome and terminal confirmation settlement commit with the related interaction, call-session, activity, domain-event, and outbox projections in one tenant database transaction. Provider transport failures and ambiguous HTTP 408, 429, and 5xx responses transition to OutcomeUnknown; deterministic client rejections remain definitive failures. ProviderCommandRecoveryBackgroundTask runs each minute and processes bounded batches selected through due-time and expired-lease indexes. Pending Dial commands are not blindly dispatched after a crash: recovery reloads the governing dialer profile and activity and re-evaluates current policy. Missing policy infrastructure, profiles, activities, or ineligible decisions fail closed and compensate the accepted work without contacting the provider. Every CAS-sensitive recovery transition runs in a fresh Orchard child scope, so a normal optimistic-concurrency ownership loss cannot reuse a canceled YesSql session or abort unrelated commands in the batch. Recovery attempts reconciliation by command key before any retry; providers that cannot prove the outcome transition safely to Paused instead of risking duplicate execution.

Callback operations​

Callbacks use the same Activity, queue, routing, and disposition path as outbound campaign calls. A CallbackRequest records the contact, destination, optional campaign and queue, requested/due window, attempt count, status, and notes. The callback dispatcher runs every minute and promotes each due pending callback into an outbound Callback activity. When the request has a queue, that activity is enqueued so the next eligible signed-in agent receives it through the Agent Workspace.

A queued callback is dialed with a built-in Preview-style profile (the agent accepts, then the platform places the call from the agent's outbound line, or the provider's default caller ID when they have none) that skips do-not-call and calling-window screening: the customer asked to be called back, so neither rule may refuse that call.

Use callbacks when an agent schedules a later follow-up, an inbound entry point offers a callback instead of waiting in queue, or workflow automation decides the next best action is a phone callback. Managers should configure a dedicated callback queue when callbacks need different SLA, skills, or priority from live inbound calls. Agents handle the promoted callback like any other outbound call: answer the offer, complete the conversation, select a disposition, and finish wrap-up through the Subject Flow.

Outbound lines​

A tenant with more than one phone number often wants different agents to call from different numbers: the sales team from the sales number and support from the support number, so a customer who calls back reaches the right people. Outbound lines do this with the phone numbers you already manage. There is no feature to enable: they are part of Contact Center Voice, which Inbound Voice, the Dialer, Recording and Supervision turn on for you.

  1. Open Channel endpoints and add each number the tenant dials from as a Phone endpoint.
  2. On each number, open Outbound line and pick the Agents who dial from this number.

Each agent dials from one number. Saving a number that lists an agent who is already on another number's line is refused with a message naming that line, and a recipe import is held to the same rule. Agents who are not on any line keep showing the provider's default caller ID, so you only need to assign the agents who should call from a different number.

The assigned number is used wherever an agent places a call:

CallNumber shown
Soft phone keypad dial, including a call the browser places itselfThe agent's line, otherwise the provider default.
Extension call to a colleagueThe agent's line, otherwise the provider default.
Dialer attempt (Preview, Power, Progressive)The Dial from number picked when the activities were loaded, then the agent's line, then the dialer profile's Caller ID, then the provider default.
Queued callbackThe agent's line, then the dialer profile's Caller ID, then the provider default.

The server always decides the number. A caller ID the browser sends with a dial is ignored, so an agent cannot show a number that was not assigned to them.

A dialer profile that must always show its own number, whoever makes the call and whatever number the load picked, can tick Always show this caller ID under Caller ID. Transfers, consult calls and supervisor legs still show the provider default.

The number has to be one your provider lets you show, which for Telnyx means a number on the account or a verified number. To send callbacks to the agent who called, point an inbound entry point for that number at the agent or their queue.

Voice Contact Center Call Router​

The dialer never talks to a telephony platform directly. It calls IVoiceContactCenterCallRouter, which resolves the configured IContactCenterVoiceProvider, so the Contact Center keeps assignment, queue, pacing, and compliance logic while the provider executes call operations. Each provider ships an adapter that implements IContactCenterVoiceProvider over its telephony provider. For example, the Asterisk adapter activates whenever the Asterisk module and Contact Center Voice are both enabled, and uses the tenant Asterisk provider when configured, otherwise resolving the configured Default Asterisk provider. Adapters are not separately selectable features.

Voice providers that support contact-center orchestration beyond soft-phone call control can also register IContactCenterVoiceProvider. The IContactCenterVoiceProviderResolver resolves those providers by technical name so future PBX integrations can participate in provider-side queueing, call assignment, and voice-specific orchestration without coupling Contact Center to one provider. Dial results include the actual executing provider identity, which is persisted on the interaction so provider events and reconciliation use the same configured alias.

Admin UX and extensibility​

Contact Center management entries live under Interaction Center. Queue groups, skills, queues, business-hours calendars, and dialer profile CRUD screens match the Omnichannel Campaigns UI: searchable list pages render summary shapes, and create/edit screens render display-driver editor shapes with the required root edit wrapper templates. Agent sign-in and presence are injected into the Telephony soft phone through DisplayDriver<SoftPhoneWidget>, so the operational controls stay with the phone while management screens remain catalog-focused.

Agent state reason codes are a catalog-backed admin surface (Interaction Center → Management → Agent states), not a provider-specific dialer setting. The Agents feature seeds standard reason codes during tenant setup by executing the agent-state-reason-codes module recipe, and the AgentStateReasonCode recipe step lets reason codes be imported or moved between tenants. A deployment plan exports them with the matching Agent State Reason Codes deployment step.

Enable via recipe​

{
"steps": [
{
"name": "Feature",
"enable": [
"CrestApps.OrchardCore.ContactCenter",
"CrestApps.OrchardCore.ContactCenter.Agents",
"CrestApps.OrchardCore.ContactCenter.Queues",
"CrestApps.OrchardCore.ContactCenter.Dialer",
"CrestApps.OrchardCore.Reports",
"CrestApps.OrchardCore.Telnyx"
]
}
]
}