Catalog imports from a PIM. Inventory feeds from a warehouse. Customer exports to a CRM. Order acknowledgments to the OMS. Email batches to subscribers. Every B2C Commerce site has dozens of jobs running on schedules, plus on-demand jobs triggered by events.
The jobs framework is powerful: chained steps, conditional execution, scheduling, monitoring. It is also easy to misuse. Jobs that work in sandbox fail in production at scale. Jobs that work at scale fail when the upstream system has a bad afternoon. Jobs that handle failure on Monday silently corrupt data on Wednesday.
Sapota's Salesforce team has built and triaged jobs across catalog management, integration, and operational workflows. The patterns below are what produces reliable jobs.
What the framework provides
A job is a named workflow consisting of steps. Each step is a custom Apex-equivalent function (Salesforce calls it "Job Step Script") or a built-in system step. Steps can be chained: step 2 runs after step 1 completes; step 3 runs only if step 2 succeeded.
A typical job definition (jobs.xml fragment):
<job job-id="ImportCatalog" priority="medium">
<flow>
<context site-id="my-site"/>
<step step-id="ImportFromSFTP"
type="custom.SFTPDownload"
enforce-restart="true">
<description>Download catalog feed from SFTP</description>
</step>
<step step-id="ProcessImport"
type="custom.CatalogImport">
<description>Parse and import catalog data</description>
</step>
<step step-id="ReindexSearch"
type="custom.SearchReindex">
<description>Trigger search index rebuild</description>
</step>
</flow>
<triggers>
<run-once enabled="false"/>
<run-recurring enabled="true">
<recurrence>
<date-from>2026-01-01Z</date-from>
<start-time>02:00:00.000Z</start-time>
<interval>1</interval>
<day-of-week>
<weekday>Monday</weekday>
<weekday>Tuesday</weekday>
<weekday>Wednesday</weekday>
<weekday>Thursday</weekday>
<weekday>Friday</weekday>
</day-of-week>
</recurrence>
</run-recurring>
</triggers>
</job>
The job orchestrates the steps. The steps do the work.
Step types
3 step categories:
System steps. Built-in steps the platform ships. Examples: ProductImport, CatalogImport, CustomerImport. Configured with parameters; no custom code. Used for standard import/export from feed files.
Pipelet steps. Older pattern (SiteGenesis era). A pipelet invoked from the job. Custom code wrapped in a pipelet definition.
Custom job steps. Modern pattern. A JavaScript module that exports an execute function. The platform calls execute with job parameters and step context.
A typical custom job step:
'use strict';
var Status = require('dw/system/Status');
var Logger = require('dw/system/Logger');
function execute(args) {
try {
var inputFile = args.InputFile;
// Do the work.
return new Status(Status.OK, 'OK', 'Processed successfully');
} catch (e) {
Logger.error('Job step failed: {0}', e.message);
return new Status(Status.ERROR, 'ERROR', e.message);
}
}
module.exports.execute = execute;
The step returns a Status. The platform uses the status to decide whether to proceed to the next step or halt the job.
Chunked processing for large data sets
The default custom step runs synchronously: it starts, does its work, returns. For large operations (millions of records), this can exceed the platform's per-step timeout limits.
The chunked step pattern:
function beforeStep(args) {
// Setup: open file, initialize counters.
}
function read() {
// Return next item or null when done.
}
function process(item) {
// Transform the item.
return item;
}
function write(items) {
// Persist a batch.
}
function afterStep(success) {
// Cleanup.
}
module.exports.beforeStep = beforeStep;
module.exports.read = read;
module.exports.process = process;
module.exports.write = write;
module.exports.afterStep = afterStep;
module.exports.chunkSize = 200;
The platform handles chunking. read is called repeatedly, returning one item per call. The platform groups items into chunks (size 200 by default), calls process per item, then write per chunk.
This pattern lets a single step process millions of records without hitting timeout limits. Each chunk runs in its own transaction; failures in one chunk do not roll back the others.
Error handling and partial success
Jobs need to decide: stop on first error, or continue and report aggregated results?
Stop on first error. The step returns Status.ERROR on the first failure. The job halts. Used when the operation is genuinely all-or-nothing (e.g., a config import that should not partially apply).
Continue with logging. The step logs each failure to a tracking custom object but returns Status.OK at the end. The job continues. Used for bulk operations where partial completion is acceptable (e.g., customer import where some records may fail validation).
Most production jobs use the "continue with logging" pattern. The job report shows failures count and links to the log records for triage.
Job scheduling
The platform supports several scheduling patterns:
Cron-style. Specify days of week, time of day, frequency. The common case.
Run-once. A one-time scheduled run at a specific datetime. Used for migrations and time-bounded operations.
Triggered by event. Jobs triggered by another job's completion (chained), by a custom event, or by manual invocation. Used for downstream processing.
For most operational jobs:
- Catalog imports overnight, after the source system completes its export.
- Inventory feeds hourly or every 30 minutes, matching warehouse update cadence.
- Customer exports daily, with downstream system pickup.
- Email batches at specific times based on engagement analytics.
Schedule conflicts (multiple jobs starting at the same time) produce platform load spikes. Stagger start times.
Alerting and observability
Jobs run silently by default. When they fail, the failure is recorded in the job execution log but no one is notified.
The patterns that produce operational visibility:
1. Email alerts on failure. Configure the job to email an ops alias on Status.ERROR. Configure for sustained failures (3 consecutive failures), not for every single failure (which can be noisy).
2. Custom alerting integrations. A job step at the end of every job posts to PagerDuty / Slack / a custom monitoring API. Job success notifications are quiet; failures wake people.
3. Job execution log queries. A separate monitoring job queries the JobExecution records for failures, durations, and patterns. Detects "ran but produced zero records" cases that pure success/failure status misses.
4. Per-job dashboards. A custom Business Manager extension showing recent job runs, durations, error counts. Available to operations team for triage.
Without operational visibility, job failures fester. A daily catalog import that has been failing for 2 weeks but nobody noticed will surface when a merchandiser asks "why is this product not on the site."
Common job mistakes
Five patterns Sapota has seen in audits:
1. Unchunked processing of large data. A custom step that iterates 100,000 records in a for loop. Hits timeout. Convert to chunked.
2. No idempotency. A job that runs twice creates duplicate records. Build idempotency: check whether work was already done, skip if so. Or use database constraints to prevent duplicates.
3. Hardcoded credentials in steps. SFTP password, API key in the job step code. Move to service framework credentials.
4. Silent failures. A step catches all errors and returns Status.OK. The job appears successful. Records lost. Either let the step fail (return Status.ERROR) or log to a tracking object that ops reviews.
5. Schedule overlap. Two large jobs scheduled at the same time, both heavy. Platform resources saturate. Some steps fail with timeout. Stagger by 15-30 minutes.
What good job architecture looks like
A B2C Commerce site with healthy jobs:
- Each job has a documented purpose, schedule, and owner.
- Large data sets use chunked processing.
- Error handling explicit: stop-on-error or continue-with-logging, never silent swallow.
- Idempotent: jobs can run multiple times without producing duplicates.
- Credentials via the services framework, not in code.
- Operational alerting on sustained failures.
- Job execution log monitored, not just retained.
Jobs are the operational backbone of the platform. Programs that treat them as deploy-and-forget produce data quality issues that emerge weeks after the original failure. Programs that treat them as engineered systems with observability and discipline catch issues immediately. Sapota's Salesforce team treats jobs operations as a deliverable on every B2C Commerce engagement, not as configuration that happens at the end.
Building or auditing jobs on a B2C Commerce site? Sapota's Salesforce team, certified on B2C Commerce Developer (Comm-Dev-101), handles job architecture, chunked processing design, and operational observability on production engagements. Get in touch ->
See our full platform services for the stack we cover.