A configurable, metadata-driven Salesforce batch system that automatically updates Case statuses based on dynamic rules. The system is designed to be portable across any Salesforce org using standard Case Management.
- Metadata-Driven Rules: Configure Case status update rules using Custom Metadata Types (CMT) - no code changes required
- Flexible Rule Criteria: Support for multiple criteria including:
- Days since last modified
- Days since created
- Days since last activity
- Record type filtering (include/exclude)
- Custom filter logic
- Batch Processing: Efficient batch processing with configurable batch sizes
- Scheduled Execution: Built-in scheduler with configurable cron expressions
- Rule Execution Order: Control rule execution order via metadata
- Stop Processing Flag: Rules can stop further processing after execution
- Comprehensive Logging: Debug mode for detailed execution logs
- Email Notifications: Configurable email notifications for errors and completion
- Metrics Tracking: Built-in batch metrics tracking
- Lightning Web Component: UI for managing scheduler and settings
- Dedicated Lightning Tab: Easy access via "Case Auto-Closer" tab in Lightning navigation
- Lightning App Page: Pre-configured page with scheduler manager component
- Without Sharing Query Layer: Query all Case records regardless of sharing rules for maximum batch coverage
- Batch Processor (
util_closer_CaseStatusBatch): Processes Cases and applies rules - Rule Engine (
util_closer_RuleEngine): Evaluates Custom Metadata rules and builds queries - Scheduler (
util_closer_CaseStatusScheduler): Manages scheduled job execution - Settings Service (
util_closer_SettingsService): Centralized configuration access - Logger (
util_closer_Logger): Debug logging utility - Notification Service (
util_closer_NotificationService): Email notification handling - Scheduler Manager LWC: Lightning Web Component for managing the scheduler
- Lightning Tab (
util_closer_Scheduler_Manager): Dedicated tab for accessing the scheduler manager - Lightning App Page (
util_closer_Scheduler_Manager): Pre-configured page containing the scheduler manager component - Case Data Access (
util_closer_CaseDataAccess): Without sharing data access layer for querying all Cases
- Salesforce CLI (
sf) version 2.x or higher - Access to a Salesforce org (Production, Sandbox, or Scratch Org)
- Appropriate permissions to deploy Apex classes, Custom Metadata Types, Custom Settings, and Lightning Web Components
sf versionExpected output: Version 2.x or higher
- Authenticate to your Salesforce org:
sf org login web --alias myorg --set-default- Deploy the source code:
sf project deploy start --target-org myorg- Verify deployment:
sf data query --query "SELECT Name FROM ApexClass WHERE Name LIKE 'util_closer_%'" --target-org myorgExpected: All util_closer_* classes should be listed.
-
Assign Permission Sets (required):
See the Security section for detailed information on permission sets.
util_closer_Administrator: Full access to manage the system (assign to admins)util_closer_Operator: Basic access to run batch jobs (assign to operators)
Important: Users must have appropriate Case object permissions (Read and Edit) to run batch jobs successfully.
-
Configure Custom Settings:
Use the provided script or configure via the Lightning Web Component:
sf apex run --file scripts/apex/create-settings.apex --target-org myorg-
Create Custom Metadata Rules:
Deploy the Custom Metadata Type records from
force-app/main/default/customMetadata/or create your own rules via Setup → Custom Metadata Types. -
Access the Lightning Tab:
After deployment, users with the
util_closer_Administratorpermission set will see the "Case Auto-Closer" tab in their Lightning navigation. The tab provides direct access to the scheduler manager interface.
The Case Status Auto-Closer system uses Salesforce's security model to control access. Most Apex classes are declared with with sharing to respect the user's object and field-level security permissions. However, the system includes a without sharing data access layer (util_closer_CaseDataAccess) that allows querying all Case records regardless of sharing rules, ensuring maximum batch coverage while still respecting field-level security for updates.
Two permission sets are provided to grant appropriate access levels:
Purpose: Full access to configure and manage the Case Status Auto-Closer system including settings, scheduling, and manual batch execution.
Access Granted:
-
Apex Class Access: All
util_closer_*classes enabledutil_closer_CaseStatusBatchutil_closer_CaseStatusSchedulerutil_closer_CaseDataAccessutil_closer_SchedulerControllerutil_closer_SettingsServiceutil_closer_RuleEngineutil_closer_NotificationServiceutil_closer_Loggerutil_closer_BatchMetrics
-
Custom Settings Access: Full CRUD access to
util_closer_Settings__c- Read, Create, Edit, Delete
-
Field-Level Security: All fields editable
Batch_Size__c- Read/EditCompletion_Notification_Emails__c- Read/EditCron_Expression__c- Read/EditDebug_Mode__c- Read/EditError_Notification_Emails__c- Read/EditIs_Active__c- Read/EditScheduled_Job_Name__c- Read/Edit
-
System Permissions:
API Enabled- Required for Apex executionRun Flows- Required for scheduled job execution
-
Tab Visibility:
util_closer_Scheduler_Managertab - Visible (provides access to the scheduler manager interface)
When to Use: Assign to system administrators, Salesforce admins, or users who need to configure and manage the system.
Purpose: Ability to view settings and manually execute the Case Status Auto-Closer batch job. Cannot modify configuration.
Access Granted:
-
Apex Class Access: All
util_closer_*classes enabled (same as Administrator)util_closer_CaseStatusBatchutil_closer_CaseStatusSchedulerutil_closer_CaseDataAccessutil_closer_SchedulerControllerutil_closer_SettingsServiceutil_closer_RuleEngineutil_closer_NotificationServiceutil_closer_Loggerutil_closer_BatchMetrics
-
Custom Settings Access: Read-only access to
util_closer_Settings__c- Read only (no Create, Edit, or Delete)
-
Field-Level Security: All fields read-only
Batch_Size__c- Read onlyCompletion_Notification_Emails__c- Read onlyCron_Expression__c- Read onlyDebug_Mode__c- Read onlyError_Notification_Emails__c- Read onlyIs_Active__c- Read onlyScheduled_Job_Name__c- Read only
-
System Permissions:
API Enabled- Required for Apex execution
When to Use: Assign to users who need to run batch jobs manually or view system status, but should not modify configuration.
- Navigate to Setup → Users → Permission Sets
- Find the permission set (
Case Auto-Closer AdministratororCase Auto-Closer Operator) - Click Manage Assignments
- Click Add Assignments
- Select users and click Assign
# Assign Administrator permission set to a user
sf data create record \
--sobject PermissionSetAssignment \
--values "AssigneeId=USER_ID PermissionSetId=PERMISSION_SET_ID" \
--target-org myorg
# Query permission set IDs first
sf data query \
--query "SELECT Id, Name FROM PermissionSet WHERE Name IN ('util_closer_Administrator', 'util_closer_Operator')" \
--target-org myorgImportant: Users must have appropriate Case object permissions to run the batch job successfully. The batch job reads and updates Case records, so users need:
-
Read Access to Case object and fields:
IdStatusLastModifiedDateCreatedDate- Any fields referenced in rule criteria (e.g., Record Type, Last Activity Date)
-
Edit Access to Case object and
Statusfield:- Required to update Case statuses based on rules
The system includes a without sharing data access layer (util_closer_CaseDataAccess) that allows the batch job to query all Case records matching the rule criteria, regardless of sharing rules. This ensures maximum coverage for batch processing.
How it works:
- Query Phase: Uses
util_closer_CaseDataAccess(without sharing) to query all Cases matching rule criteria, bypassing sharing restrictions - Update Phase: Uses
util_closer_CaseStatusBatch(with sharing) to respect field-level security when updating records
Benefits:
- Processes all Cases matching criteria, not just those visible to the running user
- Ensures consistent batch execution regardless of who schedules the job
- Maximizes the number of Cases that can be processed
Security Considerations:
- Updates still respect field-level security (FLS) - users without edit access to Cases will see update failures
- Sharing rules are bypassed only for querying, not for updating
- The running user must still have appropriate object permissions for updates to succeed
- All batch executions are logged and auditable
Note: If a user doesn't have edit access to Cases, the batch job will still query all matching Cases but will fail to update those records. Check batch execution logs for specific failure reasons.
Custom Metadata Types are readable by all users by default. However, to create or modify Custom Metadata records (util_closer_Case_Status_Rule__mdt), users need:
- Customize Application permission (typically System Administrator profile)
- Or a custom permission set with Custom Metadata Type access
Recommendation: Only System Administrators should create or modify Custom Metadata rules.
-
Principle of Least Privilege:
- Assign
util_closer_Operatorto most users who only need to run jobs - Reserve
util_closer_Administratorfor users who need to configure the system
- Assign
-
Case Object Security:
- Ensure users have appropriate Case object permissions based on your organization's security model
- Consider using sharing rules if Cases need to be restricted by ownership or criteria
-
Audit Trail:
- The system logs all batch executions (if Debug Mode is enabled)
- Review batch job execution logs regularly via Setup → Apex Jobs
-
Email Notifications:
- Configure error and completion notification emails in Custom Settings
- Only administrators should be able to modify notification settings
-
Scheduled Job Security:
- Scheduled jobs run in the context of the user who scheduled them
- Ensure the scheduling user has appropriate permissions
- Consider using a dedicated integration user for scheduled jobs
Issue: User cannot run batch job
- Solution: Verify user has the appropriate permission set assigned and Case object edit permissions
Issue: Batch job fails with "insufficient access rights" errors
- Solution: Check Case object and field-level security for the running user
Issue: User cannot view or edit Custom Settings
- Solution: Verify permission set assignment and field-level security permissions
Issue: Scheduled job fails to execute
- Solution: Verify the user who scheduled the job still has active permission set and Case object access
Issue: User cannot see the "Case Auto-Closer" tab
- Solution: Verify the user has the
util_closer_Administratorpermission set assigned, which includes tab visibility forutil_closer_Scheduler_Manager
Configure system-wide settings via Custom Settings:
| Field | Description | Default |
|---|---|---|
Is_Active__c |
Enable/disable the entire system | true |
Batch_Size__c |
Number of records processed per batch | 200 |
Debug_Mode__c |
Enable detailed debug logging | false |
Cron_Expression__c |
Cron expression for scheduled execution | 0 0 2 * * ? (Daily 2 AM) |
Scheduled_Job_Name__c |
Name of the scheduled job | util_closer_CaseStatusJob |
Error_Notification_Emails__c |
Comma-separated emails for error notifications | (empty) |
Completion_Notification_Emails__c |
Comma-separated emails for completion notifications | (empty) |
Auto_Close_Reason_Enabled__c |
Enable/disable automatic reason population | false |
Auto_Close_Reason_Field__c |
API name of Case field to store the reason value | (empty) |
Create rules to define when and how Cases should be updated:
| Field | Description | Required |
|---|---|---|
DeveloperName |
Unique identifier for the rule | Yes |
MasterLabel |
Display name | Yes |
Is_Active__c |
Enable/disable this rule | Yes |
Execution_Order__c |
Order in which rules are evaluated | Yes |
Source_Status__c |
Semicolon-separated list of source statuses | Yes |
Target_Status__c |
Target status to set | Yes |
Days_Since_Last_Modified__c |
Minimum days since last modified | No |
Days_Since_Created__c |
Minimum days since created | No |
Days_Since_Last_Activity__c |
Minimum days since last activity | No |
Record_Type_Developer_Names__c |
Semicolon-separated record types to include | No |
Exclude_Record_Type_Developer_Names__c |
Semicolon-separated record types to exclude | No |
Additional_Filter_Logic__c |
Additional SOQL WHERE clause logic | No |
Stop_Processing__c |
Stop processing after this rule executes | No |
Description__c |
Rule description | No |
Target_Reason__c |
Reason value to set when rule matches (requires Auto Close Reason feature) | No |
Close Stale Waiting Cases:
- Source Status:
Waiting on Customer;Pending Response - Target Status:
Closed - Days Since Last Modified:
30 - Execution Order:
1
Escalate Old New Cases:
- Source Status:
New - Target Status:
Escalated - Days Since Created:
7 - Execution Order:
2
The Auto Close Reason feature allows you to automatically populate a reason field on Cases when rules are applied. This is useful for tracking why cases were automatically closed or transitioned.
- When enabled, the batch job will populate a configured Case field with the reason value defined in each rule
- The target field can be any text field or picklist field on the Case object
- If a rule's
Target_Reason__cis blank, no reason will be set for that rule (status will still be updated) - If the feature is enabled but the configured field doesn't exist, the batch job will abort with an error
Step 1: Create or identify your target field
You can use the standard Reason field on Case, or create a custom field:
Field Type: Text (255) or Picklist
API Name: e.g., Close_Reason__c or Reason
If using a picklist, ensure the values you configure in Target_Reason__c match the picklist values exactly.
Step 2: Configure the Custom Settings
Set the following fields in util_closer_Settings__c:
| Field | Value |
|---|---|
Auto_Close_Reason_Enabled__c |
true |
Auto_Close_Reason_Field__c |
API name of your target field (e.g., Reason or Close_Reason__c) |
Via Anonymous Apex:
util_closer_Settings__c settings = util_closer_Settings__c.getOrgDefaults();
settings.Auto_Close_Reason_Enabled__c = true;
settings.Auto_Close_Reason_Field__c = 'Reason'; // or your custom field API name
upsert settings;Step 3: Configure reasons in your rules
For each rule in util_closer_Case_Status_Rule__mdt, set the Target_Reason__c field:
| Rule | Target_Reason__c Example |
|---|---|
| Close Stale Waiting Cases | Auto-closed: No customer response |
| Escalate Old New Cases | Auto-escalated: Exceeded SLA |
Scenario: Auto-close cases waiting on customer response after 30 days and set a reason.
- Create a custom field
Close_Reason__c(Text 255) on Case, or use the standardReasonfield - Enable the feature:
util_closer_Settings__c settings = util_closer_Settings__c.getOrgDefaults(); settings.Auto_Close_Reason_Enabled__c = true; settings.Auto_Close_Reason_Field__c = 'Close_Reason__c'; upsert settings;
- Configure the rule's
Target_Reason__c:Auto-closed due to customer inactivity
When the batch runs, matching cases will have both their Status updated to "Closed" and their Close_Reason__c field populated with "Auto-closed due to customer inactivity".
If the Auto Close Reason feature is enabled but misconfigured, the batch job will fail with a descriptive error:
- No field configured: "Auto Close Reason is enabled but no field is configured..."
- Field doesn't exist: "Auto Close Reason field 'XYZ' does not exist on the Case object..."
This ensures data integrity by preventing the batch from running with an invalid configuration.
Execute the batch job immediately:
sf apex run --file scripts/apex/run-batch.apex --target-org myorgOr via Anonymous Apex:
Id batchJobId = Database.executeBatch(new util_closer_CaseStatusBatch(), 200);
System.debug('Batch Job ID: ' + batchJobId);Schedule the job using Custom Settings configuration:
sf apex run --file scripts/apex/schedule-job.apex --target-org myorgOr via Anonymous Apex:
String jobId = util_closer_CaseStatusScheduler.scheduleJob();
System.debug('Scheduled Job ID: ' + jobId);The easiest way to access the scheduler manager is through the dedicated "Case Auto-Closer" tab:
-
Access the Tab:
- Users with the
util_closer_Administratorpermission set will see the "Case Auto-Closer" tab in their Lightning navigation - Click the tab to open the scheduler manager interface
- Users with the
-
Use the Interface:
- View current job status and next execution time
- Schedule or unschedule jobs
- Update system settings (batch size, debug mode, cron expression, etc.)
- Run batch jobs immediately
- Configure email notifications
You can also add the component to any Lightning page:
- Navigate to Setup → Lightning App Builder
- Create or edit a Lightning page
- Add the
util_closer_schedulerManagercomponent - Use the UI to manage scheduler and settings
Check batch job status:
sf data query --query "SELECT Id, Status, JobType, NumberOfErrors, TotalJobItems FROM AsyncApexJob WHERE ApexClass.Name = 'util_closer_CaseStatusBatch' ORDER BY CreatedDate DESC LIMIT 5" --target-org myorgView debug logs (if Debug Mode is enabled):
sf apex get log --target-org myorgThe project includes a comprehensive test plan document (docs/development/test-plan.md) that provides step-by-step instructions for manually testing the system using Salesforce CLI commands.
You can use Cursor IDE with Composer 1 (AI assistant) to execute the test plan interactively:
-
Open the test plan:
- Open
docs/development/test-plan.mdin Cursor - The test plan contains all necessary SF CLI commands
- Open
-
Execute commands with Composer 1:
- Copy commands from the test plan
- Ask Composer 1 to execute them, for example:
- "Run the command to create a scratch org"
- "Execute the batch job using the script"
- "Check the batch job status"
-
Follow the test plan sections:
- Environment Setup: Create scratch org and deploy code
- Configure Custom Settings: Set up system configuration
- Create Test Case Data: Generate test Cases
- Backdate Test Records: Adjust dates for testing
- Run Batch Job: Execute and verify results
- Schedule Job: Test scheduled execution
- Test Scenarios: Validate different rule configurations
# 1. Create scratch org (Composer can execute this)
sf org create scratch --definition-file config/project-scratch-def.json --alias util_closer_dev_test --duration-days 7 --set-default --target-dev-hub tnoxprod
# 2. Deploy source (Composer can execute this)
sf project deploy start --target-org util_closer_dev_test
# 3. Create settings (Composer can execute this)
sf apex run --file scripts/apex/create-settings.apex --target-org util_closer_dev_test
# 4. Run batch (Composer can execute this)
sf apex run --file scripts/apex/run-batch.apex --target-org util_closer_dev_testBenefits of using Cursor + Composer 1:
- Interactive command execution
- Context-aware assistance
- Error handling and troubleshooting
- Step-by-step guidance through the test plan
- Ability to modify commands on the fly
The Case Status Auto-Closer is built on a metadata-driven architecture that separates configuration from code:
┌─────────────────────────────────────────────────────────────┐
│ Lightning Web Component │
│ (util_closer_schedulerManager) │
└───────────────────────┬───────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Scheduler Controller │
│ (util_closer_SchedulerController) │
└───────────────────────┬───────────────────────────────────────┘
│
┌───────────────┴───────────────┐
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ Scheduler │ │ Settings Service│
│ (util_closer_ │ │ (util_closer_ │
│ CaseStatus │ │ SettingsService) │
│ Scheduler) │ └──────────────────┘
└────────┬─────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Batch Processor │
│ (util_closer_CaseStatusBatch) │
└───────────────────────┬───────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Rule Engine │
│ (util_closer_RuleEngine) │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Custom Metadata Rules │ │
│ │ (util_closer_Case_Status_Rule__mdt) │ │
│ └────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
- Metadata-Driven: All business rules are stored in Custom Metadata Types
- Separation of Concerns: Clear separation between batch processing, rule evaluation, and configuration
- Extensibility: Easy to add new rules without code changes
- Testability: Comprehensive test coverage with test data factory
- Observability: Built-in logging and metrics tracking
- Scheduler triggers the batch job based on cron expression
- Batch Processor queries Cases matching rule criteria
- Rule Engine evaluates each Case against active rules in execution order
- Batch Processor updates Case statuses based on matching rules
- Notification Service sends emails if configured
- Logger records execution details (if debug mode enabled)
closinator/
├── config/
│ └── project-scratch-def.json # Scratch org definition
├── docs/
│ └── development/
│ ├── build.md # Build documentation
│ └── test-plan.md # Manual test plan
├── force-app/
│ └── main/
│ └── default/
│ ├── classes/ # Apex classes
│ ├── customMetadata/ # Custom Metadata records
│ ├── flexipages/ # Lightning App Pages
│ ├── lwc/ # Lightning Web Components
│ ├── objects/ # Custom objects/fields
│ ├── permissionsets/ # Permission sets
│ └── tabs/ # Lightning Tabs
├── scripts/
│ └── apex/ # Utility Apex scripts
└── manifest/
└── package.xml # Package manifest
Execute Apex unit tests:
sf apex run test --class-names util_closer_CaseStatusBatch_Test --target-org myorgRun all tests:
sf apex run test --code-coverage --result-format human --target-org myorgThe project includes:
- ESLint: For Lightning Web Component code
- Prettier: For code formatting
- Husky: Git hooks for pre-commit checks
- Lint-staged: Run linters on staged files
Run linting:
npm run lintFormat code:
npm run prettier- Create a feature branch
- Make your changes
- Run tests and linting
- Submit a pull request
MIT
For issues, questions, or contributions, please contact the development team or create an issue in the repository.