Inbound Routing

Callaro uses smart inbound routing to pick the right agent for an incoming call based on the caller's recent context — not just the dialed number. This page explains how routing works, the layers involved, and how to configure or disable it.

Why smart routing

Hubspot, Salesforce, and similar platforms route by active engagement, not by static DID-to-agent mapping. Callaro applies the same idea so that:

  • A contact returning a missed campaign call lands on the same agent that tried to reach them, with full context.
  • A contact responding to a scheduled retry lands on the agent driving that campaign instead of a generic IVR.
  • A contact replying to a follow-up lands on the agent who created the follow-up.
  • Numbers that have no per-call context still fall back cleanly to the agent assigned to the dialed number.

Smart routing runs in the inbound webhook before the call is bridged. The decision and full layer trace are persisted as an inbound_routing_decision and shown on the call detail page.

How a decision is made

The resolver evaluates layers in this order. The first match wins.

Order Layer Matches when
1 Active campaign Caller has a BulkCallTask in pending, queued, placing_call, or calling state in any active campaign.
2 Retry pending Caller has a BulkCallTask in retry_pending whose retry is due.
3 Active follow-up Caller has an active FollowUp whose follow_up_at is due (or always due for none follow-ups).
4 Last outbound Caller had a recent outbound voice_session within the configured look-back window (default 7 days).
5 DID fallback Use the agent assigned to the dialed phone number (or unknown agent if none).

Each layer enforces agent eligibility — the agent must be active, not archived, and have a published version. Ineligible agents cause the layer to skip to the next.

When multiple campaigns or follow-ups match (multi-campaign tenants), the resolver picks the most recently activated option as the tie-breaker.

The resolver is bounded by a per-layer 50ms timeout and a 200ms total budget. Anything slower automatically falls back to DID, and a metric is emitted (inbound_routing.layer_timeout).

Tenant configuration

Tenant admins (and roles above) can change behavior under Tenant settings → Inbound routing.

Routing mode

  • Smart (default) — runs the layers above in order.
  • Legacy DID — disables smart routing and always uses the agent assigned to the dialed number.

Default for all tenants is smart. This is the global flip — both new and existing tenants are migrated automatically. Switch to legacy_did only if your team has a workflow that requires the old behavior.

Layer toggles

You can disable individual layers without leaving smart mode (e.g. turn off last_outbound if you don't want recent-call attribution to override the DID).

Look-back windows

  • last_outbound_window_days — how far back to look for a recent outbound call. Default 7.
  • retry_window_hours — how soon a retry_pending task is considered "due". Default 24.

Engagement auto-unenroll

When a contact engages on a call (inbound answered, or outbound that reached a human and ran for ≥10 seconds), Callaro can automatically close other pending follow-ups for that contact. The engagement_unenroll_scope setting controls how broadly:

Scope What gets closed beyond the routing-matched follow-up
off Nothing else. Only the follow-up that the routing layer matched is closed.
campaign (default, recommended) Same (contact, agent, campaign) siblings — e.g. the SMS counterpart of a call follow-up in the same sequence — transitioned to completed.
agent Campaign-scope behaviour PLUS other-campaign follow-ups for the same agent are cancelled. Cross-agent follow-ups are untouched.
contact Most aggressive (HubSpot-style). Same-campaign siblings completed, then every other active follow-up for the contact across agents and campaigns is cancelled.

Why campaign is the recommended default:

  • Each campaign has its own intent (sales sequence vs renewal vs survey). A return call for sales does not satisfy the survey objective.
  • Each agent represents a distinct relationship (sales rep vs support rep vs account manager). Multi-agent setups exist precisely so different teams can engage independently.
  • Same-campaign siblings (e.g. a call follow-up + an SMS follow-up scheduled by the same playbook) really were satisfied by the engagement, so they are completed rather than cancelled.

Pick contact only if your business treats the contact as a single thread (one outreach conversation regardless of agent).

Contact memory scope

Independent of routing mode, you can pick how contact memory is shared between agents:

  • exclusive (default) — each agent only reads memory it produced.
  • shared — agents see a union of memory across the tenant, with provenance and concatenated free-text summaries.
  • on_demand — same as exclusive at injection time, but the runtime can call a tool to fetch sibling memory when needed.

Emergency kill switch

The environment variable INBOUND_ROUTING_FORCE_LEGACY forces all tenants into legacy_did mode regardless of their setting. Acceptable truthy values: 1, true, yes, on. Use this only for incident response. Restart the API after toggling.

What appears in the UI

  • Call logs: a Routing reason column shows which layer won.
  • Call detail: the routing reason, agent, and a collapsible Routing decision trace show every layer that was evaluated, the result, and why.
  • Campaign detail: an Inbound returns card shows how many inbound calls were attributed to that campaign, broken down by routing reason, with a link to the filtered call logs.
  • Contact detail: the timeline annotates each inbound voice session with its routing reason badge.

Side effects on tasks and follow-ups

When an inbound call matches a campaign task or follow-up, the resolver closes the matched record at routing time:

  • BulkCallTask is marked contacted_inbound. A bulk_call_task_event is recorded with the inbound call_sid, the matched voice_session_id (backfilled), and inbound_routing_decision_id.
  • FollowUp is marked contacted_inbound. A follow_up_event is recorded with the same cross-links.
  • Sibling follow-ups for the same (contact, agent) are also closed implicitly with reason contacted_inbound_sibling so the contact is not double-pursued.

These records are visible in the call detail debug panel and in the existing task/follow-up event timelines.

Follow-up uniqueness

Follow-ups now dedupe per campaign. The active uniqueness key is:

(contact_id, agent_id, follow_up_type, bulk_call_campaign_id)

That means a contact in two different campaigns can have one active follow-up per campaign. Ad-hoc follow-ups (no campaign) continue to dedupe per (contact, agent, type).

Troubleshooting

Symptom Likely cause Fix
Inbound routes to the DID even though the contact has an active task Layer disabled, agent ineligible, or resolver timed out Check Inbound routing settings, agent active/published, and inbound_routing.layer_timeout metric.
Contact mapped to wrong campaign Two active campaigns; tie-breaker chose the most recent Pause the unwanted campaign or move the contact out of one.
Routing reason shows did_fallback for known contact Contact phone number does not match contacts.phone_e164 exactly Re-import contact with a normalized E.164 number.
Want to see the full trace Open call detail → Routing decision trace panel, or run Telephony::InboundRouting::Debug.trace(call_sid: …) in the console.
Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.