Overview

Core ownership, trigger, and runtime details for the Contact MQL routing process.

Current
Field Value
Process Name Lead Routing Engine - Contact MQL Routing
Business Owner RevOps
Technical Owner Salesforce Admin / Salesforce Engineering
Primary Object Contact
Trigger Event Contact.lifecyclestage__c changes to Marketing Qualified Lead
Runtime Pattern Record-triggered Flow detects the event; Apex classifies and assigns; Flow performs Sales Engagement cadence enrollment where applicable.

Purpose and Scope

The Lead Routing Engine assigns ownership for Contacts that become Marketing Qualified Leads. It is an Account-based routing process: Account ownership, Account classification, active deals, Account BDR ownership, and territory roster data determine the Contact owner.

In scope

MQL Contact routing

Route a Contact to the correct AE or BDR when HubSpot syncs the lifecycle stage to Marketing Qualified Lead.

In scope

Visible failure handling

Create Routing_Exception__c records for every unrouted outcome so RevOps can triage and resolve issues.

In scope

Routing auditability

Stamp routing path, last run time, speed-to-route metric, and last error detail on Contact records.

Out of scope

Lead object routing

Inbound records route as Contacts. Salesforce Lead assignment rules are not part of this process.

Out of scope

Account lifecycle automation

Pool-claim automation, automatic BDR sync after Region correction, and maintenance sweeps are deferred.

Out of scope

Marketing email tooling

Instantly and other marketing email processes do not participate in Salesforce routing decisions.

Actors and Systems

Systems and teams that invoke, execute, configure, or consume routing outcomes.

Actor or System Responsibility Salesforce process impact
HubSpot System of record for Contact lifecycle stage, re-MQL timing, and Contact-to-Account matching. Routing starts only after HubSpot or an authorized user updates lifecyclestage__c to Marketing Qualified Lead.
Integration or sync user Writes Contact updates into Salesforce. Must hold Routing_Trigger_Users to pass the default-deny routing gate.
Salesforce Flow Detects the trigger event, gates execution, calls Apex, and enrolls BDR-routed Contacts in cadence. Flow is intentionally thin. It does not classify routing paths.
Apex routing service Classifies path, writes ownership changes, inserts exceptions, publishes events, and isolates per-Contact failures. ContactRouter and RotationService are the only production Apex classes in this process.
RevOps Maintains roster/configuration and resolves routing exceptions. Uses Custom Metadata, Custom Settings, and queue/report views for ongoing operations.
Clay Consumes routed-contact events. Subscribes to Contact_Routed__e after successful routed paths.
Sales Engagement Runs the non-Target cadence for BDR-routed Contacts. Requires active cadence configuration and user permissions/license readiness.

Prerequisites

These conditions must be true before this process can run predictably in any sandbox or production org.

  • Routing Flows are active: Contact_BeforeSave_StampMQL and Contact_AfterSave_RouteOnMQL.
  • Integration user has trigger permission: assign Routing_Trigger_Users to users whose Contact updates should invoke routing.
  • Bypass permission is controlled: Routing_Bypass_All is assigned only when intentionally pausing automation.
  • Runtime user can update routing fields: assign Routing_Engine_Service where required for field access and Account owner transfer bypass.
  • Configuration exists: create Routing_Config__mdt.Default with Prospecting Pool user Id, triage owner Id, and non-Target cadence Id if cadence is enabled.
  • Roster exists: maintain Rep_Roster__mdt rows for active AEs and BDRs by region and segment.
  • Territory fields are populated: rotation paths require Account Region__c and AE rotation also requires Revenue_Range_Segments__c.
  • Sales Engagement is ready: cadence is active and the Flow-running user has the required Sales Engagement access.

Process Steps

This is the process flow at business level. Technical implementation details are documented in later sections.

1

MQL event occurs

Contact lifecycle changes to Marketing Qualified Lead. Before-save Flow stamps MQL timestamp and status.

2

Execution is gated

After-save Flow checks trigger permission, then bypass permission. Failed gate means clean exit.

3

Apex classifies

ContactRouter.route evaluates gates and routing paths in a fixed first-match-wins order.

4

Ownership updates

Contact owner changes to AE or BDR. For new-account rotation, Account owner changes to AE and Account BDR is stamped.

5

Side effects run

Successful paths publish a routing event. BDR-routed paths enter cadence. Tagged target Accounts can create a manual-outreach Task.

6

Admins monitor

Admins review Contact routing fields and open routing exception records for failed outcomes.

Invocation path: this process is designed for record-triggered execution and bulk updates. It handles multiple Contacts in a transaction and preserves buying-group continuity for Contacts sharing the same Account in the same batch.

Decision Matrix

Routing paths are evaluated from top to bottom. The first matching condition determines the outcome.

flowchart TD
  Start(["Contact becomes Marketing Qualified Lead"]) --> TriggerGate{"Trigger_Routing permission?"}
  TriggerGate -- "No" --> ExitNoRoute(["Exit without routing"])
  TriggerGate -- "Yes" --> BypassGate{"Bypass_Automation permission?"}
  BypassGate -- "Yes" --> ExitBypass(["Exit without routing"])
  BypassGate -- "No" --> HoldGate{"On hold, do not route, or disqualified?"}
  HoldGate -- "Yes" --> Hold(["HOLD"])
  HoldGate -- "No" --> DataGate{"Email and Account present?"}
  DataGate -- "No" --> BadData(["UNROUTED_BAD_DATA"])
  DataGate -- "Yes" --> TaggedGate{"Tagged Account?"}
  TaggedGate -- "Yes" --> TaggedOwner{"Active non-Pool Account owner?"}
  TaggedOwner -- "Yes" --> Tagged(["TAGGED_ACCOUNT"])
  TaggedOwner -- "No" --> TaggedNoOwner(["UNROUTED_TAGGED_NO_OWNER"])
  TaggedGate -- "No" --> CustomerGate{"Customer Account?"}
  CustomerGate -- "Yes" --> CustomerOwner{"Active non-Pool Account owner?"}
  CustomerOwner -- "Yes" --> Customer(["CUSTOMER_ACCOUNT"])
  CustomerOwner -- "No" --> NoOwner(["UNROUTED_NO_OWNER"])
  CustomerGate -- "No" --> DealGate{"Open Opportunity with active non-Pool owner?"}
  DealGate -- "Yes" --> ActiveDeal(["ACTIVE_DEAL"])
  DealGate -- "No" --> AccountBdrGate{"Active Account BDR?"}
  AccountBdrGate -- "Yes" --> AccountBdr(["ACCOUNT_BDR"])
  AccountBdrGate -- "No" --> AccountOwnerGate{"Account owner is Pool or inactive?"}
  AccountOwnerGate -- "Yes" --> RegionGateNew{"Account Region populated?"}
  RegionGateNew -- "No" --> NoRegion(["UNROUTED_NO_REGION"])
  RegionGateNew -- "Yes" --> AeGate{"Eligible AE in roster?"}
  AeGate -- "No" --> NoAe(["UNROUTED_NO_AE"])
  AeGate -- "Yes" --> BdrGateNew{"Eligible BDR in roster?"}
  BdrGateNew -- "No" --> NoBdr(["UNROUTED_NO_BDR"])
  BdrGateNew -- "Yes" --> RotationNew(["ROTATION_NEW_ACCOUNT"])
  AccountOwnerGate -- "No" --> RegionGateBdr{"Account Region populated?"}
  RegionGateBdr -- "No" --> NoRegion
  RegionGateBdr -- "Yes" --> BdrGateOnly{"Eligible BDR in roster?"}
  BdrGateOnly -- "No" --> NoBdr
  BdrGateOnly -- "Yes" --> RotationBdr(["ROTATION_BDR_ONLY"])

  classDef entry fill:#15161a,stroke:#15161a,color:#def862,font-weight:bold;
  classDef routed fill:#e5f4eb,stroke:#246f49,color:#18191d,font-weight:bold;
  classDef hold fill:#fff3d9,stroke:#9b6400,color:#18191d,font-weight:bold;
  classDef unrouted fill:#ffe8e5,stroke:#b42318,color:#18191d,font-weight:bold;
  classDef exit fill:#eef0f2,stroke:#5b6270,color:#18191d;
  class Start entry;
  class Hold hold;
  class Tagged,Customer,ActiveDeal,AccountBdr,RotationNew,RotationBdr routed;
  class BadData,TaggedNoOwner,NoOwner,NoRegion,NoAe,NoBdr unrouted;
  class ExitNoRoute,ExitBypass exit;
          
Order Outcome Condition Action Cadence / Task
0 HOLD Contact on hold, Account do-not-route, or Contact status is Disqualified. Stamp path and leave ownership unchanged. No cadence. No exception.
0 UNROUTED_BAD_DATA Email or Account is missing. Create Routing Exception and leave ownership unchanged. No cadence.
1 TAGGED_ACCOUNT Account is Active Target, Active Conversion, or Active Velocity. Route Contact to active Account owner. Active Target with Account BDR creates manual-outreach Task. No cadence.
2 CUSTOMER_ACCOUNT Account status is Customer. Route Contact to active non-Pool Account owner. Broken ownership becomes UNROUTED_NO_OWNER. No cadence.
3 ACTIVE_DEAL Account has newest open Opportunity with active non-Pool owner. Route Contact to Opportunity owner. No cadence.
4 ACCOUNT_BDR Account has active BDR_Owner__c. Route Contact to Account BDR. Enroll in non-Target cadence.
5 ROTATION_NEW_ACCOUNT Account owner is Prospecting Pool or inactive, and eligible AE and BDR exist. Rotate AE to Account, rotate BDR to Contact, stamp Account BDR. Enroll in non-Target cadence.
6 ROTATION_BDR_ONLY Account has active AE but no usable BDR pointer. Preserve Account owner, rotate BDR to Contact, stamp Account BDR. Enroll in non-Target cadence.

Exception Handling

The process fails visibly. Every unrouted outcome inserts a Routing_Exception__c record for RevOps triage and leaves Contact ownership unchanged.

Data

UNROUTED_BAD_DATA

Missing Email or Account. Fix source sync, enrichment, or Account matching.

Owner

UNROUTED_TAGGED_NO_OWNER

Tagged Account has no active non-Pool owner.

Owner

UNROUTED_NO_OWNER

Customer Account has broken ownership. It does not fall through to rotation.

Territory

UNROUTED_NO_REGION

Rotation was needed but Account Region was blank.

Roster

UNROUTED_NO_AE

No eligible AE for the Account region and segment.

Roster

UNROUTED_NO_BDR

No eligible BDR for the Account region.

System

UNROUTED_ERROR

Apex or DML failed for that Contact. Other Contacts in the same transaction can still route.

Audit

Resolution notes

Admins update exception status and notes directly on Routing_Exception__c.

Salesforce Components

This section documents the automation and metadata components admins and developers maintain.

Component Type Purpose Maintenance owner
Contact_BeforeSave_StampMQL Record-triggered Flow Stamps MQL timestamp and Contact status on lifecycle flip. Salesforce Admin
Contact_AfterSave_RouteOnMQL Record-triggered Flow Gates execution, invokes Apex, and handles BDR cadence enrollment. Salesforce Admin / Engineering
ContactRouter Apex class Classifies paths, writes ownership, stamps audit fields, creates Tasks/exceptions, publishes events. Salesforce Engineering
RotationService Apex class Performs locked round-robin selection using Rep_Rotation_Counter__c. Salesforce Engineering
Routing_Exception__c Custom object Operational triage queue for failed routing attempts. RevOps
Rep_Roster__mdt Custom Metadata Type Configures eligible AEs and BDRs by role, region, segment, active status, and out-of-office date. RevOps / Salesforce Admin
Routing_Config__mdt Custom Metadata Type Stores environment-specific Prospecting Pool, triage owner, and non-Target cadence Ids. Salesforce Admin
Contact_Routed__e Platform Event Publishes successful routed outcomes for downstream consumers. Salesforce Engineering / Integrations

Metadata Component Inventory

This is the full routing metadata inventory represented in source, including child metadata such as fields. The deployment manifest lists 33 top-level members; this expanded inventory counts 60 metadata components.

60 components
4 Apex classes
2 Record-triggered Flows
42 Custom fields across standard and custom objects
12 Objects, permissions, queue, and supporting metadata
Metadata type Components created
ApexClass ContactRouter, ContactRouterTest, RotationService, RotationServiceTest
Flow Contact_AfterSave_RouteOnMQL, Contact_BeforeSave_StampMQL
CustomPermission Bypass_Automation, Transfer_Account_Ownership, Trigger_Routing
PermissionSet Routing_Bypass_All, Routing_Engine_Service, Routing_Trigger_Users
CustomObject / Custom Metadata / Platform Event Contact_Routed__e, Rep_Roster__mdt, Rep_Rotation_Counter__c, Routing_Config__mdt, Routing_Exception__c
CustomField on Account Account.Account_Status__c, Account.BDR_Owner__c, Account.Do_Not_Route__c, Account.Region__c, Account.Revenue_Range_Segments__c, Account.Target_Account_Status__c
CustomField on Contact Contact.Contact_Status__c, Contact.Disqualification_Reason__c, Contact.MQL_Stamped_At__c, Contact.On_Hold__c, Contact.Routing_Last_Error__c, Contact.Routing_Last_Run__c, Contact.Routing_Path__c, Contact.Time_To_Route_Seconds__c, Contact.lifecyclestage__c
CustomField on Contact_Routed__e Contact_Routed__e.Account_Owner__c, Contact_Routed__e.Assigned_BDR__c, Contact_Routed__e.Contact_Id__c, Contact_Routed__e.Region__c, Contact_Routed__e.Routing_Path__c, Contact_Routed__e.Routing_Timestamp__c
CustomField on Rep_Roster__mdt Rep_Roster__mdt.Is_Active__c, Rep_Roster__mdt.Out_Until__c, Rep_Roster__mdt.Paired_BDR__c, Rep_Roster__mdt.Region__c, Rep_Roster__mdt.Role__c, Rep_Roster__mdt.Segment__c, Rep_Roster__mdt.User__c
CustomField on Rep_Rotation_Counter__c Rep_Rotation_Counter__c.Region__c, Rep_Rotation_Counter__c.Role__c, Rep_Rotation_Counter__c.Rotation_Counter__c, Rep_Rotation_Counter__c.Segment__c
CustomField on Routing_Config__mdt Routing_Config__mdt.NonTarget_Cadence_Id__c, Routing_Config__mdt.Prospecting_Pool_User_Id__c, Routing_Config__mdt.Unrouted_Triage_Owner_Id__c
CustomField on Routing_Exception__c Routing_Exception__c.Account__c, Routing_Exception__c.Contact__c, Routing_Exception__c.Error_Detail__c, Routing_Exception__c.Failure_Reason__c, Routing_Exception__c.Resolution_Notes__c, Routing_Exception__c.Routed_At__c, Routing_Exception__c.Status__c
Queue Routing_Triage

Not included as created metadata: org-specific records such as Routing_Config__mdt.Default, real Rep_Roster__mdt rows, rotation counter rows, users, queues beyond Routing_Triage, and Sales Engagement cadence records are configured per environment.

Data Model and Field Usage

Only fields that drive or document this process are listed here. See docs/objects-and-fields.md for the full field inventory.

Contact

Trigger and audit fields

lifecyclestage__c, Contact_Status__c, On_Hold__c, Routing_Path__c, Routing_Last_Run__c, Routing_Last_Error__c, MQL_Stamped_At__c, Time_To_Route_Seconds__c.

Account

Path input fields

OwnerId, BDR_Owner__c, Region__c, Revenue_Range_Segments__c, Account_Status__c, Do_Not_Route__c, Target_Account_Status__c.

Routing Exception

Triage fields

Contact__c, Account__c, Failure_Reason__c, Status__c, Error_Detail__c, Resolution_Notes__c, Routed_At__c.

Configuration data is environment-specific: Routing_Config__mdt.Default and real roster rows can contain User, Queue, and Cadence Ids. Do not treat those records as portable metadata unless the release process explicitly manages per-environment values.

Controls and Safeguards

Controls admins use to stop, resume, and protect routing execution.

Entry control

Trigger_Routing

Default-deny gate. Users without this permission do not invoke routing when they save Contacts.

Pause control

Bypass_Automation

Emergency bypass. Users with this permission exit routing immediately after the trigger gate.

Owner-transfer control

Transfer_Account_Ownership

Allows the routing runtime user to pass Account owner-change validation rules when assigning AEs to new Accounts.

Bulk safety

Bulkified Apex

SOQL and DML are collected outside per-Contact loops. Rotation counters are locked with row-level locking.

Error isolation

Per-Contact try/catch

One failed Contact becomes UNROUTED_ERROR without rolling back other Contacts in the batch.

Auditability

Field stamps and triage object

Routing state is visible on Contact and failed attempts are visible in a dedicated object.

Admin Automation Boundary

Routing truth stays in Apex. Flow is used only where admins need an operational workflow or where Salesforce exposes the required action only in Flow.

Area Owner Reason
Path classification, ownership writes, rotation, exceptions, events, and tagged-account outreach Tasks Apex Transaction-critical, bulk-sensitive behavior with unit test coverage.
MQL stamping, trigger/bypass gates, and cadence enrollment Flow Simple event orchestration, before-save stamping, and the Flow-only Sales Engagement action.
Future exception notifications, aging, and SLA reminders Flow after RevOps defines rules Admin-maintained operational behavior; not shipped as placeholder automation without owner, timing, channel, and duplicate-prevention requirements.

Deprecated cleanup: Contact.BDR_Owner__c and the old Contact_After_Save_Create_Call_Sequence Flow are retired. Use Account.BDR_Owner__c for BDR continuity, standard Contact.OwnerId for the routed Contact owner, and Contact_AfterSave_RouteOnMQL for cadence enrollment.

Monitoring and KPIs

Healthy automation should be tied to measurable outcomes. These are the recommended operational signals for routing.

Metric or Signal Where to measure Expected use
Time to route Contact Time_To_Route_Seconds__c Primary speed-to-lead SLA metric. Trend by source, segment, and owner.
Routing volume by path Contact report grouped by Routing_Path__c Detect unexpected spikes in hold, rotation, or unrouted outcomes.
Open exceptions Routing_Exception__c queue/report view Daily RevOps work queue. SLA target: no aged open exceptions beyond the agreed threshold.
Cadence failures Contact Routing_Last_Error__c Identify Sales Engagement permission, cadence, or target data issues.
Rotation distribution Contact report grouped by owner, region, segment, and path Weekly review for fairness within each active rotation cohort.
Downstream event delivery Contact_Routed__e subscriber monitoring Confirm Clay receives successful routed outcomes when integration testing is in scope.

Routine Maintenance

These tasks keep the process current without changing routing code.

Daily exception triage

  1. Open the configured Routing_Exception__c queue or report view.
  2. Review open and investigating exceptions.
  3. Group by Failure_Reason__c to identify common root causes.
  4. Fix the source data or configuration before re-routing affected Contacts.
  5. Set status to Resolved only after the root cause is fixed.

Weekly routing distribution review

  1. Report on Contacts routed in the last 7 days.
  2. Group by Routing_Path__c, owner, Account region, and Account segment.
  3. Review rotation cohorts for material skew.
  4. Check roster rows and out-of-office exclusions before editing counters.

Monthly roster reconciliation

  1. Compare active roster rows to current AE and BDR headcount.
  2. Deactivate rows for departed reps instead of deleting them.
  3. Clear expired Out_Until__c values.
  4. Confirm User Ids point to active Salesforce users.

Configuration review

  1. Confirm Prospecting Pool user is active.
  2. Confirm triage owner queue/user is active and monitored.
  3. Confirm cadence Id points to the intended active cadence.
  4. Confirm permission assignments match the current integration user model.

Change Management

Changes should be classified by whether they are admin configuration, controlled data operations, or release-managed metadata changes.

Change type Allowed path Validation required
Add or deactivate a rep Admin configuration in Rep_Roster__mdt. Route a test MQL in the affected region/segment.
Change cadence, triage owner, or Prospecting Pool Admin configuration in Routing_Config__mdt.Default. Run a BDR-path smoke test and a forced exception test.
Pause or resume routing Permission assignment change using Routing_Trigger_Users or Routing_Bypass_All. Confirm a test Contact either does or does not stamp Routing_Last_Run__c as expected.
Bulk re-route after root-cause fix Approved admin/developer script invoking ContactRouter.route. Sample routed Contacts and confirm no repeated exceptions.
Change routing path order or conditions Release-managed Apex change. Update specs first, run Apex tests, validate in sandbox, and document affected paths.
Add new Region or Segment Coordinated metadata/configuration change. Align Account picklists, roster rows, and smoke tests for each new cohort.

Change rule: update this page when behavior, ownership, prerequisites, data dependencies, or monitoring responsibilities change. Do not rely on code comments or Flow element names as the only reference.