Escalation Rules
This skill activates when you need to configure, review, or troubleshoot Salesforce Case Escalation Rules — the declarative mechanism that automatically notifies stakeholders or reassigns cases when they are not resolved within a specified time window.
It owns the design and operation of the rule: entry criteria, staged action thresholds, engine behaviour, the cutover, and the monitoring that proves it still works. The calendar the entry points at belongs to admin/business-hours-and-holidays; the routing that put the case somewhere in the first place belongs to admin/assignment-rules.
Before Starting
Gather this context before working on anything in this domain:
- Active rule limit: Plan for one active escalation rule per org. If multiple rule configs exist, only the one marked Active fires. Confirm whether an active rule already exists before creating a new one. UNVERIFIED (2026-09-04): the Metadata API guide describes
escalationRule as a repeating element "processed in the order they appear in the EscalationRules container" and gives each rule its own active flag; it does not state the one-active-rule ceiling, and help.salesforce.com cannot be fetched. Verify in the target org's Setup > Escalation Rules list before relying on it.
- Processing cadence: The time-based engine that drives escalation is not real time. A case that hits its threshold at 2:05 PM may not escalate until the next engine pass — plan SLA commitments accordingly. UNVERIFIED (2026-09-04): the widely-quoted "approximately every hour" cadence appears in no fetchable official source; treat the interval as "batched, not immediate" and measure it in your own org before promising a number.
- Business hours dependency: The Object Reference states it directly for the
BusinessHours object — "Escalation rules are run only during these hours" — and adds that holidays attached to a calendar suspend both the hours and the escalation rules that use them. If no calendar is restricted, the org default is 24/7.
- Who else writes
OwnerId: an escalation action that reassigns changes the case owner, which re-fires every record-triggered automation watching ownership. Inventory those before staging a reassign action.
Questions to Ask Before Configuring
Ask these before opening Setup. Each one maps to a failure documented in references/gotchas.md, and an LLM that skips them produces a rule that deploys cleanly and escalates the wrong cases at the wrong time.
| Ask | Why it matters | What a good answer adds |
|---|
| "Does the clock start at case creation, or restart every time anyone edits the case?" | escalationStartTime is CaseCreation or CaseLastModified; disableEscalationWhenModified is a separate lever that stops escalation on edit | The per-tier choice of both fields, and a list of the automations that count as an "edit" |
| "Which calendar does each tier follow, and is there a tier that must run 24/7?" | businessHoursSource is None, Case, or Static per entry — a per-Case calendar design is defeated by an entry left on None | One businessHoursSource value per entry, with the Sev-1 exception written down as a decision |
| "Which escalation rule is active in production today, and who owns it?" | Activating a new rule is a cutover, not an addition | The incumbent rule's name, its entries, and a named owner to sign off the swap |
| "When the timer expires, do we notify, change the owner, or both?" | Reassignment writes OwnerId and re-fires record automation; notify-only does not | A per-threshold action table, and the automation blast radius for any reassign action |
| "What is the SLA in minutes, and has anyone signed off on the engine's batching latency?" | minutesToEscalation is stored in minutes while Setup shows hours, and the engine fires in batches | An agreed minutes value with headroom, not an hours value transcribed as minutes |
| "How will we know next month that the rule still fires?" | IsEscalated is a plain writable boolean, not an engine-owned lock — nothing reports on a rule that quietly stopped | A saved report or query on escalated, still-open cases, with an owner and a cadence |
| "How do we cut over without a wave of instant escalations?" | Cases already past their threshold escalate on the first pass after activation | A deploy-inactive-then-activate plan with a defined comparison window |
What a proper configuration adds over just doing it: the timer measures the working time the SLA actually promised, the cutover is a comparison rather than a surprise, and someone is still looking at escalated cases a quarter later.
Core Concepts
How Escalation Rules Differ from Assignment Rules
Assignment rules route cases to users or queues when a case is created or re-opened. They fire once on creation.
Escalation rules operate on a timer: they fire when a case that matches defined criteria has been open for longer than a configured threshold. If a case is not resolved within X hours, escalation rules send notifications or reassign the case.
The two rules are complementary. Assignment routes the case on arrival; escalation follows up when response time is exceeded.
Rule Structure: One Rule, Many Entries
A single active escalation rule contains multiple rule entries. In metadata each ruleEntry carries:
| Field | Type | What it controls |
|---|
criteriaItems | FilterItem[] | Which cases the entry applies to (field, operation, value) |
formula | string | Alternative to criteriaItems — "Specify either formula or criteriaItems, but not both fields" |
booleanFilter | string | Advanced filter logic across the numbered criteriaItems |
businessHoursSource | enum | None, Case, or Static |
businessHours | string | The named calendar — "Specify only if businessHoursSource is set to Static" |
escalationStartTime | enum | CaseCreation or CaseLastModified |
disableEscalationWhenModified | boolean | Escalation is disabled when the record is modified |
escalationAction | EscalationAction[] | The staged actions to perform when the criteria are met |
Entries are evaluated in order. The first matching entry wins — subsequent entries are skipped, just like assignment rule evaluation. Rules themselves are "processed in the order they appear in the EscalationRules container".
Escalation Actions Are Staged Inside One Entry
Multi-tier escalation is built from several escalationAction elements inside a single entry, each with its own minutesToEscalation. It is not built from several entries — a case only ever matches one entry.
| Field | Meaning |
|---|
minutesToEscalation | int, minutes — the guide's own sample uses 1440 for 24 hours |
notifyTo | The user to notify |
notifyToTemplate | Template for the notification email |
notifyEmail | A free-form email address to notify |
notifyCaseOwner | Boolean — notify the current owner |
assignedTo | The user or queue the case is reassigned to (omit for notify-only) |
assignedToType | User or Queue — meaningless without assignedTo |
assignedToTemplate | Template for the email sent to the new owner; a Classic template, because "Lightning email templates aren't packageable" |
An action with no assignedTo is notification-only and is a legitimate first stage. An action with assignedTo writes OwnerId.
UNVERIFIED (2026-09-04): the commonly-cited ceiling of 5 escalation actions per entry is not in the Metadata API guide's EscalationAction table and does not appear in the Salesforce App Limits cheat sheet (grep -i escalation returns nothing). escalationAction is typed as an unbounded array. Confirm the Setup-UI ceiling in your org before designing a fifth stage; scripts/check_escalation_rules.py reports the count as INFO rather than failing on it.
Business Hours and the Escalation Clock
businessHoursSource decides per entry which clock runs:
| Value | Clock used |
|---|
None | No calendar — wall-clock time, 24/7. Correct for Sev-1. |
Case | The calendar on Case.BusinessHoursId (a writable reference field) |
Static | The calendar named in businessHours on the entry, whatever the case says |
Two facts from the Object Reference's BusinessHours entry govern the result: escalation rules run only during the calendar's hours, and holidays associated with the calendar suspend those hours and the escalation rules that use them. Restricting the calendar is what makes off-hours pausing real — a calendar left at the shipped 24/7 default makes Static and None behave identically. Calendar design itself lives in admin/business-hours-and-holidays.
Common Patterns
Pattern: Tiered Escalation by Case Priority
When to use: Different SLA windows for different priorities. P1 cases escalate in 1 hour; P3 cases escalate in 8 hours.
How it works:
- Create rule entries in priority order: P1 entry first, P2 next, P3 last.
- Each entry uses field criteria
Priority = "P1" (or P2, P3).
- Set
minutesToEscalation per stage: P1 = 60, P2 = 240, P3 = 480.
- Add staged actions per tier: P1 notifies the manager at stage 1 and reassigns at stage 2.
Catch: Rule entries are evaluated top to bottom. Put P1 first, otherwise a P1 case might match a lower-tier entry if that entry has looser criteria.
Pattern: Mixed Clocks in One Rule
When to use: Sev-1 must escalate at 3 AM Sunday; everything else must wait for the next working morning.
How it works: Give the Sev-1 entry businessHoursSource = None and the standard entries businessHoursSource = Case (or Static with a named calendar). The choice is per entry, so one rule carries both clocks. The full XML is in references/metadata-examples.md.
Why not two rules: a second rule is a cutover, not an addition — see the deploy-inactive pattern below.
Pattern: Notify and Reassign at Different Thresholds
When to use: You want to warn the case owner at hour 2, then escalate to a queue at hour 4 if still unresolved.
How it works: Within a single rule entry, add two escalationAction elements:
- Action 1:
minutesToEscalation 120 → notifyCaseOwner true, notifyTo the manager. No assignedTo.
- Action 2:
minutesToEscalation 240 → assignedTo the escalation queue, assignedToType Queue, plus notifyEmail.
Both actions are attached to the same entry (same criteria). They fire as their thresholds are crossed.
Pattern: Deploy Inactive, Then Cut Over
When to use: Replacing or materially changing the live rule in production.
How it works:
- Deploy the new rule with
<active>false</active> alongside the incumbent. Nothing changes in production.
- Compare the two rules' entry criteria against a sample of live cases before switching.
- Activate the new rule in a separate, small deploy, in a window where a wave of instant escalations is survivable.
- Watch escalated-case volume for one full SLA period before deleting the old rule.
The step-by-step with XML and CLI is in references/metadata-examples.md.
Decision Guidance
| Situation | Recommended Approach | Reason |
|---|
| Route case to agent on creation | Assignment Rule | Escalation only activates after time passes — it is not a routing tool for new cases |
| Fire when SLA time is exceeded | Escalation Rule | Purpose-built for time-based case follow-up |
| Clock should pause on weekends | businessHoursSource Case or Static, pointed at a restricted calendar | Escalation runs only during the calendar's hours |
| Sev-1 must ignore the calendar | businessHoursSource None on that entry | The choice is per entry, so it coexists with business-hours entries |
| Reset clock when agent responds | escalationStartTime CaseLastModified | The clock restarts on each update — including automation updates |
| Stop escalating once someone touches the case | disableEscalationWhenModified true | Disables escalation on modification instead of restarting the timer |
| Keep original owner but notify manager | Action with notifyTo/notifyCaseOwner and no assignedTo | Notification-only actions are valid |
| Multiple SLA tiers by priority | Multiple rule entries, ordered P1 first | First matching entry wins; put most specific criteria at top |
| Progressive escalation within one tier | Multiple escalationAction elements on one entry | A case matches one entry only; stages live inside it |
| Sub-hourly, guaranteed-precision SLA | Scheduled Flow or Apex Schedulable | The declarative engine is batched, not real time |
| Replacing the live rule | Deploy inactive, activate separately | Activation is a cutover with a possible escalation wave |
Recommended Workflow
- Inventory the incumbent — retrieve the org's current rules (
sf project retrieve start --metadata EscalationRules:Case) and record which rule is active, its entries, and its owner. Answer the table in ## Questions to Ask Before Configuring before designing anything.
- Design the tiers — one entry per distinct clock-and-criteria combination, staged actions inside each entry; capture the design in
templates/escalation-rules-template.md so the minutes, the calendar source, and the action targets are agreed before they are typed.
- Confirm the calendar — check each entry's
businessHoursSource against the calendar design in admin/business-hours-and-holidays; a Case source needs Case.BusinessHoursId populated at creation.
- Build as metadata — shape
escalationRules/Case.escalationRules-meta.xml from references/metadata-examples.md, with the new rule <active>false</active> for now.
- Lint — run
python3 skills/admin/escalation-rules/scripts/check_escalation_rules.py --manifest-dir force-app/main/default; it flags two active rules, a Static entry with no calendar, non-positive minutesToEscalation, assignedTo without assignedToType, a notified-but-templateless action (notifyCaseOwner or notifyTo with no notifyToTemplate), and catch-all entries.
- Test the clock — reuse the after-hours clock test in
admin/business-hours-and-holidays references/examples.md (Example 4) against a real case, then verify with the monitoring query in references/metadata-examples.md.
- Cut over and watch — activate in its own deploy, then run the escalated-case monitoring query for one full SLA period; if nothing fired, work
references/gotchas.md in order.
Review Checklist
Run through these before marking escalation rule work complete:
Salesforce-Specific Gotchas
Non-obvious platform behaviors that cause real production problems. Full treatment in references/gotchas.md.
- The engine batches; it does not fire on the minute. Do not sell minute-level SLA precision on a declarative escalation rule.
- Reactivating a rule can produce a wave. Cases already past their threshold escalate on the first pass after activation.
- A 24/7 calendar makes the business-hours setting a no-op. Restricting the calendar is the work; selecting it is not.
minutesToEscalation is minutes; Setup shows hours. A "4 hour" tier written as 4 escalates after four minutes.
- Reassignment writes
OwnerId. Every record-triggered automation on ownership fires again, hours after the case was created.
- Holidays on the calendar suspend the escalation rules that use it. An unmaintained holiday list silently changes SLA behaviour a year later.
IsEscalated is a plain writable boolean. Anything with update access — a data load, a flow, an integration — can set or clear it.
notifyToTemplate is required whenever the case owner or a named user is notified, but the guide never says so. An action with notifyCaseOwner true and no notifyToTemplate deploys as valid XML and fails only at org validation.
Output Artifacts
| Artifact | Description |
|---|
escalationRules/Case.escalationRules-meta.xml | The deployable rule with ordered entries and staged actions |
| Business hours decision | Which businessHoursSource each entry uses, and why any entry is None |
| Cutover plan | Deploy-inactive, activate, compare, retire — with the watch window named |
| Escalation configuration review | Checklist confirming rule order, thresholds, action targets, and calendar wiring |
| Monitoring query / report | Escalated, still-open cases by owner and age, with a review cadence |
Reference Files
| File | Read it when |
|---|
references/metadata-examples.md | Writing or reviewing the deployable XML, package.xml, CLI, cutover, or monitoring query |
references/gotchas.md | The escalation fired late, early, twice, or not at all |
references/examples.md | You want two worked scenarios end to end, plus the multiple-active-rules anti-pattern |
references/well-architected.md | Choosing declarative escalation over Flow/Apex, and the official sources behind the claims here |
references/llm-anti-patterns.md | Self-checking generated escalation guidance before returning it |
Related Skills
- admin/assignment-rules — initial routing; its
references/metadata-examples.md holds the base EscalationRules shape and its references/troubleshooting.md step 5 covers ownership overwrites
- admin/business-hours-and-holidays — the calendar an entry consumes through
businessHoursSource, and the after-hours clock test
- admin/case-management-setup — case intake, where
Case.BusinessHoursId gets populated
- admin/entitlements-and-milestones — milestone timers, the other SLA clock; not the same engine
- architect/sla-design-and-escalation-matrix — the tier table and escalation matrix that decide what these entries should say
- admin/approval-processes — time-based routing for human decisions, not SLA escalation