Developer guides
Run Record Health Check from Scheduled Apex
Schedule a developer-owned Record Health Check job with Scheduled Apex.
Use Scheduled Apex when Record Health Check should run automatically at a recurring time. The scheduled class should start Queueable or Batch Apex instead of checking all records itself.
Use this page when implementing a developer-owned schedule. The packaged scheduleDaily
helper is Apex-only and does not appear as a class an administrator can select directly in
Schedule Apex. A developer can provide a custom Schedulable wrapper. For a no-code schedule,
use a Scheduled-Triggered Flow with the packaged Flow action when its record selection fits.
Choose the scheduling pattern
Section titled “Choose the scheduling pattern”| Example | Use | Why |
|---|---|---|
| The same 400 Account IDs must run every day | Packaged daily scheduler | The IDs are known and intentionally stay the same. |
| Every night, check all Accounts modified in the last 30 days | Custom scheduler that starts a query-backed Batch | The matching Accounts change, so the Batch must query them each night. |
The packaged schedule captures record IDs when the schedule is created. Records added later are not included, and removed IDs are not automatically replaced. This behavior is correct only when the list of records is intentionally fixed.
Also decide where results go:
| Scheduled work | Result choices |
|---|---|
| Packaged daily scheduler | Publish ACTIONABLE or ALL Platform Events, or use NONE when only job completion matters. |
| Custom scheduler that starts a custom Batch | Save response.results directly, publish Platform Events, or retain no individual results. |
Before you start
Section titled “Before you start”- Decide whether every run uses the same record IDs or queries the records again.
- Assign the scheduling user the packaged Record Health Check User Permission Set. Use
Record Health Check Admin only when the user also configures Checks or views diagnostics.
Both include Custom Permission label: Record Health Check Run, Custom Permission API
name:
rhc__Record_Health_Check_Run, and the required Apex class access. - Confirm that the scheduling user has the object, record, field, and Custom Metadata access the selected Checks require.
- Copy the Check Set Qualified API Name from Setup → Custom Metadata Types → Record Health Check Set → Manage Records.
- Choose a result destination from the table above. Prepare the custom result object or Platform Event receiver before creating the schedule.
- Choose a stable, unique scheduled-job name.
The scheduling user’s time zone controls the start time. Verify that user’s Salesforce time zone before enabling a production schedule.
The packaged Batch automatically chooses a scope from 1 through 100 and lowers it when the selected Check Set’s formulas require a smaller scope. This is not a Setup setting. Ask a developer for a custom scheduler when an explicit, smaller tested scope is required.
Example: Schedule the same record IDs every day
Section titled “Example: Schedule the same record IDs every day”Schedule the same known IDs to run daily at 2:00 AM in the scheduling user’s time zone:
// Copy the exact Check Set Qualified API Name from Setup.// A Check Set included with the installed package might be rhc__Example_Account_Check_Builder_Guide.String checkSetApiName = 'My_Account_Checks';
String scheduledJobId = rhc.RecordHealthCheckScheduled.scheduleDaily( 'Nightly Account Health', checkSetApiName, accountIds, // Use ACTIONABLE to publish only FAIL, UNABLE_TO_EVALUATE, and ERROR. // Use ALL to publish every result, including PASS and SKIPPED. // Use NONE only when scheduled-job and Batch-job completion are enough. rhc.RecordHealthCheckEventPublication.ACTIONABLE);The packaged scheduling class accepts:
- a job name containing 1–80 characters;
- a Check Set Qualified API Name copied from Setup;
- 1–2,000 distinct, non-null record IDs;
- every remaining ID belongs to the selected Check Set object, or scheduling is rejected; and
- an explicit event-publication mode.
Invalid input or missing permission is rejected before Salesforce creates a CronTrigger.
Authorization is checked again when the schedule fires. Each firing starts the packaged Batch
class.
The returned scheduledJobId identifies the recurring schedule, not the Batch job started each
day. Monitor each Batch separately in Setup → Apex Jobs. Passing NONE creates no
health-result destination, so use it only when job completion is sufficient.
The packaged daily schedule automatically checks 1–100 records in each Batch transaction, reducing
the scope when required by the FormulaEval budget. The scheduled API does not accept an explicit
Batch size. To choose one, create a custom scheduler that calls
rhc.RecordHealthCheckBatch.run(..., scopeSize).
Example: Query the current records every time the schedule runs
Section titled “Example: Query the current records every time the schedule runs”First create the complete AccountHealthBatch described in Batch Apex. That example
uses NONE and saves response.results directly. Then create a small scheduler whose only job is
to start that Batch:
public with sharing class NightlyAccountHealthSchedule implements Schedulable { public void execute(SchedulableContext context) { // Copy the exact Check Set Qualified API Name from Setup. String checkSetApiName = 'My_Account_Checks';
// AccountHealthBatch queries current Accounts, publishes no result // events, and saves its response.results directly. Database.executeBatch(new AccountHealthBatch(checkSetApiName), 25); }}Schedule the custom class from Setup:
- In Setup, enter Apex Classes in Quick Find and select Apex Classes.
- Select Schedule Apex.
- Enter a job name, such as
Nightly Account Health. - Select
NightlyAccountHealthScheduleas the Apex class. - Choose the frequency, start date, end date, and preferred start time.
- Select Save.
- Open Setup → Scheduled Jobs and confirm that the schedule appears.
The same schedule can be created from Apex:
String scheduledJobId = System.schedule( 'Nightly Account Health', '0 0 2 * * ?', new NightlyAccountHealthSchedule());The value '0 0 2 * * ?' is Salesforce’s scheduling expression for every day at 2:00 AM in the
scheduling user’s time zone.
Monitor the complete chain
Section titled “Monitor the complete chain”To remove a schedule, open Setup → Scheduled Jobs, find the exact job name, and select Del. Confirm the job owner and next run before deleting it.
A scheduled run has three different IDs:
| ID | What it tracks |
|---|---|
CronTrigger ID | The recurring schedule |
AsyncApexJob ID | The Batch or Queueable job started by one firing |
Record Health Check runId | The health-check results created by one run |
Save these IDs together only when staff must follow one run from its schedule to its health results. Check these failures separately:
- the schedule did not fire or could not start the Batch or Queueable job;
- the Batch or Queueable job failed;
- Record Health Check returned
ERRORorUNABLE_TO_EVALUATE; or - the Flow, Apex trigger, or integration receiving Platform Events failed to process one.
Use one stable job name for one logical schedule. A random or timestamped name can create duplicate schedules and consume scheduled-job capacity. When a request changes, deliberately abort or replace the known schedule.
Test the schedule
Section titled “Test the schedule”Schedule the class between Test.startTest() and Test.stopTest(). Assert that it starts the
expected Batch or Queueable job or saves the expected result. Keep scheduler tests separate
from detailed health-result and Batch-group tests.
Before production activation, verify the schedule in a sandbox as the real scheduling user or with equivalent access.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Check first |
|---|---|
No CronTrigger is created | Custom Permission, job-name length, Check Set Qualified API Name, and record-ID count |
| The schedule exists but no Batch starts | The scheduling user’s current access and the latest Scheduled Apex failure |
| Batch completes but no outcomes are retained | Event-publication mode and the Flow, Apex trigger, integration, or storage that should receive them |
| The same records run every day | Expected for the packaged scheduling class; use a query-backed Batch to query the records again |
| The job runs at the wrong local time | The scheduling user’s Salesforce time zone and CRON expression |
| Duplicate schedules consume slots | Reuse one stable name and replace the known schedule deliberately |