Document Control

This documentation follows Salesforce process-documentation practices: define scope, owners, prerequisites, invocation paths, data dependencies, decision outcomes, exception handling, controls, validation, and change procedures.

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.
Source References CLAUDE.md, docs/v3-architecture-plan.md, docs/production-flows.md, docs/cadence.md, docs/objects-and-fields.md, manifest/package.xml

Documentation standard applied: every automated process should show what invokes it, what data it reads and writes, each decision path, expected outputs, failure behavior, monitoring signals, and ownership for future maintenance.

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 context 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

Documenting the source system, actor context, and downstream consumers prevents ambiguous ownership when failures occur.

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 the Routing Exception tab 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, Contact_AfterSave_RouteOnMQL, and RoutingException_BeforeSave_StampResolution.
  • 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 context 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_Stamped_At__c and Contact_Status__c = MQL.

2

Execution is gated

After-save Flow checks Trigger_Routing, then checks Bypass_Automation. Failed gate means clean exit.

3

Apex classifies

ContactRouter.route evaluates pre-route 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 Contact_Routed__e. 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__c 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 stamps

When status becomes Resolved, Flow stamps Resolved_At__c and Resolved_By__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
RoutingException_BeforeSave_StampResolution Record-triggered Flow Stamps exception resolver and timestamp when status changes to Resolved. Salesforce Admin
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, list views, and compact layouts. The deployment manifest lists 38 top-level members; this expanded inventory counts 71 metadata components.

71 components
4 Apex classes
3 Record-triggered Flows
46 Custom fields across standard and custom objects
18 Objects, permissions, UI, queue, and supporting metadata
Metadata type Components created
ApexClass ContactRouter, ContactRouterTest, RotationService, RotationServiceTest
Flow Contact_AfterSave_RouteOnMQL, Contact_BeforeSave_StampMQL, RoutingException_BeforeSave_StampResolution
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.Is_Tagged_Account__c, Account.Region__c, Account.Revenue_Range_Segments__c, Account.Target_Account_Status__c, Account.Target_Account__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.Resolved_At__c, Routing_Exception__c.Resolved_By__c, Routing_Exception__c.Routed_At__c, Routing_Exception__c.Status__c
CompactLayout Routing_Exception__c.Routing_Exception_Compact
ListView Routing_Exception__c.All_Open_Exceptions, Routing_Exception__c.My_Open_Exceptions, Routing_Exception__c.Resolved_This_Week
Layout Routing_Exception__c-Routing Exception Layout
Queue Routing_Triage
CustomTab Routing_Exception__c

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, Is_Tagged_Account__c.

Routing Exception

Triage fields

Contact__c, Account__c, Failure_Reason__c, Status__c, Error_Detail__c, Resolution_Notes__c, Routed_At__c, Resolved_At__c, Resolved_By__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 are documented so admins know how the process is intentionally stopped, resumed, and protected from unsafe execution contexts.

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 context 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.

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 tab, All Open Exceptions list 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 Routing Exception tab.
  2. Review All Open 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 the process documentation when behavior, ownership, prerequisites, data dependencies, or monitoring responsibilities change. Do not rely on code comments or Flow element names as the only process documentation.